# mediamart > One flat media library. Drop a file, it is auto-tagged and re-rendered for every > platform surface. Originals are never modified; each platform variant is a separate object. Base URL: `http://localhost:4000` (development). The OpenAPI 3.1 document is at [`/openapi.json`](http://localhost:4000/openapi.json); the human guide is [`/docs`](http://localhost:4000/docs) and the MCP guide is `docs/api/mcp.md`. ## Auth in three lines 1. Web uses the `mediamart_session` cookie set by signup/login; machines use `Authorization: Bearer mm_<48 hex>` minted by `POST /v1/keys`. 2. Signup/login also return `{ token }` in the body — send it as `Authorization: Bearer ` from clients that cannot store cookies. 3. The MCP endpoint (`POST /v1/mcp`) accepts **only** an API key, never the session cookie. ## The six endpoints that matter ### 1. `POST /v1/auth/signup` — create an account and workspace ```bash curl -sS http://localhost:4000/v1/auth/signup \ -H 'content-type: application/json' \ -d '{"name":"Ada Lovelace","email":"ada@example.com","password":"correct-horse-battery"}' ``` Returns `201 { user, token }` and sets the session cookie. `409 EMAIL_TAKEN` if the address exists. ### 2. `POST /v1/auth/login` — sign in ```bash curl -sS http://localhost:4000/v1/auth/login \ -H 'content-type: application/json' \ -d '{"email":"ada@example.com","password":"correct-horse-battery"}' ``` Returns `200 { user, token }`. Limited to 10 attempts per email address per 15 minutes (`429 rate_limited`). ### 3. `POST /v1/keys` — mint a machine key ```bash curl -sS http://localhost:4000/v1/keys \ -H "authorization: Bearer $TOKEN" \ -H 'content-type: application/json' \ -d '{"name":"laptop"}' ``` Returns `201 { id, key }`. `key` is shown **once**; only its SHA-256 is stored. Revoke with `DELETE /v1/keys/{id}`. ### 4. `POST /v1/assets` — create an upload ticket ```bash curl -sS http://localhost:4000/v1/assets \ -H "authorization: Bearer $KEY" \ -H 'content-type: application/json' \ -d '{"name":"hero.mp4","contentType":"video/mp4","bytes":10485760}' ``` Returns `201 { assetId, uploadUrl, key, expiresIn: 600 }`. The kind is derived from `contentType`; accepted types are JPEG/PNG/WebP/AVIF/GIF/HEIC, MP4/MOV/WebM, MP3/M4A/WAV/OGG and PDF, up to 200 MB. ### 5. `POST /v1/assets/{id}/finalize` — upload the bytes, then finalize ```bash curl -sS -X PUT "$UPLOAD_URL" -H 'content-type: video/mp4' --data-binary @hero.mp4 curl -sS -X POST http://localhost:4000/v1/assets/$ASSET_ID/finalize \ -H "authorization: Bearer $KEY" ``` Finalize HEADs the object, records its real size and moves the asset to `analyzing`. It returns immediately. `400 UPLOAD_MISSING` when no object was PUT; `409 QUOTA_EXCEEDED` when the plan storage quota would be exceeded. ### 6. `GET /v1/assets` — list/search ```bash curl -sS "http://localhost:4000/v1/assets?limit=30&platform=instagram" \ -H "authorization: Bearer $KEY" ``` List filters: `q`, `tags` (comma-separated, all must match), `platform`, `limit` (≤100, default 30), `cursor` (pass `next` back). For one asset, poll `GET /v1/assets/$ASSET_ID` (also useful as the finalize follow-up): signed URLs expire after 900 seconds; poll until `status` is `ready` or `failed`; `thumbUrl` is the card derivative (poster frame for video, waveform for audio). ```bash curl -sS "http://localhost:4000/v1/assets/$ASSET_ID" -H "authorization: Bearer $KEY" ``` Other live routes: `POST /v1/auth/logout`, `/v1/auth/logout-all`, `/v1/auth/me`, `/v1/auth/password`, `/v1/auth/password-reset`, `/v1/auth/password-reset/confirm`, `GET/DELETE /v1/keys`, `GET /v1/assets/suggest`, `DELETE /v1/assets/{id}`, `POST /v1/plan`, `GET /v1/platforms`, `GET /v1/usage`, `GET /health`. ## MCP `POST /v1/mcp` — stateless JSON-RPC 2.0 over Streamable HTTP. Send one message per request: ```bash curl -sS http://localhost:4000/v1/mcp \ -H "authorization: Bearer $KEY" \ -H "content-type: application/json" \ -H "mcp-protocol-version: 2026-07-28" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' ``` Methods: `initialize`, `ping`, `tools/list`, `tools/call`; notifications get `202` with no body. Protocol versions: `2026-07-28` (current), `2025-06-18`, `2025-03-26`, `2024-11-05`; an unsupported version is JSON-RPC `-32022` with the supported list in `error.data`. `GET`/`DELETE` return `405` with `Allow: POST`. A tool call returns `result.content[0].text` containing JSON-stringified payload and `result.isError`. Tool failure codes: `VALIDATION`, `NOT_FOUND`, `PAYLOAD_TOO_LARGE`, `UPLOAD_MISSING`, `QUOTA_EXCEEDED`, `INTERNAL`. The four tools: ### `mediamart_search` Search the workspace library by text, tags or platform and return matching assets with their ready derivatives. Argument schema: ```json { "type": "object", "properties": { "query": { "type": "string", "maxLength": 200, "description": "Free text matched against the title and the AI caption." }, "tags": { "type": "array", "items": { "type": "string", "maxLength": 64 }, "maxItems": 20, "description": "Tag names the asset must carry (all of them)." }, "platform": { "type": "string", "maxLength": 40, "description": "Only assets that already have a derivative for this platform." }, "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 30 } }, "additionalProperties": false } ``` Call it with `{"jsonrpc":"2.0","id":"1","method":"tools/call","params":{"name":"mediamart_search","arguments":{...}}}`. ### `mediamart_get` Fetch one asset by id with its signed original and thumbnail URLs, dimensions, tags and derivatives. Argument schema: ```json { "type": "object", "properties": { "assetId": { "type": "string", "description": "The `id` returned by search or upload." } }, "required": [ "assetId" ], "additionalProperties": false } ``` Call it with `{"jsonrpc":"2.0","id":"1","method":"tools/call","params":{"name":"mediamart_get","arguments":{...}}}`. ### `mediamart_plan` Compute crop and resize plans for a source against the platform preset registry before uploading anything. Argument schema: ```json { "type": "object", "properties": { "kind": { "type": "string", "enum": [ "image", "video" ] }, "width": { "type": "integer", "minimum": 1, "description": "Source width in pixels." }, "height": { "type": "integer", "minimum": 1, "description": "Source height in pixels." }, "bytes": { "type": "integer", "minimum": 0, "description": "Source size in bytes." }, "platforms": { "type": "array", "items": { "type": "string" }, "description": "Platform keys to plan for; every preset when omitted." } }, "required": [ "kind", "width", "height", "bytes" ], "additionalProperties": false } ``` Call it with `{"jsonrpc":"2.0","id":"1","method":"tools/call","params":{"name":"mediamart_plan","arguments":{...}}}`. ### `mediamart_upload` Upload a base64-encoded original (at most 10 MB decoded); the server writes the object, finalizes the asset and enqueues the pipeline. Argument schema: ```json { "type": "object", "properties": { "name": { "type": "string", "maxLength": 255, "description": "File name, extension included." }, "contentType": { "type": "string", "maxLength": 128, "description": "MIME type of the bytes." }, "dataBase64": { "type": "string", "description": "The file bytes, base64-encoded." } }, "required": [ "name", "contentType", "dataBase64" ], "additionalProperties": false } ``` Call it with `{"jsonrpc":"2.0","id":"1","method":"tools/call","params":{"name":"mediamart_upload","arguments":{...}}}`. Worked `tools/call` example: ```bash curl -sS http://localhost:4000/v1/mcp \ -H "authorization: Bearer $KEY" \ -H "content-type: application/json" \ -d '{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"mediamart_search","arguments":{"query":"hero","limit":5}}}' ``` ## Rate limits - API keys: 120 requests/minute per key (`API_KEY_RATE_LIMIT`). Every keyed response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (unix seconds); over the limit is `429 { "error": "RATE_LIMITED" }` with `Retry-After`. - Auth endpoints have their own in-process windows: signup 5/15 min per IP, login 10/15 min per email, password reset 5/15 min per email. These answer `429 { "error": "rate_limited" }`. - MCP requests also pass through the per-key window (429 in the REST envelope, before JSON-RPC parsing). ## Quotas and their errors - `GET /v1/usage` returns `{ plan, analyses: { used, limit }, exports: { used, limit }, storage: { usedBytes, limitBytes } }`. A `limit` of `-1` means unlimited. - Free: 500 analyses/month, unlimited exports, 2 GB storage. Pro: 10,000 / 100 GB. Team: 100,000 / 1 TB. Scale: unlimited / 10 TB. - `409 { "error": "QUOTA_EXCEEDED" }` from finalize when the upload would exceed `storage.limitBytes`. - `400 { "error": "UPLOAD_MISSING" }` when finalize cannot find the object. - `429 { "error": "RATE_LIMITED" }` (or `rate_limited` on auth) means back off until `X-RateLimit-Reset` / `Retry-After`. - `POST /v1/plan` answers an invalid body with HTTP 200 and `{ "error": "invalid_request", "issues": string[] }` — check for `error` before `data`.