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-idheader to ensure strict tenant boundaries.
x-external-user-id: user_abc123- Organization-Wide Access: Requests made without an
x-external-user-idheader 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
/v1operational 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, or500 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.
- Automatic Cost Decoration: JSON object responses from the
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-dataORapplication/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 appto 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=trueto 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
uppyandpicsha-uploaderSDK.
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 of1:1,3:4,4:3,9:16,16:9,2:3,3:2,4:5,5:4. Default1:1.quality(string, optional):lite|standard|4k. Defaultstandard.
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, defaultfalse): 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
returnedagainstrequestedto detect gaps. - The
aiobject 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:
- Authenticated API call (server-side): include your
Authorization: Bearerheader 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. - Signed URL (public embedding): for generative URLs you hand to browsers or embed in pages, mint a
?sig=signature withPOST /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 noGET /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 (thex-external-user-idyou 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 with403.
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-idwhen 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 returns404.
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, max100)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") -truestreams 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:
| Workload | How 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=true | GET 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 fromGET /assets/{id}(ai.faces[].faceId) orPOST /assets/analysis.
[!IMPORTANT]
faceIdmust be an identifier Picsha already indexed. You cannot invent your own key here — passing an unknown id returns a500withFace 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).