Skip to content

Media

Media assets — images and other uploads — live on the site that owns the API key. Upload one, get its id, and reference that id from a post body image block. The site is always derived from the key, so a key can only ever see and touch its own assets.

  • Base URL: https://api.canverly.com
  • Auth: Bearer API key — Authorization: Bearer ck_…
  • Rate limit: 60 req/min per key
Method Path Scope
GET /v1/media media:read
POST /v1/media media:write
GET /v1/media/{id} media:read
DELETE /v1/media/{id} media:write

List the site’s assets, newest first. The listing is not cursor-paginated: you ask for a page size with limit.

Parameter Type Required Notes
limit integer no 1..500, default 100. Out of range (or not an integer) returns 400 — it is never silently clamped.
folder_id string no Restrict to one folder.
tag_id string no Restrict to one tag.
Janela do terminal
curl "https://api.canverly.com/v1/media?limit=20" \
-H "Authorization: Bearer ck_live_xxx"
{
"items": [
{
"id": "019eedd0-1111-7000-8000-000000000001",
"key": "sites/01KVPWYKT6AJDGG2EJN7DW4VT5/2026/08/hero.png",
"mime": "image/png",
"bytes": 184320,
"width": 1600,
"height": 900,
"alt_text": "Dashboard screenshot",
"hash": "9f2b1c7d5e4a3b8c6d0f1e2a3b4c5d6e7f8091a2b3c4d5e6f7081920a3b4c5d6",
"status": "ready",
"original_url": "https://cdn.example.com/sites/01KVPWYKT6AJDGG2EJN7DW4VT5/2026/08/hero.png"
}
]
}

The response is always { "items": [...] } — the array is empty when the site has no assets (or none match the filters).

Upload one asset as multipart/form-data. The file part must be named literally file — no other part name is read.

  • Content-Type: multipart/form-data (anything else returns 400)
  • Max body: 8 MiB (over it returns 413)
  • Scope: media:write
Janela do terminal
curl -X POST https://api.canverly.com/v1/media \
-H "Authorization: Bearer ck_live_xxx" \
-F "file=@./hero.png"

The asset is stored immediately, but its derived variants are generated asynchronously. That is why the call answers 202 and the asset comes back with status: "processing":

{
"id": "019eedd0-1111-7000-8000-000000000001",
"key": "sites/01KVPWYKT6AJDGG2EJN7DW4VT5/2026/08/hero.png",
"mime": "image/png",
"bytes": 184320,
"width": null,
"height": null,
"alt_text": null,
"hash": "9f2b1c7d5e4a3b8c6d0f1e2a3b4c5d6e7f8091a2b3c4d5e6f7081920a3b4c5d6",
"status": "processing",
"original_url": null
}

Poll GET /v1/media/{id} until status flips to "ready". The id is usable as soon as you have it — you do not have to wait for "ready" to record it.

  • Not multipart — sending JSON (or no Content-Type) returns 400:

    { "error": { "code": "bad_request", "message": "expected multipart/form-data body with a `file` field" } }
  • Body over 8 MiB — 413. This is the same 8 MiB ceiling the rest of the public edge enforces.

  • Unsupported file type — 415, relayed from the media service.

  • Key without authorship context — a key minted before author/organization context existed cannot upload. It returns 422:

    { "error": { "code": "unprocessable", "message": "this api key has no org context; re-create it in Configurações → Integrações API" } }

    The fix is on the owner’s side: delete the key and generate a new one in Configurações → Integrações API (see Authentication).

Read one asset. Use it to poll an upload until it is "ready".

Janela do terminal
curl https://api.canverly.com/v1/media/019eedd0-1111-7000-8000-000000000001 \
-H "Authorization: Bearer ck_live_xxx"
{
"id": "019eedd0-1111-7000-8000-000000000001",
"key": "sites/01KVPWYKT6AJDGG2EJN7DW4VT5/2026/08/hero.png",
"mime": "image/png",
"bytes": 184320,
"width": 1600,
"height": 900,
"alt_text": "Dashboard screenshot",
"hash": "9f2b1c7d5e4a3b8c6d0f1e2a3b4c5d6e7f8091a2b3c4d5e6f7081920a3b4c5d6",
"status": "ready",
"original_url": "https://cdn.example.com/sites/01KVPWYKT6AJDGG2EJN7DW4VT5/2026/08/hero.png"
}

An id that belongs to another site returns 404, exactly like an id that does not exist — existence is never disclosed:

{ "error": { "code": "not_found", "message": "media asset not found" } }

Soft-delete an asset.

Janela do terminal
curl -X DELETE https://api.canverly.com/v1/media/019eedd0-1111-7000-8000-000000000001 \
-H "Authorization: Bearer ck_live_xxx" \
-i

No response body. As with GET, an id from another site returns 404 and not 403.

Field Type Notes
id string Asset identifier — this is what you put in a post’s image block.
key string Object path in storage.
mime string Detected MIME type, e.g. image/png.
bytes integer Stored size in bytes.
width integer | null Pixel width; null when unknown (typically while status is processing, or for non-image types).
height integer | null Pixel height; same caveat as width.
alt_text string | null Alternative text, when set.
hash string Content hash of the stored object.
status string "processing" while variants are being generated, "ready" afterwards.
original_url string (URI) | null URL of the original object, when available.

An image block in a post body can carry the asset id as attrs.media_id instead of a URL. When the post is served, an image recorded as a media id comes back with the URL already resolved in content_html; if the asset has been deleted or is unavailable, the block is omitted rather than rendered as a broken <img>.

{
"v": 1,
"blocks": [
{
"id": "img1",
"type": "image",
"attrs": {
"media_id": "019eedd0-1111-7000-8000-000000000001",
"alt": "Dashboard screenshot"
}
}
]
}

See Content blocks for the block document format and POST /v1/posts for creating the post itself.

Envelope: { "error": { "code", "message" } }. Full table in Errors & rate limits.

Status error.code When
400 bad_request limit not an integer or outside 1..500 (list); body not multipart/form-data, or no file part (upload).
401 unauthorized Missing, malformed, unknown or revoked key — including a pk_… public read key.
403 forbidden Key lacks media:read (list/get) or media:write (upload/delete).
404 not_found No such asset on this site — same answer for another site’s id.
413 — Upload body over 8 MiB.
415 — Unsupported media type, relayed from the media service.
422 unprocessable Upload with a key that has no organization context — re-create the key.
429 rate_limited Over 60 req/min — honor Retry-After.
5xx upstream Transient; retry with backoff. Internal detail is redacted from the message.