StillswebDevelopersCorporate DAM API

Developer API

Corporate DAM 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.

Overview

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.

Same account data

API uploads land in the same folders and assets you manage in the Stillsweb web UI.

Metered delivery

embedUrl is the stable formatai delivery URL on data.stillsweb.com for metered delivery — not a raw storage URL.

Direct upload

Init returns a signed URL. Your server PUTs bytes, then calls complete.

Authentication

Never expose client-secret in browser or mobile app code. Use this API from trusted server environments only.

Required headers

client-idYour Stillsweb client identifier
client-secretYour Stillsweb client secret
idempotency-keyoptionalRecommended on init, complete, and abort

Public gateway

  • All API requests go to https://client.stillsweb.com.
  • Send client-id and client-secret on every control-plane call.
  • File bytes upload directly to signed storage URLs — not to client.stillsweb.com.

Required scopes

  • workspace:corporate (required on every key)
  • dam:folders:create / dam:folders:read
  • dam:assets:upload / dam:assets:read
  • dam:assets:manage (AI tools: describe, apply tags — spends the owner's allowance)

Quick start

  1. 1POST https://client.stillsweb.com/api/v1/dam/uploads/sessions/init with folderName or folderId
  2. 2PUT file bytes to the returned uploadUrl
  3. 3POST .../sessions/{sessionId}/complete
  4. 4GET .../sessions/{sessionId} until previewStatus is ready or failed
API base URLhttps://client.stillsweb.com
Asset delivery URLhttps://data.stillsweb.com

Upload flow

Authenticate

Send client-id and client-secret to the services gateway on every control-plane call.

Target folder

Use folderId when known, or folderName to create or reuse a corporate folder.

Upload bytes

PUT file bytes directly to the signed storage URL — never through Stillsweb servers.

Complete & poll

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

Asset versioning

Corporate DAM separates upload rows from logical assets and public delivery. Store embedUrl in your CMS; use assetId only for API polling and mappings.

FieldStable?Use for
assetIdNew each uploadAPI routes, statusUrl, external mappings
assetPublicIdYes (same folder + filename)Public identity; drives embed URL
embedUrlYeshttps://data.stillsweb.com/e/{assetPublicId} — store in Shopify, WordPress, etc.

Replace file in place

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.

Deprecated versioned URL

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.

PUT size must match init

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.

contentType at init

Send contentType (e.g. image/jpeg). Deprecated alias: mimeType. Helps validation when storage returns application/octet-stream.

Placeholder embed images use HTTP 200: code 102 while processing, 500 when failed. Once previewStatus is ready, the embed serves the real derivative (WebP @ 80% by default).

Endpoints

API base URLhttps://client.stillsweb.com
Asset delivery URLhttps://data.stillsweb.com

All paths below are relative to https://client.stillsweb.com. See Request & response for full HTTP examples.

Folders

POST
/api/v1/dam/folders

Create a folder in the corporate workspace.

Scope: dam:folders:create

GET
/api/v1/dam/folders/{folderId}

Read folder metadata, counts, and external reference.

Scope: dam:folders:read

GET
/api/v1/dam/folders/{folderId}/assets

List assets in a folder with cursor pagination.

Scope: dam:assets:read

Upload sessions

POST
/api/v1/dam/uploads/sessions/init

Open a direct upload session and receive a signed storage URL.

Scope: dam:assets:upload

Send idempotency-key header on init.

POST
/api/v1/dam/uploads/sessions/{sessionId}/parts

Sign multipart part URLs lazily for large files.

Scope: dam:assets:upload

POST
/api/v1/dam/uploads/sessions/{sessionId}/complete

Finalize bytes, queue processing, and reserve storage once.

Scope: dam:assets:upload

GET
/api/v1/dam/uploads/sessions/{sessionId}

Poll upload and processing status for a session.

Scope: dam:assets:read or dam:assets:upload

POST
/api/v1/dam/uploads/sessions/{sessionId}/abort

Abort a pending session and release quota reservation.

Scope: dam:assets:upload

Assets

GET
/api/v1/dam/assets/search

Search 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.

GET
/api/v1/dam/assets/{assetId}

Read a single asset summary with embedUrl and preview fields.

Scope: dam:assets:read

GET
/api/v1/dam/assets/{assetId}/status

Poll upload/processing status for an asset.

Scope: dam:assets:read or dam:assets:upload

AI tools

GET
/api/v1/dam/assets/{assetId}/ai

Latest run state and results of the AI tools (describe, background removal, text extraction) for an asset.

Scope: dam:assets:read

POST
/api/v1/dam/assets/{assetId}/ai/describe

Run 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.

POST
/api/v1/dam/assets/{assetId}/ai/tags

Apply tags the describe tool suggested for this asset (recorded with source "ai").

Scope: dam:assets:manage

Audit

GET
/api/v1/dam/audit/events

List server-derived Corporate DAM audit events.

Scope: dam:audit:read

Upload lifecycle + external mapping events with channel=external_dam metadata.

Usage

GET
/api/v1/dam/usage

Read organization storage and egress consumption.

Scope: dam:usage:read

GET
/api/v1/dam/usage/rollups

Read 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.

Webhooks

POST
/api/v1/dam/webhooks

Create 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).

GET
/api/v1/dam/webhooks

List webhook subscriptions.

Scope: dam:webhooks:manage

PATCH
/api/v1/dam/webhooks/{webhookId}

Update url, events, label, or enabled flag.

Scope: dam:webhooks:manage

DELETE
/api/v1/dam/webhooks/{webhookId}

Delete a webhook subscription.

Scope: dam:webhooks:manage

Integrations

POST
/api/v1/dam/assets/{assetId}/external-mappings

Link an asset to an external entity (Shopify, WordPress, etc.).

Scope: dam:integrations:write

GET
/api/v1/dam/assets/{assetId}/external-mappings

List external entity links for an asset.

Scope: dam:integrations:read or dam:assets:read

DELETE
/api/v1/dam/assets/{assetId}/external-mappings/{mappingId}

Remove an external entity link.

Scope: dam:integrations:write

Request & response

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.

POST

Error response shape

Example failure response returned by the public gateway.

POST https://client.stillsweb.com/api/v1/dam/...

Request

No request body.

Body · json
{}

Response

Success · http
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."
  }
}
POST

Create folder

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/folders

Request

Headers
client-id: <your-client-id>
client-secret: <your-client-secret>
content-type: application/json
Body · json
{
  "name": "Campaign 2026",
  "parentFolderId": null,
  "description": "Launch assets",
  "metadata": {
    "externalReferenceId": "client-folder-123"
  }
}

Response

Success · http
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"
}
  • Scope required: dam:folders:create and workspace:corporate
  • parentFolderId null defaults to the corporate workspace root (internal id 3)
POST

Init upload session

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/init

Request

Headers
client-id: <your-client-id>
client-secret: <your-client-secret>
content-type: application/json
idempotency-key: campaign-2026:product-shot.heic
Body · json
{
  "folderName": "Campaign 2026",
  "filename": "product-shot.heic",
  "mimeType": "image/heic",
  "sizeBytes": 293608,
  "metadata": {
    "externalReferenceId": "asset-789"
  }
}

Response

Success · http
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"
}
  • Scope required: dam:assets:upload and workspace:corporate
  • folderId takes priority over folderName when both are sent
  • embedUrl is returned immediately; use it for metered viewing after processing completes
  • assetUrl may still appear as a deprecated alias equal to embedUrl
POST

Init upload session (multipart)

Large files may return uploadMode multipart instead of a single PUT URL.

POST https://client.stillsweb.com/api/v1/dam/uploads/sessions/init

Request

Headers
client-id: <your-client-id>
client-secret: <your-client-secret>
content-type: application/json
idempotency-key: campaign-2026:large-video.mp4
Body · json
{
  "folderId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "filename": "launch-video.mp4",
  "mimeType": "video/mp4",
  "sizeBytes": 524288000
}

Response

Success · http
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"
}
  • partsUrl is relative — call https://client.stillsweb.com plus partsUrl
  • Do not upload file bytes to client.stillsweb.com; only use the signed part URLs returned by /parts
POST

Sign multipart parts

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}/parts

Request

Headers
client-id: <your-client-id>
client-secret: <your-client-secret>
content-type: application/json
Body · json
{
  "partNumbers": [1, 2, 3, 4]
}

Response

Success · http
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"
    }
  ]
}
  • PUT each part to its uploadUrl and keep the ETag response header for complete
  • Request fresh part URLs if a signed URL expires before upload finishes
POST

Complete upload session

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}/complete

Request

Headers
client-id: <your-client-id>
client-secret: <your-client-secret>
content-type: application/json
Body · json
{}

Response

Success · http
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": {}
  }
}
  • For multipart, send part ETags in the request body instead of {}
  • status and previewStatus stay processing until derivatives are ready
POST

Complete multipart upload

Multipart completion requires the provider ETag for each uploaded part.

POST https://client.stillsweb.com/api/v1/dam/uploads/sessions/{sessionId}/complete

Request

Headers
client-id: <your-client-id>
client-secret: <your-client-secret>
content-type: application/json
Body · json
{
  "parts": [
    { "partNumber": 1, "etag": "\"abc123\"" },
    { "partNumber": 2, "etag": "\"def456\"" }
  ]
}

Response

Success · http
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"
}
GET

Get upload status

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

Headers
client-id: <your-client-id>
client-secret: <your-client-secret>

Response

Success · http
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"
    }
  }
}
  • previewUrl and urlVariants appear only after processing finishes
  • embedUrl remains the canonical metered delivery URL
GET

Get asset status

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}/status

Request

Headers
client-id: <your-client-id>
client-secret: <your-client-secret>

Response

Success · http
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
}
  • Use statusUrl from init/complete responses (relative — prepend gateway base URL)
  • previewStatus failed includes failureReason (validation, scan, or derivative step)
  • Re-uploading the same filename returns the same assetPublicId/embedUrl but a new assetId — poll the new id
GET

Get folder

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

Headers
client-id: <your-client-id>
client-secret: <your-client-secret>

Response

Success · http
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"
}
GET

List folder assets

List assets in a folder with cursor pagination.

GET https://client.stillsweb.com/api/v1/dam/folders/{folderId}/assets?limit=50&cursor=

Request

Headers
client-id: <your-client-id>
client-secret: <your-client-secret>
Query params
limit — max 100, default 50
cursor — assetId from previous page nextCursor

Response

Success · http
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
}
GET

Search corporate assets

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

Headers
client-id: <your-client-id>
client-secret: <your-client-secret>
Query params
q — optional keyword
folderId — optional corporate folder
assetType — optional (image, video, pdf, …)
limit — max 100, default 50
cursor — assetId from previous nextCursor

Response

Success · http
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
}
  • Scope required: dam:assets:read and workspace:corporate
  • Only searches the Corporate Workspace — never personal or proofing folders
GET

Get AI tool results

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}/ai

Request

Headers
client-id: <your-client-id>
client-secret: <your-client-secret>
content-type: application/json

Response

Success · http
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
}
  • Scope required: dam:assets:read and workspace:corporate
  • run.status is running, done or failed; a run older than 30 minutes with no result is reported as failed
  • tags[].status is suggested, applied or dismissed — only suggested tags can be applied
POST

Run the describe tool

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/describe

Request

Headers
client-id: <your-client-id>
client-secret: <your-client-secret>
content-type: application/json
Body · json
{}

Response

Success · http
HTTP/1.1 200 OK
content-type: application/json

{
  "success": true,
  "data": { "tool": "describe", "status": "queued", "jobId": 48213 },
  "error": null
}
  • Scope required: dam:assets:manage and workspace:corporate
  • Asynchronous — poll GET /api/v1/dam/assets/{assetId}/ai until describe.run.status is done or failed
  • Errors: AI_TOOLS_NOT_INCLUDED (403), AI_MONTHLY_LIMIT_REACHED (429, do not retry this period), AI_TOO_MANY_PENDING (429), AI_TOOL_ALREADY_RUNNING (409), AI_TOOL_UNSUPPORTED_FILE (400), AI_TOOLS_UNAVAILABLE (503)
  • Writes an audit event (ai_describe_started, actorType api_key)
POST

Apply suggested tags

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/tags

Request

Headers
client-id: <your-client-id>
client-secret: <your-client-secret>
content-type: application/json
Body · json
{
  "tags": ["tabby", "window-sill"]
}

Response

Success · http
HTTP/1.1 200 OK
content-type: application/json

{
  "success": true,
  "data": { "applied": ["tabby", "window-sill"] },
  "error": null
}
  • Scope required: dam:assets:manage and workspace:corporate
  • 1–50 tags, each up to 128 characters; matched case-insensitively against the suggestions
  • AI_TAGS_NOT_SUGGESTED (400) when none of the tags were suggested; AI_DESCRIPTION_NOT_FOUND (404) before describe has completed
  • Writes an audit event (ai_tags_applied, actorType api_key)
POST

Create external asset mapping

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-mappings

Request

Headers
client-id: <your-client-id>
client-secret: <your-client-secret>
content-type: application/json
Body · 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

Success · http
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"
}
  • Scope required: dam:integrations:write and workspace:corporate
  • organizationId is the credential owner user id (1:1 corporate workspace account)
GET

List external asset mappings

List all external entity links recorded for an asset.

GET https://client.stillsweb.com/api/v1/dam/assets/{assetId}/external-mappings

Request

Headers
client-id: <your-client-id>
client-secret: <your-client-secret>

Response

Success · http
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"
    }
  ]
}
  • Scope required: dam:integrations:read or dam:assets:read
DELETE

Delete external asset mapping

Remove an external entity link from an asset.

DELETE https://client.stillsweb.com/api/v1/dam/assets/{assetId}/external-mappings/{mappingId}

Request

Headers
client-id: <your-client-id>
client-secret: <your-client-secret>
Body · json
{}

Response

Success · http
HTTP/1.1 200 OK
content-type: application/json

{
  "deleted": true,
  "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
}
  • Scope required: dam:integrations:write and workspace:corporate
POST

Abort upload session

Cancel a pending session, abort multipart uploads when applicable, and release quota reservation.

POST https://client.stillsweb.com/api/v1/dam/uploads/sessions/{sessionId}/abort

Request

Headers
client-id: <your-client-id>
client-secret: <your-client-secret>
content-type: application/json
Body · json
{
  "reason": "client_aborted"
}

Response

Success · http
HTTP/1.1 200 OK
content-type: application/json

{
  "sessionId": "8f1c2b4a-6d7e-4f90-b123-456789abcdef",
  "status": "aborted"
}

Field guide

assetId
Numeric id for this upload. Changes on every init/complete. Use in API paths and poll /assets/{assetId}/status.
assetPublicId
Stable public id for the logical file (same folder + filename + type). Drives embedUrl; unchanged when you replace the file in place.
statusUrl
Relative path from init/complete, e.g. /api/v1/dam/assets/171234/status. Prepend https://client.stillsweb.com.
previewStatus
processing → ready or failed. When failed, failureReason describes the pipeline step.
embedUrl
Canonical public delivery URL on formatai: 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.
assetUrl
Deprecated backward-compatible alias of embedUrl (same value when present). Do not use in new integrations.
uploadUrl
Temporary signed storage URL. PUT file bytes here only.
partsUrl
Relative path on https://client.stillsweb.com. Prepend the base URL before calling /parts.
previewUrl / urlVariants
Signed derivative URLs when previewStatus is ready.

Embed delivery examples

Append transform query params to embedUrl when needed. Use fmt for output format — not fm. f means flip.

  • https://data.stillsweb.com/e/ast_abc123
  • https://data.stillsweb.com/e/ast_abc123?w=800&h=800
  • https://data.stillsweb.com/e/ast_abc123?w=800&fmt=webp
  • https://data.stillsweb.com/e/ast_abc123?w=800&fmt=jpeg

When fmt is omitted, formatai returns WebP @ 80% quality with metadata stripped.

Folder targeting

Prefer folderId

When you already know the folder UUID from a prior upload or folder create response, send folderId. It always wins over folderName.

folderName create-or-reuse

If folderId is omitted, send folderName. Stillsweb looks up an existing corporate root folder case-insensitively ("Campaign 2026" matches "campaign 2026") or creates one.

v1 supports corporate-workspace only. Uploads and folders are owned by the credential's Stillsweb account — never by values in the request body.

Code examples

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());

Operations

Idempotency & retries

  • Reuse the same idempotency-key on init to get the existing session back.
  • Complete and abort also accept idempotency-key for safe retries.
  • External DAM idempotency is namespaced separately from web UI uploads.

Multipart uploads

  • Init returns uploadMode: multipart and a relative partsUrl.
  • POST https://client.stillsweb.com/api/v1/dam/uploads/sessions/{sessionId}/parts with partNumbers.
  • Send part ETags to /complete. Request fresh part URLs if they expire.
  • Browser clients need storage CORS exposing ETag.

Rate limits

  • Default: 120 requests/minute per client id.
  • Default: 240 requests/minute per IP address.
  • Configure via EXTERNAL_DAM_RATE_LIMIT_* env vars on services.
  • Set EXTERNAL_DAM_RATE_LIMIT_REDIS_URL (or REDIS_URL) for shared counters across pods.

Webhook signatures

  • Store signingSecret from POST /webhooks — it is shown once.
  • Verify X-Stillsweb-Signature: t={unix},v1={hmac} over {timestamp}.{rawBody}.
  • Use HMAC-SHA256 with the signing secret; reject timestamps older than 300 seconds.
  • Also inspect X-Stillsweb-Event and X-Stillsweb-Delivery-Id for idempotency.

Production notes

  • Run folder lowercase-index and upload-session client-context migrations.
  • Set EXTERNAL_DAM_RATE_LIMIT_REDIS_URL on services for multi-pod rate limits.
  • Raw storage keys are never returned by the public API.

Error codes

Failed responses include a stable code field so integrations can branch on retryable vs permanent errors.

CodeMeaning
INVALID_REQUESTInvalid body, folder target, MIME type, or file metadata.
UNAUTHORIZEDMissing or invalid client-id / client-secret headers.
FORBIDDENPlan, scope, workspace, or account status blocks the action.
NOT_FOUNDFolder, asset, upload session, or external mapping not found for this owner.
CONFLICTIdempotency or resource state conflict.
FILE_TOO_LARGEFile exceeds plan or upload limit.
RATE_LIMITEDPer-client or per-IP limit exceeded. Retry after the window.
DAM_UPSTREAM_ERRORPublic request accepted but internal DAM service failed.
AI_TOOLS_NOT_INCLUDED403 — the owner's plan has no AI image tools. Do not retry; the account needs an upgrade.
AI_MONTHLY_LIMIT_REACHED429 — the monthly AI allowance is used up (message carries the reset date). Not RATE_LIMITED: stop until the next period.
AI_TOO_MANY_PENDING429 — too many AI runs in flight for this owner. Retry after some finish.
AI_TOOL_ALREADY_RUNNING409 — the same tool is already running on this asset. Poll GET …/ai.
AI_TOOLS_UNAVAILABLE503 — AI service not configured or unreachable. Retry later.
AI_TOOL_UNSUPPORTED_FILE400 — the asset is not an image.
AI_TAGS_NOT_SUGGESTED400 — none of the given tags were suggested for this asset.
AI_DESCRIPTION_NOT_FOUND404 — describe has not completed for this asset.

Need client credentials?

Organization owners and team members with Manage API keys can create credentials in account settings.

Developer & Integrations