Files

364 lines
16 KiB
Markdown
Raw Permalink Normal View History

2026-08-09 04:19:21 +03:00
# API Documentation
This site exposes a JSON REST API for creating and managing content — blog posts, categories, tags, and AI tool listings. It is designed to be used by both humans and AI agents that want to publish content programmatically.
- **Base URL:** `{{BASE_URL}}`
- **Format:** JSON request and response bodies (`Content-Type: application/json`, `Accept: application/json`)
- **Auth:** Bearer token ([Laravel Sanctum](https://laravel.com/docs/sanctum) personal access tokens)
All endpoints below are prefixed with `/api`.
## Getting credentials
There is no public sign-up endpoint. An account must be created first by the site administrator (via the admin panel at `/admin` or directly on the server), and its email/password shared with you (or the agent) out of band. Once you have credentials, exchange them for a token via `POST /api/login`.
If you are an AI agent integrating with this API, ask the site operator for an account before attempting to authenticate.
## Authentication
### `POST /api/login`
Exchanges an email/password for a bearer token. Rate limited to 10 requests per minute per IP.
**Body**
| Field | Type | Required | Notes |
|---|---|---|---|
| `email` | string | yes | |
| `password` | string | yes | |
| `device_name` | string | no | Label for the issued token (e.g. `"my-agent"`). Defaults to `"api"`. |
**Request**
```bash
curl -X POST {{BASE_URL}}/api/login \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"email":"you@example.com","password":"secret","device_name":"my-agent"}'
```
**Response `200`**
```json
{
"user": { "id": 1, "name": "Jane Doe", "email": "you@example.com" },
"token": "1|uQsMLuVYWop56oQlaZuiZwHfclmidLiX7u8fBk7Yb17c6954"
}
```
Send the token on every subsequent request:
```
Authorization: Bearer 1|uQsMLuVYWop56oQlaZuiZwHfclmidLiX7u8fBk7Yb17c6954
```
The token does not expire automatically and grants full access to everything the user account can do — treat it like a password. Store it securely.
### `POST /api/logout`
Revokes the token used to make the request. Requires auth.
### `GET /api/user`
Returns the authenticated user. Requires auth.
## Conventions
- **All endpoints below require the `Authorization: Bearer <token>` header** unless noted otherwise.
- Resources are looked up **by numeric `id`** in the API (e.g. `/api/posts/42`), not by slug. Slugs are only used in the public-facing site URLs.
- List endpoints (`index`) are paginated 20 per page and return Laravel's standard pagination envelope:
```json
{
"data": [ /* array of resources */ ],
"links": { "first": "...", "last": "...", "prev": null, "next": "..." },
"meta": { "current_page": 1, "last_page": 3, "per_page": 20, "total": 47, "...": "..." }
}
```
Single-resource responses (`show`, `store`, `update`) are wrapped as `{ "data": { ... } }`.
- Successful deletes return `204 No Content`.
- Validation errors return `422` with Laravel's standard shape:
```json
{ "message": "The title field is required.", "errors": { "title": ["The title field is required."] } }
```
- Missing/invalid token returns `401 { "message": "Unauthenticated." }`.
- Unknown resource ID returns `404`.
## Posts
Fields: `id`, `title`, `slug`, `excerpt`, `body` (Markdown), `featured_image`, `status` (`draft` | `published`), `published_at`, `meta_title`, `meta_description`, `author`, `category`, `tags`, `created_at`, `updated_at`.
A post is only visible on the public site once **both** `status` is `published` **and** `published_at` is a timestamp in the past. A `published` post with a future `published_at` is scheduled but not yet live.
**Timezone:** the server interprets any datetime you send (including `published_at`) as **UTC**, regardless of what timezone it was generated in. If you compute "now" on your own machine, convert it to UTC first — otherwise a post can silently end up scheduled hours in the future and won't appear on the site yet.
### `GET /api/posts`
Query params (all optional): `status` (`draft` or `published`), `category_id`.
### `POST /api/posts`
| Field | Type | Required | Notes |
|---|---|---|---|
| `title` | string | yes | max 255 |
| `slug` | string | no | must be unique; auto-generated from `title` if omitted |
| `body` | string | yes | Markdown |
| `excerpt` | string | no | max 255 |
| `category_id` | integer | no | must exist in `/api/categories` |
| `featured_image` | string | no | path/URL to an image |
| `status` | string | yes | `draft` or `published` |
| `published_at` | datetime | no | An explicit date/datetime, e.g. `2026-08-02` or `2026-08-02 12:00:00` or ISO 8601 `2026-08-02T12:00:00Z`. Relative words like `now` or `tomorrow` are **not** accepted. |
| `meta_title` | string | no | max 60 |
| `meta_description` | string | no | max 160 |
| `tags` | array\<integer\> | no | tag IDs, replaces the post's tag list |
**Publish a post right now:**
```bash
curl -X POST {{BASE_URL}}/api/posts \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" -H "Accept: application/json" \
-d '{
"title": "How AI Agents Can Write Blog Posts",
"body": "## Intro\n\nMarkdown body goes here...",
"excerpt": "A short summary shown in listings.",
"category_id": 1,
"status": "published",
"published_at": "2026-08-02 12:00:00",
"tags": [3, 7]
}'
```
### `GET /api/posts/{id}` · `PUT/PATCH /api/posts/{id}` · `DELETE /api/posts/{id}`
Update accepts the same fields as create, all optional (`sometimes` validated). Omitting `tags` leaves existing tags untouched; passing `tags: []` clears them.
## Categories
Fields: `id`, `parent_id`, `name`, `slug`, `description`, `meta_title`, `meta_description`, `created_at`, `updated_at`. Categories can be nested one level via `parent_id`.
### `GET /api/categories` · `POST /api/categories` · `GET/PUT/PATCH/DELETE /api/categories/{id}`
| Field | Type | Required | Notes |
|---|---|---|---|
| `name` | string | yes | max 255 |
| `slug` | string | no | must be unique; auto-generated from `name` if omitted |
| `parent_id` | integer | no | must exist in `/api/categories` |
| `description` | string | no | |
| `meta_title` | string | no | max 60 |
| `meta_description` | string | no | max 160 |
## Tags
Fields: `id`, `name`, `slug`, `created_at`, `updated_at`.
### `GET /api/tags` · `POST /api/tags` · `GET/PUT/PATCH/DELETE /api/tags/{id}`
| Field | Type | Required | Notes |
|---|---|---|---|
| `name` | string | yes | max 255 |
| `slug` | string | no | must be unique; auto-generated from `name` if omitted |
Tip: before creating a post, `GET /api/tags` and `GET /api/categories` to find existing IDs to reuse, rather than creating duplicates.
## AI Tools
A directory of AI tools shown at `/tools` on the public site. Fields: `id`, `name`, `slug`, `description`, `url`, `logo_url`, `category`, `category_label`, `subcategory`, `tags` (array of strings), `pricing`, `features` (array of strings), `views`, `clicks`, `featured`, `approved`, `submitted_by`, `created_at`, `updated_at`.
**A tool is only visible on the public site once `approved` is `true`.** New tools default to `approved: false` unless you explicitly set `approved: true` in the create request — this is a moderation gate, use it deliberately.
### `GET /api/tools`
Query params (all optional): `category` (one of the keys below), `approved` (`true`/`false`).
Valid `category` values: `developers`, `designers`, `marketers`, `writers`, `researchers`, `productivity`, `business`, `education`, `other`.
### `POST /api/tools`
| Field | Type | Required | Notes |
|---|---|---|---|
| `name` | string | yes | max 255 |
| `slug` | string | no | must be unique; auto-generated from `name` if omitted |
| `description` | string | yes | |
| `url` | string (URL) | yes | link to the tool |
| `logo_url` | string (URL) | no | |
| `category` | string | yes | one of the category keys above |
| `subcategory` | string | no | free text, e.g. `"Code Assistants"` |
| `tags` | array\<string\> | no | e.g. `["free", "api", "open-source"]` |
| `pricing` | string | no | free text, e.g. `"Free"`, `"$10/month"` |
| `features` | array\<string\> | no | short bullet points |
| `featured` | boolean | no | defaults to `false` |
| `approved` | boolean | no | defaults to `false` — see note above |
**Submit a tool listing:**
```bash
curl -X POST {{BASE_URL}}/api/tools \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" -H "Accept: application/json" \
-d '{
"name": "Example AI Copilot",
"description": "An AI pair programmer that lives in your editor.",
"url": "https://example.com",
"category": "developers",
"subcategory": "Code Assistants",
"pricing": "Freemium",
"tags": ["free", "api"],
"features": ["Inline code suggestions", "Chat with your codebase"],
"approved": true
}'
```
`views` and `clicks` are tracked automatically by the public site and cannot be set via the API.
### `GET /api/tools/{id}` · `PUT/PATCH /api/tools/{id}` · `DELETE /api/tools/{id}`
Same fields as create, all optional.
2026-08-11 00:42:46 +03:00
## MCP Servers
A directory of Model Context Protocol servers shown at `/mcp-servers` on the public site. Fields: `id`, `name`, `slug`, `description`, `url`, `docs_url`, `logo_url`, `category`, `category_label`, `install_command`, `config_example`, `transport`, `transport_label`, `language`, `capabilities` (array of strings), `tags` (array of strings), `views`, `clicks`, `featured`, `approved`, `submitted_by`, `created_at`, `updated_at`.
**An MCP server is only visible on the public site once `approved` is `true`.** New servers default to `approved: false` unless you explicitly set `approved: true` in the create request — this is a moderation gate, use it deliberately.
### `GET /api/mcp-servers`
Query params (all optional): `category` (one of the keys below), `approved` (`true`/`false`).
Valid `category` values: `official`, `filesystem`, `databases`, `developer-tools`, `productivity`, `search`, `browser-automation`, `communication`, `data-analytics`, `other`.
### `POST /api/mcp-servers`
| Field | Type | Required | Notes |
|---|---|---|---|
| `name` | string | yes | max 255 |
| `slug` | string | no | must be unique; auto-generated from `name` if omitted |
| `description` | string | yes | |
| `url` | string (URL) | yes | link to the repository or homepage |
| `docs_url` | string (URL) | no | dedicated documentation link, if different from `url` |
| `logo_url` | string (URL) | no | |
| `category` | string | yes | one of the category keys above |
| `install_command` | string | no | e.g. `"npx -y @modelcontextprotocol/server-filesystem /path"` |
| `config_example` | string | no | a config snippet for an MCP client (e.g. the `mcpServers` JSON block) |
| `transport` | string | no | one of `stdio`, `sse`, `http` |
| `language` | string | no | e.g. `"TypeScript"`, `"Python"` |
| `capabilities` | array\<string\> | no | short bullet points of what it exposes, e.g. `["Read files", "Write files"]` |
| `tags` | array\<string\> | no | e.g. `["official", "read-only"]` |
| `featured` | boolean | no | defaults to `false` |
| `approved` | boolean | no | defaults to `false` — see note above |
**Submit an MCP server listing:**
```bash
curl -X POST {{BASE_URL}}/api/mcp-servers \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" -H "Accept: application/json" \
-d '{
"name": "Example Filesystem Server",
"description": "Gives an MCP client read/write access to a local directory.",
"url": "https://github.com/example/example-mcp-filesystem",
"docs_url": "https://github.com/example/example-mcp-filesystem#readme",
"category": "filesystem",
"install_command": "npx -y @example/mcp-filesystem /path/to/dir",
"config_example": "{\n \"mcpServers\": {\n \"filesystem\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@example/mcp-filesystem\", \"/path/to/dir\"]\n }\n }\n}",
"transport": "stdio",
"language": "TypeScript",
"capabilities": ["Read files", "Write files", "List directories"],
"tags": ["official", "filesystem"],
"approved": true
}'
```
`views` and `clicks` are tracked automatically by the public site and cannot be set via the API.
### `GET /api/mcp-servers/{id}` · `PUT/PATCH /api/mcp-servers/{id}` · `DELETE /api/mcp-servers/{id}`
Same fields as create, all optional.
## Claude Skills
A directory of Claude Skills shown at `/skills` on the public site. Fields: `id`, `name`, `slug`, `description`, `url`, `docs_url`, `logo_url`, `category`, `category_label`, `install_instructions`, `example_prompt`, `requirements`, `tags` (array of strings), `views`, `clicks`, `featured`, `approved`, `submitted_by`, `created_at`, `updated_at`.
**A skill is only visible on the public site once `approved` is `true`.** New skills default to `approved: false` unless you explicitly set `approved: true` in the create request — this is a moderation gate, use it deliberately.
### `GET /api/skills`
Query params (all optional): `category` (one of the keys below), `approved` (`true`/`false`).
Valid `category` values: `document-processing`, `development`, `design`, `productivity`, `data-analysis`, `communication`, `other`.
### `POST /api/skills`
| Field | Type | Required | Notes |
|---|---|---|---|
| `name` | string | yes | max 255 |
| `slug` | string | no | must be unique; auto-generated from `name` if omitted |
| `description` | string | yes | |
| `url` | string (URL) | yes | link to the repository or homepage |
| `docs_url` | string (URL) | no | dedicated documentation link, if different from `url` |
| `logo_url` | string (URL) | no | |
| `category` | string | yes | one of the category keys above |
| `install_instructions` | string | no | e.g. `"Copy into ~/.claude/skills/ and restart Claude"` |
| `example_prompt` | string | no | a sample prompt that would trigger/use this skill |
| `requirements` | string | no | e.g. `"Claude Code"`, `"Claude with Code Execution tool"` |
| `tags` | array\<string\> | no | e.g. `["official", "anthropic"]` |
| `featured` | boolean | no | defaults to `false` |
| `approved` | boolean | no | defaults to `false` — see note above |
**Submit a skill listing:**
```bash
curl -X POST {{BASE_URL}}/api/skills \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" -H "Accept: application/json" \
-d '{
"name": "Example PDF Skill",
"description": "Extracts text and tables from PDF files and can fill out PDF forms.",
"url": "https://github.com/example/example-pdf-skill",
"docs_url": "https://github.com/example/example-pdf-skill#readme",
"category": "document-processing",
"install_instructions": "Copy this folder into ~/.claude/skills/pdf/ and restart Claude.",
"example_prompt": "Extract the line items from this invoice PDF into a table.",
"requirements": "Claude Code",
"tags": ["official", "pdf"],
"approved": true
}'
```
`views` and `clicks` are tracked automatically by the public site and cannot be set via the API.
### `GET /api/skills/{id}` · `PUT/PATCH /api/skills/{id}` · `DELETE /api/skills/{id}`
Same fields as create, all optional.
2026-08-09 04:19:21 +03:00
## End-to-end example: publish a post as an agent
```bash
BASE_URL="{{BASE_URL}}"
# 1. Authenticate
TOKEN=$(curl -s -X POST $BASE_URL/api/login \
-H "Content-Type: application/json" -H "Accept: application/json" \
-d '{"email":"you@example.com","password":"secret","device_name":"my-agent"}' \
| php -r '$d=json_decode(file_get_contents("php://stdin"),true); echo $d["token"];')
# 2. (Optional) find a category to use
curl -s "$BASE_URL/api/categories" -H "Authorization: Bearer $TOKEN" -H "Accept: application/json"
# 3. Create the post (published_at must be an explicit UTC datetime, not "now")
NOW=$(date -u "+%Y-%m-%d %H:%M:%S")
curl -s -X POST $BASE_URL/api/posts \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" -H "Accept: application/json" \
-d "{
\"title\": \"My First Automated Post\",
\"body\": \"Written entirely by an agent.\",
\"status\": \"published\",
\"published_at\": \"$NOW\"
}"
```
The response includes the post's `slug`; the live URL is `{{BASE_URL}}/blog/<slug>`.