Requests, scheduling, and migration

Automate the approval-oriented workflows behind replacement links, multi-file request forms, scheduled revisions, and website media imports.

Management routes accept a bearer session or a workspace API key with the stated scope. Public upload routes use their expiring request token and never expose a workspace API key.

Replacement requests

POST/api/assets/{asset_id}/replacement-requests
200 OK

Create an expiring upload capability for one existing file.

Required scopeassets:write
Request body
{
  "recipientName": "Agency producer",
  "note": "Upload the approved hero image",
  "expiresInDays": 7
}

The response includes the secret token once and a sharePath under /replace/.

GET/api/assets/{asset_id}/replacement-requests
200 OK

List the latest replacement requests and their review state.

Required scopeassets:read
POST/api/assets/{asset_id}/replacement-requests/{request_id}/approve
200 OK

Publish an uploaded candidate now or create a scheduled publication and optional rollback.

Required scopeassets:write
Schedule approval
{
  "publishAt": "2026-09-01T14:00:00Z",
  "rollbackAt": "2026-09-08T14:00:00Z"
}
POST/api/assets/{asset_id}/replacement-requests/{request_id}/reject
200 OK

Reject a submitted candidate and remove its temporary bytes.

Required scopeassets:write
DELETE/api/assets/{asset_id}/replacement-requests/{request_id}
200 OK

Revoke a request link and remove any temporary candidate.

Required scopeassets:write
GET/api/replacement-requests/{token}
200 OK

Read recipient-safe request metadata. This public route uses the request token.

POST/api/replacement-requests/{token}/upload
200 OK

Create a size-bound presigned upload URL for a replacement candidate.

POST/api/replacement-requests/{token}/submit
200 OK

Verify the uploaded bytes and submit the candidate for review.

Scheduled revisions

POST/api/assets/{asset_id}/scheduled-revisions
200 OK

Schedule a retained revision to become current, with an optional later rollback.

Required scopeassets:write
Request body
{
  "version": 12,
  "publishAt": "2026-09-01T14:00:00Z",
  "rollbackAt": null
}
GET/api/assets/{asset_id}/scheduled-revisions
200 OK

List queued and processing publication jobs for one asset.

Required scopeassets:read
DELETE/api/assets/{asset_id}/scheduled-revisions/{job_id}
200 OK

Cancel a publication job while it is still queued.

Required scopeassets:write

Multi-file asset requests

POST/api/request-forms
201 Created

Create an expiring form with up to 25 requested file fields.

Required scopeassets:write
Request body
{
  "name": "Launch package",
  "instructions": "Use final approved exports",
  "expiresAt": "2026-09-30T23:59:59Z",
  "files": [{
    "label": "Hero image",
    "required": true,
    "acceptedTypes": ["image/*", ".pdf"],
    "maxBytes": 26214400,
    "bucketId": "bucket_uuid"
  }]
}
GET/api/request-forms
200 OK

List request forms and submission counts in the active workspace.

Required scopeassets:read
GET/api/request-forms/submissions
200 OK

List submitted files awaiting or following a decision.

Required scopeassets:read
POST/api/request-forms/files/{file_id}/decision
200 OK

Approve, reject, assign to another bucket, or use a submission as a replacement.

Required scopeassets:write
Assign to a destination
{
  "action": "assign",
  "bucketId": "bucket_uuid",
  "note": "Approved for campaign use"
}
DELETE/api/request-forms/{form_id}
200 OK

Revoke a request form so its public token returns 410.

Required scopeassets:write
The public form flow uses /api/public/request-forms/{token}, then creates a submission, initializes each upload, completes each file upload, and finally completes the submission. Upload bytes go directly to the returned presigned URL as application/octet-stream.

Website migration

POST/api/migration-scans
202 Accepted

Queue a public-site scan of 1 to 200 pages.

Required scopeassets:write
Request body
{ "url": "https://example.com", "maxPages": 50 }
GET/api/migration-scans/{scan_id}
200 OK

Read scan progress and discovered media, including detected MIME, broken state, references, and imported stable URL.

Required scopeassets:read
POST/api/migration-scans/{scan_id}/imports
202 Accepted

Queue up to 200 available files into a bucket and normalized nested folder path.

Required scopeassets:write
Request body
{
  "mediaIds": ["media_uuid"],
  "bucketId": "bucket_uuid",
  "path": "website-import/images"
}
GET/api/migration-scans/{scan_id}/export/{format}
200 OK

Download a csv or json map from original URLs to new stable URLs.

Required scopeassets:read

Personal notification preferences

GET/api/notifications/preferences
200 OK

Return every supported event with its effective channels, timing, enabled state, and inheritance flag.

Required scopesigned-in user session
PUT/api/notifications/preferences
200 OK

Save a personal override for one event. At least one supported channel is required.

Required scopesigned-in user session
Request body
{
  "eventType": "request.submitted",
  "channels": ["in_app", "email"],
  "mode": "immediate",
  "enabled": true
}
Notification records and preferences belong to a person. Workspace API keys cannot use the notification endpoints.