Endpoints
Every REST route in the Raster API, with parameters, responses, and examples.
Organization endpoints live under
https://api.raster.app/organizations/:organizationId/...; GET /me and POST /libraries sit at the root. The
:organizationId segment is your organization slug. Library-scoped endpoints
add /libraries/:libraryId. Every request requires the Api-Version: 2026-07-08 header, and every request except POST /libraries also requires
Authorization: Bearer <API_KEY>
(see Authentication).
| Method | Path | Access | Description |
|---|---|---|---|
GET | /me | Read | Resolve your key's organization + library scope. |
POST | /libraries | Anonymous | Create an organization + library + API key with no account. |
GET | /organizations/:orgId/libraries | Read | List libraries in an organization. |
POST | /organizations/:orgId/libraries | Write | Create a library in an organization. |
PATCH | /organizations/:orgId/libraries/:libraryId | Write | Rename a library. |
GET | /organizations/:orgId/libraries/:libraryId/assets | Read | List assets in a library. |
GET | /organizations/:orgId/libraries/:libraryId/assets/:assetId | Read | Get one asset by id. |
GET | /organizations/:orgId/search/assets | Read | Search assets across libraries. |
GET | /organizations/:orgId/libraries/:libraryId/tags | Read | List tags in a library. |
POST | /organizations/:orgId/libraries/:libraryId/assets | Write | Upload one or more assets. |
DELETE | /organizations/:orgId/libraries/:libraryId/assets | Write | Move assets to trash by id (soft delete). |
POST | /organizations/:orgId/libraries/:libraryId/assets/tag | Write | Apply tags to a batch of assets. |
POST | /organizations/:orgId/libraries/:libraryId/assets/untag | Write | Remove tags from a batch of assets. |
PATCH | /organizations/:orgId/libraries/:libraryId/assets/:assetId/description | Write | Replace the description on a single asset. |
PATCH | /organizations/:orgId/libraries/:libraryId/assets/:assetId/approval | Read | Set the approval state of a single asset (OAuth only). |
POST | /organizations/:orgId/libraries/:libraryId/assets/transfer | Write | Move a batch of assets to another library. |
POST | /organizations/:orgId/libraries/:libraryId/assets/:assetId/promote | Write | Make a variant the asset's default. |
GET /me
Resolves the organizations and libraries your credential (an API key or an
OAuth access token) can act on. Call this first when you hold only a credential: it
returns the organizationId (and authorized library ids) that every other
endpoint needs as a path segment. A key, or an OAuth token bound to one
organization, resolves that single organization; a token granted your whole
account resolves every organization you belong to under organizations. Send the
Authorization: Bearer and Api-Version headers as on every authenticated
request.
Returns
{ data: { organizationId, organizationName, plan, libraries, user } }:
organizationId: the organization your key belongs to; pass it to every org-scoped endpoint.organizationName: the organization's display name, ornullwhen it has none.plan: the organization's plan (e.g.pro,hobby), ornullwhen none is set.libraries: one{ library, access }entry per library your key can act on.accessis"write"(read and write) or"read"(read-only). UseGET /organizations/:orgId/librariesfor their names and counts.user:{ name }for an OAuth access token, naming the account the token acts as so a client can label its connection.nameisnullwhen the account records none. Absent for an API key, which belongs to an organization rather than a person (as in the example below).
Example
curl --request GET \
--header 'Authorization: Bearer <API_KEY>' \
--header 'Api-Version: 2026-07-08' \
--url 'https://api.raster.app/me'Response:
{
"data": {
"organizationId": "acme-co",
"organizationName": "Acme Co",
"plan": "pro",
"libraries": [
{ "library": "marketing", "access": "write" },
{ "library": "product-shots", "access": "write" },
{ "library": "archive", "access": "read" }
]
}
}POST /libraries
Creates a new organization, with a starter library and an API key for it, for a caller who has no Raster account and no API key yet. A client (typically an AI agent) calls it with a person's email, gets back a ready-to-use key, and starts uploading right away. The new library is held for 30 days so the person can claim it: they open the emailed link to move the library and everything in it into a real Raster account. Anything left unclaimed when the window closes is removed.
The same flow is available over MCP as the
create_organization tool. To create a
library inside an existing organization, use
POST /organizations/:orgId/libraries instead.
POST to https://api.raster.app/libraries. The endpoint is anonymous: it
creates the key, so send the request with no Authorization header. Include the
Api-Version header and read the { data } / { error } envelope as on every
other request. The endpoint has a per-IP rate limit (429) and the per-email
quota below.
Parameters
A JSON request body:
Prop
Type
Returns
{ data: { organizationId, libraryId, apiKey, claimUrl, expiresAt, emailSent } }:
organizationIdandlibraryId: identify the new library. Pass both to every later asset call, e.g.POST /organizations/:orgId/libraries/:libraryId/assets.apiKey: the Bearer token for those calls. It is returned only once, so store it immediately.claimUrl: the link the user opens to claim the library into a Raster account.expiresAt: ISO 8601 timestamp for when the claim window closes.emailSent:trueif the claim link was emailed. Whenfalse, surfaceclaimUrlto the user directly.
Example
curl --request POST \
--header 'Api-Version: 2026-07-08' \
--header 'Content-Type: application/json' \
--url 'https://api.raster.app/libraries' \
--data '{"email":"<your-email>","name":"Sample library"}'Response:
{
"data": {
"organizationId": "k3m9x7p2qar8zt",
"libraryId": "b7n2w4j8x1c5dq",
"apiKey": "pk_…",
"claimUrl": "https://raster.app/claim/…",
"expiresAt": "2026-07-03T00:00:00.000Z",
"emailSent": true
}
}Use the returned apiKey as the Authorization: Bearer token and the
organizationId and libraryId as the target for every later call: upload,
list, search, tag, and the other organization-scoped endpoints.
GET /organizations/:orgId/libraries
Lists the libraries in an organization that your credential can reach.
Parameters
Prop
Type
Returns
{ data: Library[] }. See Library.
Example
curl --request GET \
--header 'Authorization: Bearer <API_KEY>' \
--header 'Api-Version: 2026-07-08' \
--url 'https://api.raster.app/organizations/acme-co/libraries?page=1&pageSize=20'POST /organizations/:orgId/libraries
Creates a new library inside an organization. The caller must be an API key with
Write access or, over OAuth, an Owner of the organization. A read-only key
cannot create libraries. A library created with an API key is added to that
key's allowlist, so the same key can upload to it immediately. The new library
is shown to your whole team in the Raster app. To start a new organization
from scratch with no account, use the anonymous
POST /libraries.
The same flow is available over MCP as the
create_library tool.
Parameters
Prop
Type
Returns
201 Created with { data: Library }. See Library. A
new library has zeroed counters and no tags. On a free (hobby) plan at the
library cap, returns 400 LIBRARY_LIMIT_EXCEEDED. On an
organization whose Pro plan ended while it still has more than one member,
returns 403 PRO_PLAN_CANCELED. A read-only
key returns 403 API_KEY_READ_ONLY. An
OAuth access token whose account is not an Owner of the organization returns
403 FORBIDDEN.
Example
curl --request POST \
--header 'Authorization: Bearer <API_KEY>' \
--header 'Api-Version: 2026-07-08' \
--header 'Content-Type: application/json' \
--url 'https://api.raster.app/organizations/acme-co/libraries' \
--data '{"name":"Marketing","slug":"marketing"}'PATCH /organizations/:orgId/libraries/:libraryId
Renames a library your API key has access to. The new name appears everywhere the library is shown, including each team member's view. The URL slug stays the same.
The same flow is available over MCP as the
rename_library tool.
Parameters
Prop
Type
Returns
{ data: Library }, the updated Library. A key not
authorized for the library, or a missing library, returns 404.
Example
curl --request PATCH \
--header 'Authorization: Bearer <API_KEY>' \
--header 'Api-Version: 2026-07-08' \
--header 'Content-Type: application/json' \
--url 'https://api.raster.app/organizations/acme-co/libraries/marketing' \
--data '{"name":"Marketing 2026"}'GET /organizations/:orgId/libraries/:libraryId/assets/:assetId
Fetches a single asset by id, in the same Asset shape
that GET /organizations/:orgId/libraries/:libraryId/assets returns.
Use it to read one asset's url, tags, description, and dimensions without
paging the list. The :assetId is the id returned by list, search, or
upload.
Parameters
Prop
Type
Returns
{ data: Asset }. See Asset. Returns the same 404
whether the asset does not exist, belongs to a different library, or is trashed.
Example
curl --request GET \
--header 'Authorization: Bearer <API_KEY>' \
--header 'Api-Version: 2026-07-08' \
--url 'https://api.raster.app/organizations/acme-co/libraries/marketing/assets/k3m9x7p2qar8zt'GET /organizations/:orgId/libraries/:libraryId/assets
Lists assets in a library, paginated and optionally filtered by tag.
Parameters
Prop
Type
Returns
{ data: Asset[] }. See Asset. The unfiltered list
returns one entry per original asset, each carrying its live variants in the
variants field. A tag-filtered list returns every matching asset and
matching variant as its own entry (a variant carries its parentId), so
variants are not additionally nested there.
Example
curl --request GET \
--header 'Authorization: Bearer <API_KEY>' \
--header 'Api-Version: 2026-07-08' \
--url 'https://api.raster.app/organizations/acme-co/libraries/barcelona/assets?page=1&pageSize=20&tags=landscape,nature'GET /organizations/:orgId/search/assets
Searches assets across the libraries your credential can reach, returning
relevance-ranked hits with optional highlighted snippets. The endpoint is
organization-scoped: pass libraries to narrow the search to a subset of
authorized libraries, or omit it to search every library the credential can reach.
Parameters
Prop
Type
Returns
{ data: SearchResult }. See SearchResult and
SearchHit.
Example
curl --request GET \
--header 'Authorization: Bearer <API_KEY>' \
--header 'Api-Version: 2026-07-08' \
--url 'https://api.raster.app/organizations/acme-co/search/assets?q=sunset&libraries=barcelona,lisbon&page=1&pageSize=10'Asking for an unauthorized library fails the whole request with
404 API_KEY_NOT_AUTHORIZED_FOR_LIBRARY. The request is rejected
before any search runs, and no partial results are returned. To search
every library the credential can reach, omit libraries entirely.
GET /organizations/:orgId/libraries/:libraryId/tags
Lists the tags in a library, with their hit counts.
Parameters
Prop
Type
Returns
{ data: Tag[] }. See Tag.
Example
curl --request GET \
--header 'Authorization: Bearer <API_KEY>' \
--header 'Api-Version: 2026-07-08' \
--url 'https://api.raster.app/organizations/acme-co/libraries/barcelona/tags?limit=100'POST /organizations/:orgId/libraries/:libraryId/assets
Uploads one or more files to a library. The request body is multipart/form-data
with a repeatable files field.
Parameters
Prop
Type
Returns
{ data: { responseText: string, assets: Asset[] } }. Each entry has the
same Asset shape that GET /assets returns. The url
field is populated immediately with the asset's permanent CDN URL, which never
changes. The upload response also includes id, name, libraryId, tags,
contentType, and created. Fields computed asynchronously (width,
height, size, blurhash, sizes, variants, description,
updated, uploadedBy) are null in the upload response and are filled in
once processing finishes, typically within seconds. Re-read the asset with
GET /assets to get them.
When parentId is set, the returned asset is the new variant: its id is the
variant's id, parentId is the asset it belongs to, and appUrl opens the
parent asset with this variant selected. The variant is nested under the
parent's variants; a GET /assets list returns the parent carrying it in
that variants array.
Example
curl --request POST \
--header 'Authorization: Bearer <API_KEY>' \
--header 'Api-Version: 2026-07-08' \
--url 'https://api.raster.app/organizations/acme-co/libraries/barcelona/assets' \
--form 'files=@/path/to/photo-1.jpg' \
--form 'files=@/path/to/photo-2.png'To upload a file as a variant of an existing asset, pass parentId and a
single file:
curl --request POST \
--header 'Authorization: Bearer <API_KEY>' \
--header 'Api-Version: 2026-07-08' \
--url 'https://api.raster.app/organizations/acme-co/libraries/barcelona/assets' \
--form 'files=@/path/to/variant.jpg' \
--form 'parentId=<PARENT_ASSET_ID>'The multipart field name must be files. Anything else is rejected with
400 and BAD_USER_INPUT. Per-file size and count limits map to
413 PAYLOAD_TOO_LARGE and 400 BAD_USER_INPUT respectively. See the
Errors reference.
DELETE /organizations/:orgId/libraries/:libraryId/assets
Moves assets to trash by id, in batches. This is a soft delete, the same one the
Raster app performs. The assets leave GET /assets and
search immediately (both only return live assets), stay
recoverable from trash, and are permanently removed after 30 days.
Emptying the trash permanently is done in the Raster app.
Trashing an asset also trashes its variants. A variant cannot be trashed on its
own, only together with its original, so a variant id in ids fails the whole
call with 400 BAD_USER_INPUT
before anything moves. Pass the original's id instead.
Parameters
Prop
Type
Returns
{ data: { success: boolean, message: string, ids: string[] } }:
ids: the asset ids this call moved to trash. Ids already in trash (an idempotent retry) or not in the library are skipped, soids.lengthequals the count inmessage. When none of the ids names an asset in the library, the call fails with400 BAD_USER_INPUT.message:"Moved N assets to trash", whereNisids.length.success:truein the normal case, including a call that changes nothing because every requested id was already trashed ("Moved 0 assets to trash",ids: []). It isfalsewhen some of the ids failed to move: the ids that did move are inids, the rest are still live, and retrying the call moves them. The library'sassetsCountandtrashCountchange only by the ids that moved.
Example
curl --request DELETE \
--header 'Authorization: Bearer <API_KEY>' \
--header 'Api-Version: 2026-07-08' \
--header 'Content-Type: application/json' \
--url 'https://api.raster.app/organizations/acme-co/libraries/barcelona/assets' \
--data '{"ids":["abc123","def456"]}'POST /organizations/:orgId/libraries/:libraryId/assets/tag
Applies one or more tags to a batch of assets in a single library. The call is
idempotent on (asset, tag) pairs the asset already carries: re-running it
changes nothing for those pairs.
Parameters
Prop
Type
Returns
{ data: { taggedCount: number } }. taggedCount is the number of
(asset, tag) pairs the call added. Pairs the asset already carried are
skipped and not counted. To check whether every requested pair was
applied, compare taggedCount against assetIds.length × tags.length.
Example
curl --request POST \
--header 'Authorization: Bearer <API_KEY>' \
--header 'Api-Version: 2026-07-08' \
--header 'Content-Type: application/json' \
--url 'https://api.raster.app/organizations/acme-co/libraries/barcelona/assets/tag' \
--data '{"assetIds":["abc123","def456"],"tags":["sunset","landscape"]}'POST /organizations/:orgId/libraries/:libraryId/assets/untag
Removes one or more tags from a batch of assets in a single library. Like
/assets/tag, the call is idempotent: pairs the asset doesn't carry are
skipped.
Parameters
Prop
Type
Returns
{ data: { untaggedCount: number } }. untaggedCount is the number of
(asset, tag) pairs the call removed.
Example
curl --request POST \
--header 'Authorization: Bearer <API_KEY>' \
--header 'Api-Version: 2026-07-08' \
--header 'Content-Type: application/json' \
--url 'https://api.raster.app/organizations/acme-co/libraries/barcelona/assets/untag' \
--data '{"assetIds":["abc123"],"tags":["landscape"]}'PATCH /organizations/:orgId/libraries/:libraryId/assets/:assetId/description
Replaces the description on a single asset. The value is stored verbatim,
with no trimming and no AI rewrite. Pass an empty string to clear the field.
Parameters
Prop
Type
Returns
{ data: { assetId: string, description: string } }. The response echoes the
asset id and the stored description, so a client can confirm what was stored
without an extra read.
Example
curl --request PATCH \
--header 'Authorization: Bearer <API_KEY>' \
--header 'Api-Version: 2026-07-08' \
--header 'Content-Type: application/json' \
--url 'https://api.raster.app/organizations/acme-co/libraries/barcelona/assets/abc123/description' \
--data '{"description":"Golden hour over the Mediterranean."}'PATCH /organizations/:orgId/libraries/:libraryId/assets/:assetId/approval
Sets the approval of a single asset (an original or a
variant), with an optional note. Each call replaces the asset's previous
approval, including its note. The asset's uploader gets an email when it is set
to approved or needs_changes, unless they set it themselves.
Send an OAuth access token as the bearer token. An API key can read an
asset's approval but cannot set it: the request is rejected with
403 APPROVAL_REQUIRES_USER. The
token's account needs access to the library at any role, Viewer included, and the
organization needs a paid plan: without one the request is rejected with
403 PAID_PLAN_REQUIRED. When setting
approval is not available to the token's account, the request is rejected with
403 FORBIDDEN.
A state outside the organization's approval states, a note over 500
characters, or an asset that is not in the library is rejected with
400 BAD_USER_INPUT before anything is
written.
Parameters
Prop
Type
Returns
{ data: { assetId: string, approval: Approval } }. The response echoes the
asset id and the Approval that was written, so a
client can confirm what was stored without an extra read.
Example
curl --request PATCH \
--header 'Authorization: Bearer <ACCESS_TOKEN>' \
--header 'Api-Version: 2026-07-08' \
--header 'Content-Type: application/json' \
--url 'https://api.raster.app/organizations/acme-co/libraries/barcelona/assets/abc123/approval' \
--data '{"state":"needs_changes","note":"Crop tighter on the left."}'POST /organizations/:orgId/libraries/:libraryId/assets/:assetId/promote
Makes one of the asset's variants its default: the variant's file replaces what the asset serves, at the same URL. Anything already embedding that URL shows the new file without being updated, so you can correct or replace a published asset without changing any link to it. Works the same for a photo and for a video.
Promoting bumps the asset's contentVersion.
The previous default is preserved as a new variant, so it can be promoted back, and keeps the name it had. The asset keeps its id and URL and takes the promoted variant's name. Its description and the tags generated from its image follow the image: the promoted variant hands over any it carries, and the preserved variant keeps the ones that described it. Tags you added yourself stay on the asset.
The promoted variant's own entry is removed, because its image is now the asset's.
Rejected with 400 BAD_USER_INPUT when the
id names an original rather than a variant, when the variant is archived or still
processing, when the asset is not live, when the asset is itself still being
processed (a video whose poster is not ready yet), when the asset is neither an
image nor a video, or when the variant is a different kind of media than the
asset. A variant that is not in the library, or is in the trash, returns
404 NOT_FOUND. If another promote is already running on the same asset, the request is
rejected with 409 CONFLICT. Wait for it to finish, then retry.
Parameters
Prop
Type
Returns
{ data: { assetId: string, promotedVariantId: string, demotedVariantId: string } }.
All three are echoed so you can update local state without a follow-up read:
demotedVariantId is the new variant holding the previous default's image.
The response confirms the promote is accepted and echoes the three ids; the
image swap and the removal of the promoted variant's entry complete a moment
later. Re-fetch the asset after a short delay to see the change. Its
variants list is final once the swap finishes.
Because the URL doesn't change, a cached copy of the old image may still be
served for a short time. Append the asset's contentVersion
to the URL as ?v= to render the new image.
Example
curl --request POST \
--header 'Authorization: Bearer <API_KEY>' \
--header 'Api-Version: 2026-07-08' \
--header 'Content-Type: application/json' \
--url 'https://api.raster.app/organizations/acme-co/libraries/barcelona/assets/abc123/promote' \
--data '{"variantId":"var789"}'POST /organizations/:orgId/libraries/:libraryId/assets/transfer
Moves a batch of assets from one library to another within the same
organization. The URL's :libraryId is the source library; the body
names the target. A target library outside the organization fails with
404 API_KEY_NOT_AUTHORIZED_FOR_LIBRARY. When source and target are the same, the call
changes nothing (transferredCount: 0).
Parameters
Prop
Type
Returns
{ data: { transferredCount: number, sourceLibraryId: string, targetLibraryId: string } }.
Source and target are echoed so a client can confirm both ends without an
extra fetch.
Example
curl --request POST \
--header 'Authorization: Bearer <API_KEY>' \
--header 'Api-Version: 2026-07-08' \
--header 'Content-Type: application/json' \
--url 'https://api.raster.app/organizations/acme-co/libraries/barcelona/assets/transfer' \
--data '{"targetLibraryId":"lisbon","assetIds":["abc123","def456"]}'