Same account data
API uploads land in the same folders and assets you manage in the Stillsweb web UI.
Developer API
Create folders, upload assets with direct signed URLs, and receive metered Stillsweb embed links from https://client.stillsweb.com — without proxying file bytes through your integration or ours.
The Corporate DAM API lets server-side integrations create folders and upload assets into a Stillsweb corporate workspace using client credentials. File bytes go directly to cloud storage; Stillsweb never proxies your uploads.
API uploads land in the same folders and assets you manage in the Stillsweb web UI.
embedUrl is the stable formatai delivery URL on data.stillsweb.com for metered delivery — not a raw storage URL.
Init returns a signed URL. Your server PUTs bytes, then calls complete.
client-secret in browser or mobile app code. Use this API from trusted server environments only.Required headers
client-idYour Stillsweb client identifierclient-secretYour Stillsweb client secretidempotency-keyoptionalRecommended on init, complete, and abortPOST https://client.stillsweb.com/api/v1/dam/uploads/sessions/init with folderName or folderIdPUT file bytes to the returned uploadUrlPOST .../sessions/{sessionId}/completeGET .../sessions/{sessionId} until previewStatus is ready or failedhttps://client.stillsweb.comhttps://data.stillsweb.comSend client-id and client-secret to the services gateway on every control-plane call.
Use folderId when known, or folderName to create or reuse a corporate folder.
PUT file bytes directly to the signed storage URL — never through Stillsweb servers.
Call complete, then poll status until previews are ready. Use embedUrl for metered delivery.
Architecture
Client (client-id + client-secret) → services https://client.stillsweb.com/api/v1/dam/* → internal JWT (server-to-server only) → main backend /api/v1/internal/dam/* → signed PUT / multipart to S3 or GCP → complete → scan, derivatives, previews
Corporate DAM separates upload rows from logical assets and public delivery. Store embedUrl in your CMS; use assetId only for API polling and mappings.
| Field | Stable? | Use for |
|---|---|---|
| assetId | New each upload | API routes, statusUrl, external mappings |
| assetPublicId | Yes (same folder + filename) | Public identity; drives embed URL |
| embedUrl | Yes | https://data.stillsweb.com/e/{assetPublicId} — store in Shopify, WordPress, etc. |
Re-upload product-shot.jpg to the same folder → same embedUrl and assetPublicId, new assetId. Poll the new statusUrl until previewStatus is ready. Embed serves the latest version automatically.
Legacy /e/{assetPublicId}/v{n}/{filename} URLs still work on https://data.stillsweb.com but version and filename are ignored. New integrations should use the canonical /e/{assetPublicId} path only.
The size field at init must equal exact file bytes. Use one presigned uploadUrl per init — do not merge URLs or add extra x-amz headers.
Send contentType (e.g. image/jpeg). Deprecated alias: mimeType. Helps validation when storage returns application/octet-stream.
previewStatus is ready, the embed serves the real derivative (WebP @ 80% by default).https://client.stillsweb.comhttps://data.stillsweb.comAll paths below are relative to https://client.stillsweb.com. See Request & response for full HTTP examples.
/api/v1/dam/foldersCreate a folder in the corporate workspace.
Scope: dam:folders:create
/api/v1/dam/folders/{folderId}Read folder metadata, counts, and external reference.
Scope: dam:folders:read
/api/v1/dam/folders/{folderId}/assetsList assets in a folder with cursor pagination.
Scope: dam:assets:read
/api/v1/dam/uploads/sessions/initOpen a direct upload session and receive a signed storage URL.
Scope: dam:assets:upload
Send idempotency-key header on init.
/api/v1/dam/uploads/sessions/{sessionId}/partsSign multipart part URLs lazily for large files.
Scope: dam:assets:upload
/api/v1/dam/uploads/sessions/{sessionId}/completeFinalize bytes, queue processing, and reserve storage once.
Scope: dam:assets:upload
/api/v1/dam/uploads/sessions/{sessionId}Poll upload and processing status for a session.
Scope: dam:assets:read or dam:assets:upload
/api/v1/dam/uploads/sessions/{sessionId}/abortAbort a pending session and release quota reservation.
Scope: dam:assets:upload
/api/v1/dam/assets/searchSearch assets in the corporate workspace (keyword, folder, type filters).
Scope: dam:assets:read
Declared before /assets/{assetId} so search is not treated as an id.
/api/v1/dam/assets/{assetId}Read a single asset summary with embedUrl and preview fields.
Scope: dam:assets:read
/api/v1/dam/assets/{assetId}/statusPoll upload/processing status for an asset.
Scope: dam:assets:read or dam:assets:upload
/api/v1/dam/assets/{assetId}/aiLatest run state and results of the AI tools (describe, background removal, text extraction) for an asset.
Scope: dam:assets:read
/api/v1/dam/assets/{assetId}/ai/describeRun the describe tool: a one-sentence description plus tag suggestions. Asynchronous; spends one run of the owner's monthly AI allowance.
Scope: dam:assets:manage
Poll GET …/ai until describe.run.status is done or failed.
/api/v1/dam/assets/{assetId}/ai/tagsApply tags the describe tool suggested for this asset (recorded with source "ai").
Scope: dam:assets:manage
/api/v1/dam/audit/eventsList server-derived Corporate DAM audit events.
Scope: dam:audit:read
Upload lifecycle + external mapping events with channel=external_dam metadata.
/api/v1/dam/usageRead organization storage and egress consumption.
Scope: dam:usage:read
/api/v1/dam/usage/rollupsRead daily or dimensional usage rollups (API requests, egress bytes).
Scope: dam:usage:read
metric=api_requests|egress_bytes; groupBy=day|apiKey|asset; from/to as YYYY-MM-DD.
/api/v1/dam/webhooksCreate an HTTPS webhook subscription.
Scope: dam:webhooks:manage
signingSecret is returned once on create. Verify X-Stillsweb-Signature: t=...,v1=... over {timestamp}.{rawBody} (HMAC-SHA256, 300s tolerance).
/api/v1/dam/webhooksList webhook subscriptions.
Scope: dam:webhooks:manage
/api/v1/dam/webhooks/{webhookId}Update url, events, label, or enabled flag.
Scope: dam:webhooks:manage
/api/v1/dam/webhooks/{webhookId}Delete a webhook subscription.
Scope: dam:webhooks:manage
/api/v1/dam/assets/{assetId}/external-mappingsLink an asset to an external entity (Shopify, WordPress, etc.).
Scope: dam:integrations:write
/api/v1/dam/assets/{assetId}/external-mappingsList external entity links for an asset.
Scope: dam:integrations:read or dam:assets:read
/api/v1/dam/assets/{assetId}/external-mappings/{mappingId}Remove an external entity link.
Scope: dam:integrations:write
Each example shows the full URL, required headers, JSON request body when applicable, and a representative success response. Successful responses use the envelope { success, data, error }. Failures return the same envelope with success: false and an error.code / error.message.
Example failure response returned by the public gateway.
POST https://client.stillsweb.com/api/v1/dam/...Request
No request body.
{}Response
HTTP/1.1 403 Forbidden
content-type: application/json
{
"success": false,
"data": null,
"error": {
"code": "CORPORATE_WORKSPACE_DISABLED",
"message": "Corporate workspace is not enabled for this account. Enable it in Settings to use the Corporate DAM API."
}
}Create a folder under the corporate workspace root, or return an existing folder when the same case-insensitive name already exists at the same parent.
POST https://client.stillsweb.com/api/v1/dam/foldersRequest
client-id: <your-client-id>
client-secret: <your-client-secret>
content-type: application/json{
"name": "Campaign 2026",
"parentFolderId": null,
"description": "Launch assets",
"metadata": {
"externalReferenceId": "client-folder-123"
}
}Response
HTTP/1.1 200 OK
content-type: application/json
{
"folderId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"name": "Campaign 2026",
"parentFolderId": "3",
"description": "Launch assets",
"metadata": {
"externalReferenceId": "client-folder-123"
},
"assetCount": 0,
"childFolderCount": 0,
"createdAt": "2026-05-29T10:00:00.000Z",
"updatedAt": "2026-05-29T10:00:00.000Z"
}Reserve quota, create the asset row, and return a signed storage URL. Send either folderId or folderName.
POST https://client.stillsweb.com/api/v1/dam/uploads/sessions/initRequest
client-id: <your-client-id>
client-secret: <your-client-secret>
content-type: application/json
idempotency-key: campaign-2026:product-shot.heic{
"folderName": "Campaign 2026",
"filename": "product-shot.heic",
"mimeType": "image/heic",
"sizeBytes": 293608,
"metadata": {
"externalReferenceId": "asset-789"
}
}Response
HTTP/1.1 200 OK
content-type: application/json
{
"sessionId": "8f1c2b4a-6d7e-4f90-b123-456789abcdef",
"assetId": "171234",
"folderId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"status": "initiated",
"uploadMode": "single_put",
"uploadUrl": "https://storage.example.com/signed-put-url",
"expiresAt": "2026-05-29T00:30:00.000Z",
"assetPublicId": "ast_abc123",
"embedUrl": "https://data.stillsweb.com/e/ast_abc123"
}Large files may return uploadMode multipart instead of a single PUT URL.
POST https://client.stillsweb.com/api/v1/dam/uploads/sessions/initRequest
client-id: <your-client-id>
client-secret: <your-client-secret>
content-type: application/json
idempotency-key: campaign-2026:large-video.mp4{
"folderId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"filename": "launch-video.mp4",
"mimeType": "video/mp4",
"sizeBytes": 524288000
}Response
HTTP/1.1 200 OK
content-type: application/json
{
"sessionId": "8f1c2b4a-6d7e-4f90-b123-456789abcdef",
"assetId": "171235",
"folderId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"status": "initiated",
"uploadMode": "multipart",
"uploadId": "provider-multipart-id",
"partSize": 8388608,
"totalParts": 63,
"partsUrl": "/api/v1/dam/uploads/sessions/8f1c2b4a-6d7e-4f90-b123-456789abcdef/parts",
"expiresAt": "2026-05-29T00:30:00.000Z",
"assetPublicId": "ast_def456",
"embedUrl": "https://data.stillsweb.com/e/ast_def456"
}Request signed URLs for one or more part numbers. Call this as needed instead of receiving every part URL during init.
POST https://client.stillsweb.com/api/v1/dam/uploads/sessions/{sessionId}/partsRequest
client-id: <your-client-id>
client-secret: <your-client-secret>
content-type: application/json{
"partNumbers": [1, 2, 3, 4]
}Response
HTTP/1.1 200 OK
content-type: application/json
{
"sessionId": "8f1c2b4a-6d7e-4f90-b123-456789abcdef",
"parts": [
{
"partNumber": 1,
"uploadUrl": "https://storage.example.com/part-1",
"expiresAt": "2026-05-29T00:30:00.000Z"
},
{
"partNumber": 2,
"uploadUrl": "https://storage.example.com/part-2",
"expiresAt": "2026-05-29T00:30:00.000Z"
}
]
}Verify the object in storage, finalize multipart uploads when needed, deduct storage once, and queue processing.
POST https://client.stillsweb.com/api/v1/dam/uploads/sessions/{sessionId}/completeRequest
client-id: <your-client-id>
client-secret: <your-client-secret>
content-type: application/json{}Response
HTTP/1.1 200 OK
content-type: application/json
{
"sessionId": "8f1c2b4a-6d7e-4f90-b123-456789abcdef",
"assetId": "171234",
"folderId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"status": "processing",
"previewStatus": "processing",
"assetPublicId": "ast_abc123",
"embedUrl": "https://data.stillsweb.com/e/ast_abc123",
"asset": {
"assetId": "171234",
"assetPublicId": "ast_abc123",
"embedUrl": "https://data.stillsweb.com/e/ast_abc123",
"filename": "product-shot.heic",
"assetType": "image",
"status": "uploaded",
"previewStatus": "processing",
"previewUrl": null,
"thumbnailUrl": null,
"urlVariants": {}
}
}Multipart completion requires the provider ETag for each uploaded part.
POST https://client.stillsweb.com/api/v1/dam/uploads/sessions/{sessionId}/completeRequest
client-id: <your-client-id>
client-secret: <your-client-secret>
content-type: application/json{
"parts": [
{ "partNumber": 1, "etag": "\"abc123\"" },
{ "partNumber": 2, "etag": "\"def456\"" }
]
}Response
HTTP/1.1 200 OK
content-type: application/json
{
"sessionId": "8f1c2b4a-6d7e-4f90-b123-456789abcdef",
"assetId": "171235",
"folderId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"status": "processing",
"previewStatus": "processing",
"assetPublicId": "ast_def456",
"embedUrl": "https://data.stillsweb.com/e/ast_def456"
}Poll processing progress after complete. Use this until previewStatus is ready or failed.
GET https://client.stillsweb.com/api/v1/dam/uploads/sessions/{sessionId}Request
client-id: <your-client-id>
client-secret: <your-client-secret>Response
HTTP/1.1 200 OK
content-type: application/json
{
"sessionId": "8f1c2b4a-6d7e-4f90-b123-456789abcdef",
"assetId": "171234",
"folderId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"status": "processing",
"previewStatus": "ready",
"assetPublicId": "ast_abc123",
"embedUrl": "https://data.stillsweb.com/e/ast_abc123",
"asset": {
"assetId": "171234",
"assetPublicId": "ast_abc123",
"embedUrl": "https://data.stillsweb.com/e/ast_abc123",
"filename": "product-shot.heic",
"assetType": "image",
"status": "uploaded",
"previewStatus": "ready",
"previewUrl": "https://signed.example.com/500.webp",
"thumbnailUrl": "https://signed.example.com/240.webp",
"urlVariants": {
"240": "https://signed.example.com/240.webp",
"500": "https://signed.example.com/500.webp"
}
}
}Poll processing after complete. Path is /api/v1/dam/assets/{assetId}/status — not /api/v1/assets/....
GET https://client.stillsweb.com/api/v1/dam/assets/{assetId}/statusRequest
client-id: <your-client-id>
client-secret: <your-client-secret>Response
HTTP/1.1 200 OK
content-type: application/json
{
"success": true,
"data": {
"assetId": "171234",
"assetPublicId": "ast_abc123",
"status": "ready",
"previewStatus": "ready",
"failureReason": null,
"embedUrl": "https://data.stillsweb.com/e/ast_abc123",
"assetUrl": "https://data.stillsweb.com/e/ast_abc123"
},
"error": null
}Read folder metadata and live asset/folder counts for a folder owned by the credential account.
GET https://client.stillsweb.com/api/v1/dam/folders/{folderId}Request
client-id: <your-client-id>
client-secret: <your-client-secret>Response
HTTP/1.1 200 OK
content-type: application/json
{
"folderId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"name": "Campaign 2026",
"parentFolderId": "3",
"description": "Launch assets",
"metadata": {
"externalReferenceId": "client-folder-123"
},
"assetCount": 12,
"childFolderCount": 2,
"createdAt": "2026-05-29T10:00:00.000Z",
"updatedAt": "2026-05-29T11:15:00.000Z"
}List assets in a folder with cursor pagination.
GET https://client.stillsweb.com/api/v1/dam/folders/{folderId}/assets?limit=50&cursor=Request
client-id: <your-client-id>
client-secret: <your-client-secret>limit — max 100, default 50
cursor — assetId from previous page nextCursorResponse
HTTP/1.1 200 OK
content-type: application/json
{
"folderId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"items": [
{
"assetId": "171234",
"assetPublicId": "ast_abc123",
"embedUrl": "https://data.stillsweb.com/e/ast_abc123",
"filename": "product-shot.heic",
"assetType": "image",
"status": "uploaded",
"previewStatus": "ready",
"previewUrl": "https://signed.example.com/500.webp",
"thumbnailUrl": "https://signed.example.com/240.webp",
"createdAt": "2026-05-29T10:05:00.000Z"
}
],
"nextCursor": null
}Search assets in the credential owner's Corporate Workspace. Matches filename, folder name, legacy image id, and assetPublicId.
GET https://client.stillsweb.com/api/v1/dam/assets/search?q=product&folderId=&assetType=image&limit=50&cursor=Request
client-id: <your-client-id>
client-secret: <your-client-secret>q — optional keyword
folderId — optional corporate folder
assetType — optional (image, video, pdf, …)
limit — max 100, default 50
cursor — assetId from previous nextCursorResponse
HTTP/1.1 200 OK
content-type: application/json
{
"folderId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"items": [
{
"assetId": "171234",
"assetPublicId": "ast_abc123",
"embedUrl": "https://data.stillsweb.com/e/ast_abc123",
"filename": "product-shot.heic",
"assetType": "image",
"status": "uploaded",
"previewStatus": "ready",
"previewUrl": "https://signed.example.com/500.webp",
"thumbnailUrl": "https://signed.example.com/240.webp",
"createdAt": "2026-05-29T10:05:00.000Z"
}
],
"nextCursor": null
}Latest run state and result of each AI tool for an asset the key's account owns. Poll this after starting a run; results stay available for later reads.
GET https://client.stillsweb.com/api/v1/dam/assets/{assetId}/aiRequest
client-id: <your-client-id>
client-secret: <your-client-secret>
content-type: application/jsonResponse
HTTP/1.1 200 OK
content-type: application/json
{
"success": true,
"data": {
"describe": {
"run": { "status": "done", "updatedAt": "2026-10-21T09:12:40.000Z" },
"result": {
"description": "A tabby cat sitting on a sunny window sill.",
"tags": [
{ "tag": "tabby", "score": 0.41, "status": "applied" },
{ "tag": "window-sill", "score": 0.2, "status": "suggested" },
{ "tag": "cat", "score": 0.1, "status": "dismissed" }
],
"models": { "labels": "beit", "caption": "blip" },
"createdAt": "2026-10-21T09:12:40.000Z"
}
},
"backgroundRemoval": { "run": null, "result": null },
"textExtraction": { "run": null, "result": null }
},
"error": null
}Queues a describe run (one-sentence description plus tag suggestions) on an owned image. Spends one run of the owner's monthly AI image tools allowance; a failed run is released, not counted.
POST https://client.stillsweb.com/api/v1/dam/assets/{assetId}/ai/describeRequest
client-id: <your-client-id>
client-secret: <your-client-secret>
content-type: application/json{}Response
HTTP/1.1 200 OK
content-type: application/json
{
"success": true,
"data": { "tool": "describe", "status": "queued", "jobId": 48213 },
"error": null
}Turns describe suggestions into tags on the asset. Only tags the tool suggested for this asset are accepted; applied tags carry provenance source "ai".
POST https://client.stillsweb.com/api/v1/dam/assets/{assetId}/ai/tagsRequest
client-id: <your-client-id>
client-secret: <your-client-secret>
content-type: application/json{
"tags": ["tabby", "window-sill"]
}Response
HTTP/1.1 200 OK
content-type: application/json
{
"success": true,
"data": { "applied": ["tabby", "window-sill"] },
"error": null
}Link an uploaded asset to an external entity (Shopify product image, WordPress media slot, etc.). Re-posting the same provider + entity identity upserts externalUrl and metadata.
POST https://client.stillsweb.com/api/v1/dam/assets/{assetId}/external-mappingsRequest
client-id: <your-client-id>
client-secret: <your-client-secret>
content-type: application/json{
"provider": "shopify",
"externalEntityType": "product_image",
"externalEntityId": "gid://shopify/ProductImage/123456789",
"externalUrl": "https://cdn.shopify.com/s/files/1/example.jpg",
"metadata": { "slot": "hero" }
}Response
HTTP/1.1 200 OK
content-type: application/json
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"organizationId": 6899,
"assetId": "171234",
"provider": "shopify",
"externalEntityType": "product_image",
"externalEntityId": "gid://shopify/ProductImage/123456789",
"externalUrl": "https://cdn.shopify.com/s/files/1/example.jpg",
"metadata": { "slot": "hero" },
"createdAt": "2026-06-01T10:00:00.000Z",
"updatedAt": "2026-06-01T10:00:00.000Z"
}List all external entity links recorded for an asset.
GET https://client.stillsweb.com/api/v1/dam/assets/{assetId}/external-mappingsRequest
client-id: <your-client-id>
client-secret: <your-client-secret>Response
HTTP/1.1 200 OK
content-type: application/json
{
"assetId": "171234",
"items": [
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"organizationId": 6899,
"assetId": "171234",
"provider": "shopify",
"externalEntityType": "product_image",
"externalEntityId": "gid://shopify/ProductImage/123456789",
"externalUrl": "https://cdn.shopify.com/s/files/1/example.jpg",
"metadata": { "slot": "hero" },
"createdAt": "2026-06-01T10:00:00.000Z",
"updatedAt": "2026-06-01T10:00:00.000Z"
}
]
}Remove an external entity link from an asset.
DELETE https://client.stillsweb.com/api/v1/dam/assets/{assetId}/external-mappings/{mappingId}Request
client-id: <your-client-id>
client-secret: <your-client-secret>{}Response
HTTP/1.1 200 OK
content-type: application/json
{
"deleted": true,
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
}Cancel a pending session, abort multipart uploads when applicable, and release quota reservation.
POST https://client.stillsweb.com/api/v1/dam/uploads/sessions/{sessionId}/abortRequest
client-id: <your-client-id>
client-secret: <your-client-secret>
content-type: application/json{
"reason": "client_aborted"
}Response
HTTP/1.1 200 OK
content-type: application/json
{
"sessionId": "8f1c2b4a-6d7e-4f90-b123-456789abcdef",
"status": "aborted"
}/assets/{assetId}/status.embedUrl; unchanged when you replace the file in place./api/v1/dam/assets/171234/status. Prepend https://client.stillsweb.com.processing → ready or failed. When failed, failureReason describes the pipeline step.https://data.stillsweb.com/e/{assetPublicId}. Optional query params: w, h, fmt (jpeg, png, webp, avif), meta, r, f (flip h/v), dpi. Omit fmt for WebP @ 80% with metadata stripped. f is flip, not format.embedUrl (same value when present). Do not use in new integrations./parts.Embed delivery examples
Append transform query params to embedUrl when needed. Use fmt for output format — not fm. f means flip.
When fmt is omitted, formatai returns WebP @ 80% quality with metadata stripped.
When you already know the folder UUID from a prior upload or folder create response, send folderId. It always wins over folderName.
If folderId is omitted, send folderName. Stillsweb looks up an existing corporate root folder case-insensitively ("Campaign 2026" matches "campaign 2026") or creates one.
corporate-workspace only. Uploads and folders are owned by the credential's Stillsweb account — never by values in the request body.const API = "https://client.stillsweb.com";
const headers = {
"client-id": process.env.STILLSWEB_CLIENT_ID,
"client-secret": process.env.STILLSWEB_CLIENT_SECRET,
"idempotency-key": "campaign-2026:product-shot.heic",
};
const init = await fetch(`${API}/api/v1/dam/uploads/sessions/init`, {
method: "POST",
headers: { ...headers, "content-type": "application/json" },
body: JSON.stringify({
folderName: "Campaign 2026",
filename: "product-shot.heic",
mimeType: "image/heic",
sizeBytes: 293608,
}),
}).then((r) => r.json());
await fetch(init.uploadUrl, {
method: "PUT",
headers: { "content-type": "image/heic" },
body: fileStream,
});
const asset = await fetch(
`${API}/api/v1/dam/uploads/sessions/${init.sessionId}/complete`,
{ method: "POST", headers: { ...headers, "content-type": "application/json" }, body: "{}" },
).then((r) => r.json());Failed responses include a stable code field so integrations can branch on retryable vs permanent errors.
| Code | Meaning |
|---|---|
| INVALID_REQUEST | Invalid body, folder target, MIME type, or file metadata. |
| UNAUTHORIZED | Missing or invalid client-id / client-secret headers. |
| FORBIDDEN | Plan, scope, workspace, or account status blocks the action. |
| NOT_FOUND | Folder, asset, upload session, or external mapping not found for this owner. |
| CONFLICT | Idempotency or resource state conflict. |
| FILE_TOO_LARGE | File exceeds plan or upload limit. |
| RATE_LIMITED | Per-client or per-IP limit exceeded. Retry after the window. |
| DAM_UPSTREAM_ERROR | Public request accepted but internal DAM service failed. |
| AI_TOOLS_NOT_INCLUDED | 403 — the owner's plan has no AI image tools. Do not retry; the account needs an upgrade. |
| AI_MONTHLY_LIMIT_REACHED | 429 — the monthly AI allowance is used up (message carries the reset date). Not RATE_LIMITED: stop until the next period. |
| AI_TOO_MANY_PENDING | 429 — too many AI runs in flight for this owner. Retry after some finish. |
| AI_TOOL_ALREADY_RUNNING | 409 — the same tool is already running on this asset. Poll GET …/ai. |
| AI_TOOLS_UNAVAILABLE | 503 — AI service not configured or unreachable. Retry later. |
| AI_TOOL_UNSUPPORTED_FILE | 400 — the asset is not an image. |
| AI_TAGS_NOT_SUGGESTED | 400 — none of the given tags were suggested for this asset. |
| AI_DESCRIPTION_NOT_FOUND | 404 — describe has not completed for this asset. |
Organization owners and team members with Manage API keys can create credentials in account settings.