Picsha AI

picsha.ai API Specification (v1)

1. Product Philosophy

"S3 with a Brain." Picsha.ai is a serverless, usage-based media backend designed for AI Agents and "Vibe Coders." It abstracts complex processing pipelines (TUS, Rekognition, LibreOffice, Vector Search) into simple, developer-friendly endpoints.

2. Base URL & Authentication

  • Base URL: https://api.picsha.ai/v1
  • Authentication: Bearer Token via Header.
    Authorization: Bearer sk_live_51Mx...
    
  • Multi-Tenant Scoping:
    • Organizations: All requests are automatically scoped to the data silo of the Organization associated with your API Key.
    • End-User Tracking: When uploading or querying assets on behalf of a user, you must pass their identifier using the x-external-user-id header to ensure strict tenant boundaries.
    x-external-user-id: user_abc123
    
    • Organization-Wide Access: Requests made without an x-external-user-id header operate at the full scope of the API key's Organization. Use this only for internal tools and org-wide automations — customer-facing multi-user applications should always pass the end-user's identifier.
  • Dynamic Transaction Cost Tracking:
    • Automatic Cost Decoration: JSON object responses from the /v1 operational API include a "cost" attribute in standard currency format (e.g. "$0.0050"), representing the standard retail value of that specific transaction. Endpoints that return a bare JSON array (GET /webhooks, GET /transformations, GET /backgrounds, GET /dam/{assetId}/events) are not decorated — there is nowhere to attach the field.
    • No-Charge Error Guarantee: Any API transaction that results in an HTTP failure (status code >= 300, e.g., validation errors, 401 Unauthorized, or 500 Server Error) reports a transaction cost of "$0.0000".
    • Toggle: Response cost decoration is a deployment-level setting (ENABLE_RESPONSE_COST_TRACKING). Contact support to have it disabled for your account; it is not currently a self-service dashboard toggle.

API Fundamentals

Rate Limits & Quotas

The REST API is not currently rate-limited per request. What it does enforce is your plan quota: when an operation would exceed your organization's free-tier allowance and no billing method is on file, the API responds 402 Payment Required rather than throttling you.

{ "message": "Payment Required: You have exceeded your free tier limits for this operation. Please add a billing method in your dashboard to continue." }

The one rate-limited surface is the hosted MCP endpoint (POST /v1/mcp), capped at 120 requests per minute per credential. It returns 429 Too Many Requests with the standard x-ratelimit-limit, x-ratelimit-remaining, and x-ratelimit-reset headers. Other endpoints do not emit those headers today — do not build client logic that depends on them.

Retries

Requests are not idempotency-keyed. Retrying a failed POST /assets or POST /assets/generate will create a second asset (and a second billing event) if the original actually succeeded, so treat a timeout as "unknown" and reconcile with GET /assets before retrying billed operations. Deduplication does protect uploads of byte-identical files: re-uploading the same bytes returns the existing asset rather than storing and billing a second copy.

Error Codes

Errors return a flat JSON object with a human-readable message:

{ "message": "Asset not found" }

Some routes use { "error": "..." } instead, and schema-validation failures come back in Fastify's format:

{ "statusCode": 400, "code": "FST_ERR_VALIDATION", "error": "Bad Request", "message": "querystring/limit must be <= 100" }

Always branch on the HTTP status code rather than parsing the body — there is no stable machine-readable code field.

  • 400 Bad Request: Validation failed or invalid parameters were supplied.
  • 401 Unauthorized: Invalid or missing API key.
  • 402 Payment Required: The operation exceeded your plan quota — add a billing method.
  • 403 Forbidden: Missing or invalid delivery signature (Strict Transformations), or the asset is blocked by moderation.
  • 404 Not Found: The resource does not exist — or it exists but belongs to another organization. Cross-tenant reads are deliberately indistinguishable from missing records.
  • 429 Too Many Requests: Hosted MCP endpoint rate limit exceeded.
  • 500 Internal Server Error: An unexpected error occurred on Picsha's end.

3. Core Endpoints

A. Ingest (The "Magic" Upload)

POST /assets

Handles all file ingestion types (Multipart, URL, Raw) and triggers the AI processing pipeline based on configuration.

Headers:

  • Content-Type: multipart/form-data OR application/json (for URL fetch)
  • x-external-user-id: string (Required) - Unique identifier for the end-user uploading the asset.

Query Parameters:

  • ?ephemeral=true: Marks the asset for automatic deletion after 24 hours. Ideal for temporary processing pipelines where you only need to extract AI summaries or metadata without incurring long-term storage costs.
  • ?expires_at=2026-12-31T23:59:59Z: Optional ISO-8601 timestamp for when the asset should be automatically deleted by our cleanup workers.

Parameters (JSON Body for URL / Config):

{
  "url": "https://example.com/files/quarterly_report.docx",
  "config": {
    "auto_tag": true,          // Vision AI object and face detection. Defaults to true.
    "auto_summarize": true,    // AI summaries for images and documents, transcription for audio/video. Defaults to true.
    "vectorize": true,         // Generates multimodal embeddings for vector similarity search. Defaults to true.
    "location_lookup": true,   // Reverse geocodes EXIF coordinates. Defaults to true.
    "adaptive_stream": false,  // Triggers an async adaptive-bitrate (HLS) transcode job for videos. Defaults to false.
    "content_moderation": false, // Runs the content-moderation safety check. Defaults to false.
    "antivirus_scan": true,    // High-speed malware/virus scanning at ingest. Defaults to true; set false to opt out.
    "quality_metrics": true,   // Deterministic image quality scores + face attributes (no external calls, no per-metric billing). Defaults to true.
    "remove_background": false, // Generates a background-removed alpha cutout at ingest for instant bg_rem / bg_asset rendering. One charge per unique image. Defaults to false.
    "render_on_upload": "w=300&q=80; w=800&fit=cover", // Pre-warms dynamic delivery CDN cache with specific sizes
    "expires_at": "2026-12-31T23:59:59Z" // ISO-8601 timestamp for automatic asset deletion
  },
  "tags": ["finance", "report", "q4"],
  "metadata": {
    "project_id": "my_replit_app_123"
  }
}

Response (Success):

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "url": "https://picsha-assets.s3.us-east-1.amazonaws.com/org_abc/user_pete/550e8400-e29b-41d4-a716-446655440000/my_file.jpg",
  "message": "Asset uploaded successfully",
  "cost": "$0.0050"
}

Note: Ingestion processing runs asynchronously. You will receive an immediate response while the AI extraction, transcoding, and embedding run in the background. Subscribe to the asset.processed webhook to be notified when metadata and derivatives are ready.

B. Client-Side Presigned Upload URL

GET /assets/presigned

Generates a presigned S3 upload URL for direct client-side PUT uploading (e.g. for Uppy or other upload widgets).

Query Parameters:

  • filename: string (Required) - Original name of the file to be uploaded.
  • contentType: string (Required) - MIME type of the file.

Response:

{
  "method": "PUT",
  "url": "https://picsha-ingress.s3.amazonaws.com/org_abc123/user_pete/550e8400-e29b-41d4-a716-446655440000/my_file.jpg?AWSAccessKeyId=...",
  "headers": {
    "Content-Type": "image/jpeg"
  }
}

C. List Assets

GET /assets

Lists metadata and retrieval URLs for assets belonging to the authenticated Organization or User.

Query Parameters:

  • page: number (Default: 1) - Pagination page index.
  • limit: number (Default: 20) - Number of assets to return per page (max 100).
  • type: string (Optional) - Filter by mime-type prefix (e.g., image, video, application).
  • tag: string (Optional) - Filter by exact metadata tag.
  • search: string (Optional) - Simple keyword search on the original filename.
  • sort: string (Optional) - Sorting order. Available options: added-desc, added-asc, created-desc, created-asc, name-asc, name-desc.
  • albumId: string (Optional) - Filter by exact album group identifier.
  • metadataField + metadataValue: string (Optional) - Filter by an exact metadata key/value pair, e.g. metadataField=apiKeyName&metadataValue=prints app to list assets ingested by a specific API key (see Ingest attribution).

Response:

{
  "cost": "$0.0005",
  "data": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "status": "completed",
      "mimeType": "image/jpeg",
      "originalName": "team-pic.jpg",
      "url": "https://cdn.picsha.ai/...",
      "thumbnailUrl": "https://cdn.picsha.ai/render/550e8400-e29b-41d4-a716-446655440000?w=600&fmt=webp",
      "tags": ["team", "cambridge"],
      "metadata": {
        "project_id": "my_app_123"
      }
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 1
  }
}

Asset Status Lifecycle

Because ingestion is heavily asynchronous, assets transition through several system states during their lifecycle. The status property will always reflect one of the following canonical enum values:

  • queued: The asset has been saved and is waiting for an available Queue Worker node to begin processing.
  • active: The worker node has started the ingestion pipeline (derivative generation, metadata extraction, and AI processing).
  • completed: All AI analysis, transcription, multimodal embeddings, and primary web proxies have been generated successfully. The asset is fully indexed and ready for dynamic CDN delivery.
  • failed: An unrecoverable error occurred during the ingestion pipeline.
  • infected: The ClamAV sidecar detected malware or a virus. The file is quarantined and blocked from CDN delivery.
  • pending_moderation: (If enabled) AWS Rekognition Content Moderation flagged the asset for unsafe content. It is isolated pending manual review.
  • rejected: The asset was manually rejected during moderation review.

D. Get Asset Details

GET /assets/{id}

Retrieves full details and signed URLs for a single asset.

Response:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "cost": "$0.0002",
  "status": "completed",
  "mimeType": "image/jpeg",
  "originalName": "team-pic.jpg",
  "url": "https://cdn.picsha.ai/...",
  "thumbnailUrl": "https://cdn.picsha.ai/render/550e8400-e29b-41d4-a716-446655440000?w=600&fmt=webp",
  "tags": ["team", "cambridge"],
  "metadata": {
    "project_id": "my_app_123"
  }
}

E. Update Asset Metadata

PATCH /assets/{id}

Partially updates an asset's title, tags, or custom metadata object.

Request Body:

{
  "meta": {
    "title": "New Title"
  },
  "tags": ["add_this_tag", "-remove_this_tag"],
  "metadata": {
    "project_id": "updated_project_id"
  },
  "expires_at": "2026-12-31T23:59:59Z",
  "requiresSignature": true
}

Note: Prefixing a tag with a - sign (e.g. "-remove_this_tag") will remove it from the asset's tags list, while standard strings will append it. Pass null to expires_at to permanently remove an expiration date.

Response:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "message": "Asset updated successfully",
  "cost": "$0.0000"
}

F. Delete, Trash & Restore

DELETE /assets/{id}

By default, this moves the asset into a 30-day Trash state (soft delete), preventing accidental or malicious permanent data loss. Trashed assets immediately stop appearing in list and search results, their delivery URLs return 404 (cached CDN copies are evicted within seconds), and GET /assets/{id} continues to return them with status: "trashed" so you can build a recycle-bin UI. After 30 days, a nightly cleanup permanently purges them.

Parameters:

  • force (boolean): Pass ?force=true to skip the Trash and permanently, irreversibly destroy the asset (database record, search index entry, and stored files).

Response:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "message": "Asset deleted successfully",
  "trashed": true,
  "purgeAt": "2026-08-08T15:46:11.385Z",
  "cost": "$0.0005"
}

(With force=true, trashed is false and purgeAt is null.)

POST /assets/{id}/restore

Restores a trashed asset to its pre-trash state. Delivery, listing, and search resume immediately — no re-processing or re-analysis occurs. Returns 404 if the asset is not in the Trash.

Response:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "completed",
  "message": "Asset restored successfully",
  "cost": "$0.0005"
}

POST /assets/bulk-delete and POST /assets/bulk-restore

Bulk equivalents accepting { "ids": ["...", "..."] }. bulk-delete also accepts "force": true for permanent deletion. Unknown or unauthorized IDs are silently skipped, never deleted. Responses report deletedCount / restoredCount.

Listing the Trash

GET /assets?status=trashed returns only trashed assets (the recycle-bin view). All other list and search calls exclude trashed assets automatically.

G. AI Summarization

POST /assets/{id}/summarize

Returns the AI summary produced for an asset. Summaries are generated by the ingest pipeline (Anthropic Claude via Bedrock) and stored on the asset, so this call serves the existing summary and is not billed again.

Response (summary available): 200 OK

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "summary": "A detailed shot of a collaborative workspace in Cambridge...",
  "message": "Summary retrieved successfully",
  "cost": "$0.0000"
}

Response (no summary yet): 202 Accepted — analysis is re-queued for you.

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "message": "No summary available yet — analysis has been queued. Poll GET /v1/assets/{id} for ai.summaryLong.",
  "cost": "$0.0000"
}

Assets belonging to another organization return 404. To force a fresh pass over an asset that already has a summary, use POST /assets/{id}/analyze (below).

G2. Re-run AI Analysis

POST /assets/{id}/analyze

Re-runs the full ingest pipeline on an existing asset — face and object detection, summaries, quality metrics, and embeddings. Useful after enabling a capability you originally opted out of, or when an upstream analysis failed.

Request Body (optional):

{
  "config": {
    "auto_tag": true,
    "auto_summarize": true,
    "vectorize": true,
    "location_lookup": true,
    "quality_metrics": true
  }
}

Response: 202 Accepted

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "message": "Re-analysis queued",
  "cost": "$0.0015"
}

H. Resumable Uploads (TUS Protocol)

POST /upload/resumable

  • Standard TUS 1.0 Protocol endpoint.
  • Supports large files (>100MB) and unstable connections.
  • Implementation: Wraps internal tus-node-server.
  • SDK Support: Compatible with uppy and picsha-uploader SDK.

I. Generate New Images (Text-to-Image)

POST /assets/generate

Generates a brand-new image from a natural language prompt via MIMI and ingests it as a normal asset (analysis, DAM, renders, and further MIMI edits all work on it).

{
  "prompt": "a foggy pine forest at dawn",
  "aspect_ratio": "16:9",
  "quality": "standard"
}
  • prompt (string, required): Natural language description of the image to generate.
  • aspect_ratio (string, optional): One of 1:1, 3:4, 4:3, 9:16, 16:9, 2:3, 3:2, 4:5, 5:4. Default 1:1.
  • quality (string, optional): lite | standard | 4k. Default standard.

Response: 201 Created

{
  "assetId": "550e8400-e29b-41d4-a716-446655440000",
  "url": "https://cdn.picsha.ai/render/550e8400-e29b-41d4-a716-446655440000"
}

Billing: the same event/price as the corresponding MIMI mode (Lite 6.0¢, Standard 16.0¢, 4K 32.0¢).

J. Batch AI Analysis

POST /assets/analysis

Fetches the complete ingest analysis for up to 200 assets in a single call — EXIF capture time, camera, location, labels with confidence, face bounding boxes and attributes, AI captions/summaries, quality metrics, and (optionally) the 1024-dimensional multimodal embeddings. Built for AI agents and layout engines that need to reason over a whole photo set at once instead of issuing hundreds of GET /assets/{id} calls.

{
  "ids": ["550e8400-...", "661f9511-...", "..."],
  "includeEmbeddings": false
}
  • ids (string[], required): 1–200 asset IDs.
  • includeEmbeddings (bool, optional, default false): include each asset's multimodal vector. Off by default — embeddings add ~8KB per asset to the response.

Response: 200 OK

{
  "assets": [
    {
      "id": "550e8400-...",
      "originalName": "IMG_2041.jpg",
      "mimeType": "image/jpeg",
      "status": "completed",
      "width": 4032,
      "height": 3024,
      "captureDate": "2026-06-14T18:42:11.000Z",
      "cameraMake": "Apple",
      "cameraModel": "iPhone 15 Pro",
      "locationString": "Carmel-by-the-Sea, CA",
      "exif": { "...": "..." },
      "ai": {
        "labels": [{ "name": "Beach", "confidence": 99.2 }],
        "faces": [{ "boundingBox": { "...": "..." } }],
        "quality": { "sharpness": 82, "brightness": 61, "contrast": 55 },
        "faceDetails": [{ "eyesOpen": true, "smile": true, "poseYaw": 4.1 }]
      },
      "cutout": null,
      "embedding": null
    }
  ],
  "requested": 3,
  "returned": 3
}
  • Assets the caller is not authorized to read — and trashed assets — are silently omitted; compare returned against requested to detect gaps.
  • The ai object is the asset's full analysis record as produced by the ingest pipeline (see the Ingestion Pipeline doc); fields appear only when the corresponding pipeline stage ran.

4. Delivery & Transformation

GET /assets/{id}/render GET /seo/{id}/{seoUrlString}

Dynamic, edge-cached image transformations. Note that the CDN (cdn.picsha.ai/render/:assetId) sits directly in front of the API (api.picsha.ai/v1/assets/:assetId/render). Both URL path shapes are valid and route to the exact same transformation engine. The /seo alias route functions exactly the same as /render but supports semantic URL crawlers.

Forced Downloads You can force the browser to securely download an asset rather than displaying it by appending ?download=true (or its alias ?export=true). The file is served with a Content-Disposition: attachment header using a secure filename derived from the original asset's name and format.

Authorization for Generative Operations (always on)

Plain transformations (resizing, cropping, format conversion, watermarking, overlays) are publicly deliverable by default — anonymous <img src> embedding keeps working. Generative operations are not: any delivery URL carrying a billed generative parameter (mimi, mimi_bg, gen_rem, gen_fill, gen_mask, or bg_rem=true) must prove it is authorized, or it is rejected with 401 before any generation runs. There are two ways to authorize:

  1. Authenticated API call (server-side): include your Authorization: Bearer header as with any other endpoint. Nothing else is required — the engine validates that the asset belongs to your organization and automatically stamps a delivery signature onto its CDN redirect, so standard server-side render calls work unchanged. Authenticated generative calls are tenant-scoped: you can only run paid generations against your own organization's assets.
  2. Signed URL (public embedding): for generative URLs you hand to browsers or embed in pages, mint a ?sig= signature with POST /assets/{id}/sign-delivery (below). Sign the exact path form you will serve — API-form (/v1/assets/{id}/render, /v1/seo/...) or CDN-form (/render/{id}, /seo/{id}/{seoUrlString}) — and the exact query permutation.

This protection is enforced at both the API and the CDN edge and cannot be disabled. It exists so that nobody who discovers an asset ID can burn paid generations against your account.

Signed Delivery URLs & Strict Transformations (Security)

Beyond the always-on generative protection above, Picsha AI supports Strict Transformations for full lockdown. When Strict Transformations are enabled globally in your Organization settings (or individually per-asset via requiresSignature: true), you must append a cryptographically valid ?sig= parameter to the delivery URL for any query permutation — including plain transforms. This ensures that malicious users cannot artificially inflate your usage by enumerating parameters (e.g., ?w=1, ?w=2).

You can generate a signed URL using the helper endpoint: POST /assets/{id}/sign-delivery

{
  "urlPath": "/v1/seo/123e4567-e89b-12d3-a456-426614174000/image.webp",
  "queryParams": {
    "w": 600,
    "fit": "cover"
  }
}

Response:

{
  "signedUrl": "/v1/seo/123e4567-e89b-12d3-a456-426614174000/image.webp?fit=cover&w=600&sig=39950449a74b1306",
  "signature": "39950449a74b1306"
}

Note: the signature covers the exact query permutation — include download=true in queryParams when signing a forced-download URL.

For comprehensive details on standard parameters (dimensions, cropping, formats), smart AI cropping, watermarking, Background Operations (background removal/replacement), and the signature MIMI natural language generative editing capability (mimi, mimi_mode, upscale, mimi_bg, async), see the Transformations & Generative AI guide.


5. Search (Vector & Semantic)

Picsha provides hybrid searching combining Amazon Titan multimodal vectors with structural metadata filters.

A. Search via POST

POST /search

Query using a JSON body. Ideal for programmatic queries and complex filtering.

Request Body:

  • query: string (Default: "") - Conversational NLP search query.
  • mode: "standard" | "ai" (Default: "standard") - "standard" performs pure text/keyword search. "ai" performs vector-driven semantic search.
  • threshold: number (Default: 0.6) - Relevance confidence threshold (only for AI mode).
  • sort: string (Optional) - Sorting order: relevance, added-desc, added-asc, created-desc, created-asc, name-asc, name-desc.
  • limit: number (Default: 40) - Number of assets to return per page (max 100).
  • filters / advancedFilters: object (Optional) - Precise filter constraints:
    • addedAfter / addedBefore: string (ISO date format) - Ingestion timestamp range.
    • capturedAfter / capturedBefore: string (ISO date format) - EXIF photo capture date range.
    • face: string - Filter by specific recognized human face name.
    • object: string - Filter by specific auto-detected visual label/object.
    • place: string - Filter by reverse-geocoded location label.
    • ocr: string - Filter by text extracted via OCR.
    • type: string - Filter by mime-type prefix (e.g., image, video).
    • style: string - Filter by custom style categorization tag.
    • metadataField / metadataValue: string - Filter by specific custom/legacy metadata field and matching value.

Request JSON:

{
  "query": "photos of dogs on the beach",
  "mode": "ai",
  "threshold": 0.65,
  "advancedFilters": {
    "type": "image",
    "capturedAfter": "2026-01-01",
    "metadataField": "pictures_meta_subject.value",
    "metadataValue": "#special events"
  }
}

Response:

{
  "results": [
    {
      "id": "123e4567-e89b-12d3-a456-426614174000",
      "score": 0.92,
      "url": "https://cdn.picsha.ai/123e4567-e89b-12d3-a456-426614174000/source.jpg"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 40,
    "total": 1
  },
  "cost": "$0.0025"
}

B. Pagination

page and limit are supplied as query parameters even on the POST request, while the query and filters travel in the body:

POST https://api.picsha.ai/v1/search?page=2&limit=40

[!NOTE] Search is POST-only. There is no GET /search — send the query in a JSON body and keep pagination in the query string.


6. Billing & Usage

GET /usage

Returns your organization's metered usage events, aggregated by billing event type.

Query Parameters:

  • groupBy: "userId" (Optional) - Additionally break each event type down by end user (the x-external-user-id you sent at write time).

Headers:

  • x-org-id: string (Optional) - Defaults to the organization your API key belongs to. If sent, it must match that organization (dashboard sessions: an organization you are a member of) — a mismatch is rejected with 403.

Response:

{
  "usage": [
    { "type": "storage_upload", "userId": null, "totalQuantity": 10.5, "count": 42 },
    { "type": "mimi_job", "userId": null, "totalQuantity": 6, "count": 6 }
  ],
  "cost": "$0.0005"
}

totalQuantity is the summed metered quantity for that event type (GB for storage, job count for AI operations); count is the number of events. Per-event retail pricing is listed in your dashboard's billing section.


7. Digital Asset Management (DAM) Primitives

These endpoints support advanced DAM workflows like asset grouping, explicit 1-to-1 relationships (e.g., source RAW to web JPEG), and asset event/comment tracking.

  • Base Prefix: /v1/dam
  • Scoping: Every DAM endpoint requires authentication and is scoped to the organization of your API key (plus x-external-user-id when you send it). Organization and user are taken from the credential — never from the request body — and any asset you reference must belong to you, or the call returns 404.

A. Asset Events & Comments

Track comments, edits, status updates, or custom activities for specific assets.

POST /events

Create an asset event or user comment.

Request Body:

{
  "assetId": "550e8400-e29b-41d4-a716-446655440000",
  "eventType": "COMMENT_ADDED",
  "eventData": {
    "username": "Pete",
    "comment": "Nice composition in this image!"
  }
}

GET /:assetId/events

Fetch all events and comments for a specific asset.

Response (Success):

[
  {
    "id": "9f2a1c60-4d7b-4a12-8f3e-77c1b2d5e604",
    "assetId": "550e8400-e29b-41d4-a716-446655440000",
    "orgId": "org_abc123",
    "userId": "usr_dev_456",
    "eventType": "COMMENT_ADDED",
    "eventData": {
      "username": "Pete",
      "comment": "Nice composition in this image!"
    },
    "createdAt": "2026-05-22T23:00:00Z"
  }
]

(This endpoint returns a bare array, so it carries no cost attribute.)

B. Asset Relationships

Explicitly relate assets (e.g., versions, derivations, replacements).

POST /relationships

Create an explicit relationship between two assets.

Request Body:

{
  "sourceAssetId": "222e8400-e29b-41d4-a716-446655440000",
  "targetAssetId": "333e8400-e29b-41d4-a716-446655440000",
  "relationshipType": "REPLACES"
}

C. Asset Groups & Collections

Logically bucket assets into folders or campaigns.

POST /groups

Create a new group.

Request Body:

{
  "name": "Q3 Marketing Campaign",
  "metadata": {
    "project_id": "proj_123"
  }
}

GET /groups/:id

Retrieve a specific group by ID.


8. Webhooks

Manage your Enriched Agentic Webhooks. Webhooks allow your system to receive rich, cognitive payloads asynchronously when assets finish processing. For full details on the payload structure and security signatures, please see the Enriched Agentic Webhooks guide.

A. List Webhooks

GET /webhooks

Retrieve all registered webhooks for the authenticated user/organization.

Response:

[
  {
    "id": 42,
    "url": "https://api.yourdomain.com/receiver",
    "events": ["asset.processed"],
    "config": {
      "include_embeddings": true,
      "include_text": true
    },
    "createdAt": "2026-06-02T02:58:00.000Z"
  }
]

B. Register Webhook

POST /webhooks

Register a new webhook subscription.

Request Body:

{
  "url": "https://api.yourdomain.com/receiver",
  "events": ["asset.processed"],
  "config": {
    "include_embeddings": true,
    "include_text": true
  }
}

C. Update Webhook

PATCH /webhooks/{id}

Update an existing webhook's endpoint URL, event subscriptions, or payload configuration.

Request Body (Partial updates allowed):

{
  "events": ["asset.processed", "asset.failed"]
}

D. Delete Webhook

DELETE /webhooks/{id}

Permanently remove a webhook subscription.

Response:

{
  "message": "Webhook deleted successfully"
}

9. Named Transformations

Named transformations allow you to save a complex set of rendering parameters (like smart cropping, watermarking, and format conversion) into a single alias. You can then apply all of these parameters to an asset delivery URL by simply appending ?t=your_alias_name.

A. List Transformations

GET /transformations

Retrieve all named transformations for your organization.

Response:

[
  {
    "id": "tf_1a2b3c",
    "name": "Social Media Auto Crop",
    "alias": "social_square",
    "commands": {
      "w": 1080,
      "h": 1080,
      "fit": "cover",
      "pos": "attention",
      "fmt": "webp"
    },
    "createdAt": "2026-06-01T12:00:00.000Z"
  }
]

B. Create Transformation

POST /transformations

Save a new rendering parameter configuration under a short alias.

Request Body:

{
  "name": "Watermarked Thumbnail",
  "alias": "thumb_watermark",
  "commands": {
    "w": 400,
    "wm": "444e8400-e29b-41d4-a716-446655440000",
    "wm_pos": "southeast"
  }
}

C. Get Transformation

GET /transformations/{id}

Retrieve a specific transformation configuration by ID.

D. Update Transformation

PUT /transformations/{id}

Overwrite the commands or details for an existing transformation.

Request Body:

{
  "name": "Updated Name",
  "alias": "thumb_watermark",
  "commands": {
    "w": 500,
    "wm": "444e8400-e29b-41d4-a716-446655440000"
  }
}

E. Delete Transformation

DELETE /transformations/{id}

Permanently delete a transformation.


9b. Backgrounds Library

Backgrounds are org-scoped scene images used for cutout compositing: pair them with the render engine's bg_asset parameter to layer any asset's background-removed cutout over a stored scene — a pure-CPU operation served from the CDN edge once cached. A background references an existing asset, so it inherits the full ingest pipeline (moderation, derivatives) automatically.

A. List Backgrounds

GET /backgrounds

B. Register Background

POST /backgrounds

Registers an already-uploaded image asset as a reusable compositing background.

Request Body:

{
  "name": "Rainy London Street",
  "assetId": "550e8400-e29b-41d4-a716-446655440000",
  "metadata": { "weather": "rain", "region": "uk" }
}

Usage with the render engine:

GET /v1/assets/{productAssetId}/render?w=800&h=800&bg_asset={backgroundId}

The product's stored cutout is composited over the scene. If no cutout exists yet, one is generated and persisted on first request (billed once per unique image), and every subsequent render — any size, any background — is a fast, cached CPU composite.

C. Get Background

GET /backgrounds/{id}

D. Update Background

PATCH /backgrounds/{id}

Update the name or metadata of a background.

E. Delete Background

DELETE /backgrounds/{id}


9c. Moderation Review Queue

When content_moderation is enabled at ingest, flagged assets land in pending_moderation and are blocked from delivery until a human resolves them.

A. List the Queue

GET /assets/moderation/pending

Lists your organization's assets awaiting manual review, newest first.

Query Parameters:

  • limit: number (Default: 20, max 100)
  • offset: number (Default: 0)

Response:

{ "assets": [ { "id": "550e8400-...", "status": "pending_moderation", "originalName": "upload.jpg" } ], "cost": "$0.0000" }

B. Approve or Reject

POST /assets/{id}/moderation

Request Body:

{ "action": "approve" }

approve returns the asset to completed and restores delivery; reject sets rejected, which keeps it permanently blocked. Assets not in pending_moderation return 400.


9d. Downloads, Signing & Notifications

A. Download the Original

GET /assets/{id}/download

Streams the original, un-transformed bytes with a Content-Disposition: attachment header. Authenticated and tenant-scoped — for public download links, mint a signed delivery URL with ?download=true instead.

Query Parameters:

  • inline: "true" | "false" (Default: "false") - true streams for inline browser display rather than forcing a download.

B. Sign a Delivery URL

POST /assets/{id}/sign-delivery — mints the HMAC ?sig= for a transformation URL; see Delivery & Transformation above.

C. Time-Limited Link to the Original File

POST /sign

Returns a presigned S3 URL to the asset's original stored file — the raw upload, not a render. Use it for expiring download links to source files (a RAW, a master video, a signed contract PDF).

Request Body:

{ "asset_id": "550e8400-e29b-41d4-a716-446655440000", "expires": 3600 }
  • expires: number (Default: 3600) - Link lifetime in seconds.

Response:

{ "url": "https://picsha-assets.s3.amazonaws.com/...&X-Amz-Expires=3600", "expiresAt": "2026-08-15T18:30:00.000Z", "cost": "$0.0005" }

D. Signed Upload URL

POST /upload/sign

Server-side counterpart to GET /assets/presigned, taking a JSON body and returning the asset id it provisioned.

Request Body:

{ "contentType": "image/jpeg", "filename": "sunset.jpg" }

Response:

{ "uploadUrl": "https://...", "assetId": "550e8400-...", "key": "org_abc/user_pete/550e8400-.../sunset.jpg", "method": "PUT" }

E. Live Notification Stream (SSE)

GET /notifications/stream

A Server-Sent Events stream of the same events that drive webhooks (asset.processed, face.propagation.*), scoped to the authenticated user — or to x-external-user-id when you authenticate with an API key. Useful for updating a UI as uploads finish, without standing up a webhook receiver.

Because EventSource cannot set headers, this endpoint also accepts the credential as a ?token= query parameter.

F. Escalate to Support

POST /support/escalate

Sends a message to the Picsha engineering team — the REST endpoint behind the escalate_to_support MCP and A2A tools.

Request Body:

{ "subject": "Docs gap: signed URL expiry", "headline": "exp parameter unclear", "message": "..." }

G. Account Statistics

GET /account/stats

Returns storage totals, per-feature AI usage, and retail cost breakdowns for the organization — the data behind the dashboard's usage panel.


10. Asynchronous Work & Completion Signals

Ingestion, transcoding, and generative rendering all run outside the request that triggers them. There are two completion channels, and which one you use depends on the workload:

WorkloadHow you learn it finished
Ingest, AI analysis, embeddings, HLS transcode (adaptive_stream)The asset.processed webhook, or poll GET /assets/{id} until status leaves queued/active
MIMI / generative renders sent with &async=trueGET https://cdn.picsha.ai/render/status/{jobId} — see the Transformations & MIMI guide

[!NOTE] There is no general-purpose Core API job-polling endpoint. Use the webhook (or asset status) for pipeline work, and the CDN status endpoint for generative renders.


11. Rekognition Face Indexing

Picsha natively integrates with AWS Rekognition to provide automatic biometric face indexing.

Faces are indexed automatically at ingest: when auto_tag is enabled, every detected face is added to your organization's Rekognition Collection and stored with a Rekognition-issued faceId. You then put a name to one of those detected faces, and Picsha propagates that name across every other matching face in your library.

A. Name a Detected Face

PUT /v1/rekognition/faces/{faceId}

Assigns a person's name to a face that ingest already detected, then propagates it to matching faces across the organization (asynchronously — subscribe to the face.propagation.* webhooks to track it).

Path Parameters:

  • faceId: The Rekognition-issued face identifier from the asset's analysis record. Read it from GET /assets/{id} (ai.faces[].faceId) or POST /assets/analysis.

[!IMPORTANT] faceId must be an identifier Picsha already indexed. You cannot invent your own key here — passing an unknown id returns a 500 with Face not found. Find the face first, then name it.

Request Body:

{
  "name": "John Doe"
}

Response:

{
  "id": "b2c4d6e8-...",
  "faceId": "0f4a1c62-8b3d-4e21-9f77-6a1e2d3c4b5a",
  "name": "John Doe",
  "confidence": "99.4",
  "cost": "$0.0120"
}

Once named, the name is searchable as a hard entity filter — {"advancedFilters": {"face": "John Doe"}} (see Search).