Assets API reference
Read your final assets and your published decks over HTTP with a key you make yourself. The Assets API is the shorter introduction; openapi.yaml is the machine-readable contract, also served at /.well-known/openapi and linked from every response as rel="service-desc".
The endpoints
GET /v1/assets- A page of your final assets, newest first. Filter by
name,formatorupdated_since; page withcursor. GET /v1/assets/{id}- One asset, with a freshly signed URL. This is how you refresh a link that has expired.
GET /v1/assets/{id}/content- Redirects to the file itself, so fetching one is a single
curl -Lrather than a list, a parse and a second request. GET /v1/decks- The decks you have published to a link, with the link and the PDF.
GET /v1/decks/{id}- One deck, with a fresh PDF link.
GET /v1/decks/{id}/content- Redirects to the PDF. The same file a viewer of the link downloads.
GET /v1/me- Whose library the key reads, which plan is behind it and what the key may do. The cheapest way to check a key works at all.
Polling, and how deletions reach you
Send the ETag back as If-None-Match and an unchanged library answers 304 with no body, which costs you nothing and us no signing. The ETag is of the data rather than of the body, so it moves when an asset is renamed or when its upload finishes, not only when one is added.
updated_since returns only what has changed, and it is the only mode that reports deletions: an asset the owner has deleted comes back as an id, deleted: true and a date, so a mirror can drop it. Without that parameter, deleted assets are simply absent, because a first read does not want a graveyard. There are no webhooks.
Rate limits
600 requests per key per hour. Every response carries RateLimit-Policy and RateLimit as structured fields, so you can pace yourself before being refused rather than after. A 429 also carries Retry-After, which takes precedence over both.
Requests with an invalid key are limited separately, by address. A working key is never affected by that one.
Sizes
Leave w and h off and you get the file as it was rendered, at full size. A 4K export comes back 3840 pixels wide.
Add either to get a smaller copy. The range is 1 to 2500 pixels, which is the image transformer's limit rather than ours, and a source over 50 megapixels cannot be transformed at all. The size is signed into the URL, so a link cannot be widened after it has been handed out, and that is cost control rather than a restriction: transformations are billed per original image.
A browser cannot call this
There is no CORS header and that is deliberate, not an omission: a key in client-side JavaScript is a key anyone who opens the page can read and spend. This is an API for a build script, a CMS, a plugin or an agent: things that can hold a secret. If you need browser access, mint short-lived tokens on your own server and proxy through it.
Errors
Every refusal is an RFC 9457 problem document, sent as application/problem+json. Branch on type: the slug is stable and will not be reworded, where title may be. A 401 carries WWW-Authenticate and a 405 carries Allow.
missing-key 401
No Authorization header. Send it as `Authorization: Bearer asp_live_…`.
invalid-key 401
The key is malformed, revoked, expired, or was never real. These are deliberately indistinguishable: telling them apart would confirm to somebody holding a withdrawn key that it had once been live.
insufficient-scope 403
The key is yours and does not carry `assets:read`. Make a new one.
no-cloud-plan 403
The account has no cloud plan. The API reads final assets out of cloud storage, so there is nothing for it to serve without one. Base, Growth and Pro are identical here.
invalid-argument 400
A query parameter is out of range or the wrong shape. The `detail` says which and what is allowed. Nothing is silently coerced: a value the contract does not accept is refused rather than resolved to a default.
invalid-cursor 400
The `cursor` was not one of ours. Pass back `next_cursor` from the previous response, unchanged. It is opaque, so building one by hand is not supported.
not-found 404
No such asset or deck. A record belonging to another account answers the same way, because a 403 would confirm the id exists and let a stranger map a library by status code.
not-uploaded 409
On `/content` only: the record exists but its bytes have not finished uploading. The rest of the record is readable now, and the file will arrive.
method-not-allowed 405
Every endpoint here is read-only. The `Allow` header says so.
rate-limited 429
600 requests per key per hour, or too many requests with an invalid key from one address. `Retry-After` is the accurate one. A working key is never affected by the second limit.
unavailable 503
Our fault, not yours: a dependency did not answer. Safe to retry.
internal-error 500
An unhandled fault. Quote the `request_id` from the response if you get in touch.
What it will not do
It never writes. There is no endpoint that creates, changes or deletes anything, and a key cannot be given one. The scope on every key is read-only, which is what stops a key made today quietly becoming a write key the day writing ships.
It serves no bytes. Every response is metadata and a signed URL, and storage serves the file, so a call costs the same whether the asset is a 40 KB PNG or a 400 MB video. A resize is signed into the URL, so a link cannot be widened after it has been handed out.
It returns no paths from outside the library. Sources can be folders you pointed Aspects at anywhere on disk, so they are absent here entirely, which is the same decision the MCP server made.
It reads final assets and published decks, and nothing else. Artboards, notes and raw exports stay out: both of these are things you deliberately marked as finished, and an API over everything would be a file browser.
Versioning
The version is in the path. A change that could break a client gets a new path and /v1 keeps working. Adding a field is not a breaking change and will happen without notice, so ignore fields you do not recognise. If a version is ever retired it will say so in its own responses first, with Deprecation and Sunset headers. Neither is sent today.
