Reference: Uploads, Media and Previews

Sending a file in chunks that can be resumed, the media we have received and analysed, and free before/after previews of one frame.

Uploads

How a file on your computer becomes a media on our side, in chunks that can be resumed. The whole process is explained in Uploads and media.

POST /uploads #

Credential: API key and secret (HTTP Basic) or an access token (Bearer) · scope media:write

Opens an upload: you tell us the file's name and size, and we tell you how to cut it into chunks and where to send them. Nothing is sent yet. This is step 2 of the workflow.

Parameters
NameWhereTypeRequiredWhat it is
Idempotency-Keyheaderstringno (recommended)Your own value of 1 to 64 printable characters: a new random one (a UUID) for every new request, and the same one when you send that request again after a lost response. The shell examples keep it in a variable that a second run of the same block does not change (see Idempotency keys). Example: 9b2f0c7e-3d4a-4f61-8a5e-2c1b7d9e0f43.
file_namebody (JSON)stringyesThe file's name with its extension: .mov, .mp4, .mxf, .mkv, .mpg, .mpeg, .wav or .mp3 (upper or lower case). Up to 1000 characters are accepted; a name longer than 200 characters is shortened (its start and its extension are kept). Example: A001_C003.mov.
sizebody (JSON)integeryesThe file's size in bytes: 1 to 53,687,091,200 (50 GiB). Example: 35181362.
chunk_sizebody (JSON)integernoThe size of every chunk except the last, in bytes: 4,194,304 (4 MiB) to 33,554,432 (32 MiB). Default: 8,388,608 (8 MiB).
sha256body (JSON)stringno (recommended)The whole file's SHA-256 as 64 lower-case hexadecimal characters - in the example replace YOUR_FILE_SHA256 with your file's: sha256sum A001_C003.mov (Linux, Git Bash), shasum -a 256 A001_C003.mov (macOS) or (Get-FileHash A001_C003.mov).Hash.ToLower() (PowerShell). We check it after the completion; a file that does not match ends as a failed media with error.code ChecksumMismatch. Leave it out rather than send another file's. Example: a93dcb26...f44f.
purposebody (JSON)stringnojob or preview (a short part uploaded only for a preview; deleted 24 hours after the upload was completed unless a job uses it). Default: job.
sourcebody (JSON)objectnoAnything you want to keep with the media - for example the clip name, reel name and timecode of your edit - up to 4 KB as JSON. It comes back as source of the media; we do not read it. Example: {"clip_name": "A001_C003"}.
ingest.keep_frame_ratebody (JSON)booleannotrue keeps 29.97 fps material with 3:2 pulldown at 29.97 fps (see Uploads and media). Default: false.
picturebody (JSON)objectno (recommended)The file's frame size, {"width": 1920, "height": 1080} (whole pixels; 0 and 0 for sound only). A picture larger than limits.picture of GET /health (2048 × 1556, either way round) answers PictureTooLarge at once - before you send anything.
notify_urlbody (JSON)stringnoA public https address of yours (up to 2000 characters) that gets a webhook when the media is analysed. API keys only.

What you get back: 201 Created with an Upload object and a Location header (the upload's address). Keep its id, chunk_size and chunk_count.

Good to know: At most 10 uploads can be open at the same time per user (TooManyUploads). An open upload is kept for 7 days after its last chunk arrived.

Request
KEY_UPLOADS=${KEY_UPLOADS:-$(openssl rand -hex 16)}
curl -sS -u "$AUTH" "$API/uploads" -H "Content-Type: application/json" -H "Idempotency-Key: $KEY_UPLOADS" \
     -d '{"file_name": "A001_C003.mov", "size": 35181362,
          "sha256": "YOUR_FILE_SHA256",
          "source": {"clip_name": "A001_C003", "reel_name": "A001", "timecode_in": "01:00:10:00"}}'
Response: 201 Created
{
  "id": "upl_5f0c2a9e41b7d3c8e6a1f094",
  "status": "open",
  "file_name": "A001_C003.mov",
  "size": 35181362,
  "chunk_size": 8388608,
  "chunk_count": 5,
  "max_parallel": 4,
  "chunk_url_template": "https://www.recuromedia.com/api/1.0/uploads/upl_5f0c2a9e41b7d3c8e6a1f094/chunks/{index}",
  "received_count": 0,
  "received_bytes": 0,
  "missing": [0, 1, 2, 3, 4],
  "purpose": "job",
  "media_id": null,
  "expires_at": "2026-10-13T09:03:38Z",
  "direct_chunk_url_template": "https://www.recuromedia.com/api/1.0/uploads/upl_5f0c2a9e41b7d3c8e6a1f094/chunks/{index}"
}

Error codes it can answer (each links to what it means and what to do): ValidationFailed, MediaTypeUnsupported, FileTooLarge, PictureTooLarge, TooManyUploads, InsufficientStorage, IdempotencyKeyReused, InsufficientScope. Any endpoint can also answer RateLimited and InternalError.

PUT /uploads/{upload_id}/chunks/{index} #

Credential: API key and secret (HTTP Basic) or an access token (Bearer) · scope media:write

Sends one chunk of the file. Chunk 0 is the first chunk_size bytes of the file, chunk 1 the next chunk_size bytes, and so on; the last chunk is the rest. Send them in any order, up to max_parallel at the same time.

Parameters
NameWhereTypeRequiredWhat it is
upload_idpathstringyesThe upload's id, as POST /uploads gave it: upl_ and 24 characters. Example: upl_5f0c2a9e41b7d3c8e6a1f094.
indexpathintegeryesThe chunk's number, from 0 to chunk_count - 1.
Content-Typeheaderstringyesapplication/octet-stream.
Repr-Digestheaderstringno (recommended)The chunk's SHA-256 in base64 between colons: sha-256=:<base64>: (RFC 9530). A chunk damaged on the way is then refused (ChunkDigestMismatch) and you send it again.
(the body)body (bytes)bytesyesThe chunk's bytes exactly: chunk_size bytes, or for the last chunk size - index × chunk_size bytes. Not base64, not JSON.

What you get back: 200 OK with index (the chunk you sent) and received_count (how many different chunks have arrived so far).

Good to know: Sending a chunk again is harmless: it is written again. A completed upload takes no more chunks (UploadNotOpen). Chunks have no per-minute limit; at most 4 chunks of one upload, 16 of one credential and 32 of one user can be on their way at the same time (TooManyParallelChunks, Retry-After: 1).

Request
curl -sS -u "$AUTH" -X PUT "$API/uploads/upl_5f0c2a9e41b7d3c8e6a1f094/chunks/0" \
     -H "Content-Type: application/octet-stream" \
     -H "Repr-Digest: sha-256=:$(openssl dgst -sha256 -binary chunk_aaaa | base64):" \
     --data-binary @chunk_aaaa
Response: 200 OK
{ "index": 0, "received_count": 1 }

Error codes it can answer (each links to what it means and what to do): ChunkSizeMismatch, ChunkDigestMismatch, ChunkOutOfRange, ChunkTooLarge, ValidationFailed, NotFound, UploadNotOpen, UploadExpired, TooManyParallelChunks, InsufficientStorage. Any endpoint can also answer RateLimited and InternalError.

GET /uploads/{upload_id} #

Credential: API key and secret (HTTP Basic) or an access token (Bearer) · scope media:read

Shows an upload and which chunks are still missing. Use it to resume an upload that was interrupted: send the chunks in missing, then complete.

Parameters
NameWhereTypeRequiredWhat it is
upload_idpathstringyesThe upload's id, as POST /uploads gave it: upl_ and 24 characters. Example: upl_5f0c2a9e41b7d3c8e6a1f094.
If-None-MatchheaderstringnoThe last ETag (a 304 when nothing has changed).

What you get back: 200 OK with an Upload object and an ETag header. missing lists at most the first 1,000 missing chunks: when you have sent those, ask again.

Good to know: An upload changes only when its chunks arrive, so it answers no Retry-After: ask when you need its missing list, at most once a second. A completed upload is kept 1 day after its completion (with its media_id); after that this answers NotFound - find the media with GET /media. Its expires_at counts only while it is open.

Request
curl -sS -u "$AUTH" "$API/uploads/upl_5f0c2a9e41b7d3c8e6a1f094"
Response: 200 OK
{
  "id": "upl_5f0c2a9e41b7d3c8e6a1f094", "status": "open", "file_name": "A001_C003.mov", "size": 35181362,
  "chunk_size": 8388608, "chunk_count": 5, "max_parallel": 4,
  "chunk_url_template": "https://www.recuromedia.com/api/1.0/uploads/upl_5f0c2a9e41b7d3c8e6a1f094/chunks/{index}",
  "received_count": 4, "received_bytes": 33554432, "missing": [4], "purpose": "job", "media_id": null, "expires_at": "2026-10-13T09:03:38Z"
}

Error codes it can answer (each links to what it means and what to do): NotFound, UploadExpired. Any endpoint can also answer RateLimited and InternalError.

POST /uploads/{upload_id}/complete #

Credential: API key and secret (HTTP Basic) or an access token (Bearer) · scope media:write

Tells us that every chunk has arrived. The file becomes a media and its analysis starts. Then wait for the media to be ready (GET /media/{media_id}).

Parameters
NameWhereTypeRequiredWhat it is
upload_idpathstringyesThe upload's id, as POST /uploads gave it: upl_ and 24 characters. Example: upl_5f0c2a9e41b7d3c8e6a1f094.

What you get back: 202 Accepted with status (completed), upload_id, media_id (keep it: it is the id of your media from now on) and media (a Media object), and a Location header with the media's address. Completing again gives the same media - nothing is made twice. The example is an upload opened with a sha256: its media starts analyzing, stage information (the SHA-256 is checked first); without sha256 it starts queued, stage null.

Good to know: The body is empty (--data '' sends an empty POST). Missing chunks answer UploadIncomplete with details.missing (at most the first 1,000): send those and complete again. When you already have a media with the same name, the new one is named with a number, for example A001_C003 (2).mov. We read the file's frame size before it becomes a media: a picture larger than limits.picture answers PictureTooLarge, and the upload and its file are deleted (nothing is charged).

Request
curl -sS -u "$AUTH" --data '' "$API/uploads/upl_5f0c2a9e41b7d3c8e6a1f094/complete"
Response: 202 Accepted
{
  "status": "completed",
  "upload_id": "upl_5f0c2a9e41b7d3c8e6a1f094",
  "media_id": "med_4821",
  "media": { "id": "med_4821", "name": "A001_C003.mov", "status": "analyzing", "stage": "information", "percent": 0.0, "eta_seconds": null, "size": 35181362,
             "purpose": "job", "source": { "clip_name": "A001_C003", "reel_name": "A001", "timecode_in": "01:00:10:00" },
             "jobs": [], "previews": [], "created_at": "2026-10-06T09:03:39Z", "error": null }
}

Error codes it can answer (each links to what it means and what to do): UploadIncomplete, UploadNotOpen, UploadExpired, MediaBusy, PictureTooLarge, NotFound. Any endpoint can also answer RateLimited and InternalError.

DELETE /uploads/{upload_id} #

Credential: API key and secret (HTTP Basic) or an access token (Bearer) · scope media:write

Gives up an upload: its chunks are deleted. Use it for an upload you will not complete, so that it does not count against your 10 open uploads.

Parameters
NameWhereTypeRequiredWhat it is
upload_idpathstringyesThe upload's id, as POST /uploads gave it: upl_ and 24 characters. Example: upl_5f0c2a9e41b7d3c8e6a1f094.

What you get back: 204 No Content: deleted; no body. Deleting a completed upload deletes only the upload's record - its media stays.

Request
curl -sS -u "$AUTH" -X DELETE "$API/uploads/upl_5f0c2a9e41b7d3c8e6a1f094"
Response: 204 No Content
HTTP/2 204

Error codes it can answer (each links to what it means and what to do): NotFound. Any endpoint can also answer RateLimited and InternalError.

Media

The files we have received and analysed.

POST /media/imports #

Credential: API key and secret (HTTP Basic) or an access token made from them - not an app sign-in · scope media:write

Makes a media from a file that is on the internet already (your own storage, Dropbox, Google Drive) instead of uploading it from your computer. We download it ourselves. API keys only - apps upload.

Parameters
NameWhereTypeRequiredWhat it is
Idempotency-Keyheaderstringno (recommended)Your own value of 1 to 64 printable characters: a new random one (a UUID) for every new request, and the same one when you send that request again after a lost response. The shell examples keep it in a variable that a second run of the same block does not change (see Idempotency keys). Example: 9b2f0c7e-3d4a-4f61-8a5e-2c1b7d9e0f43.
urlbody (JSON)stringyesA public https:// address of the file, up to 4000 characters. Private and local addresses (for example 192.168.x.x or localhost) are refused, also when a redirect leads there; at most 5 redirects are followed.
file_namebody (JSON)stringnoThe media's name, with one of the extensions of POST /uploads. Default: the name the address's server gives (its Content-Disposition header), else the last part of the address.
usernamebody (JSON)stringnoA user name for the address (HTTP authentication of your storage). It is sent only to the host you named.
passwordbody (JSON)stringnoThe password that goes with username.
picturebody (JSON)objectnoThe file's frame size, as for POST /uploads: a picture over limits.picture answers PictureTooLarge and nothing is downloaded. Without it, the analysis checks the size: the media ends failed with error.code PictureTooLarge.
notify_urlbody (JSON)stringnoA public https address of yours for a webhook when the media is analysed.

What you get back: 202 Accepted with a Media object (status queued, stage downloading, percent of the download) and a Location header. When the download has finished, the media is analysed like an upload: poll GET /media/{media_id} until ready.

Good to know: An app sign-in gets InsufficientScope with details.credential api_key.

Request
KEY_IMPORTS=${KEY_IMPORTS:-$(openssl rand -hex 16)}
curl -sS -u "$AUTH" "$API/media/imports" -H "Content-Type: application/json" -H "Idempotency-Key: $KEY_IMPORTS" \
     -d '{"url": "https://storage.example.com/reels/A001_C003.mov"}'
Response: 202 Accepted
{ "id": "med_4822", "name": "A001_C003.mov", "status": "queued", "stage": "downloading", "percent": 0.0, "eta_seconds": null, "size": 35181362,
  "purpose": "job", "source": null, "jobs": [], "previews": [], "created_at": "2026-10-06T09:05:12Z", "error": null }

Error codes it can answer (each links to what it means and what to do): ValidationFailed, ImportUrlFailed, MediaTypeUnsupported, FileTooLarge, PictureTooLarge, InsufficientStorage, InsufficientScope, IdempotencyKeyReused. Any endpoint can also answer RateLimited and InternalError.

GET /media #

Credential: API key and secret (HTTP Basic) or an access token (Bearer) · scope media:read

Lists your media, newest first, a page at a time. Use it to find a media again (for example after your program restarted).

Parameters
NameWhereTypeRequiredWhat it is
statusquerystringnoOnly media in this state: queued, analyzing, ready, failed or all. Default: all.
purposequerystringnojob, preview or all. Default: job (preview uploads are left out).
limitqueryintegernoHow many entries one page holds: 1 to 200. Default: 50.
cursorquerystringnoThe next_cursor of the page before, exactly as it came. Leave it out for the first page. See Lists and pages.

What you get back: 200 OK with data (a list of Media objects - without jobs and previews; ask a single media for those) and next_cursor (a string to get the next page, or null on the last page).

Good to know: One request looks at most at 500 media. With a status or purpose filter a page can therefore hold fewer than limit entries - even none - and still have a next_cursor: follow next_cursor until it is null.

Request
curl -sS -u "$AUTH" "$API/media?status=ready&limit=2"
Response: 200 OK
{ "data": [ { "id": "med_4821", "name": "A001_C003.mov", "status": "ready", "...": "..." }, { "id": "med_4790", "...": "..." } ],
  "next_cursor": "eyJiZWZvcmUiOjQ3OTB9" }

Error codes it can answer (each links to what it means and what to do): ValidationFailed. Any endpoint can also answer RateLimited and InternalError.

GET /media/{media_id} #

Credential: API key and secret (HTTP Basic) or an access token (Bearer) · scope media:read

Shows one media: how far its analysis is, and - once it is ready - what the analysis found: length, frame rate, frame size, borders, black-and-white or colour, sound, timecode, scenes and warnings. Poll it after complete until status is ready or failed.

Parameters
NameWhereTypeRequiredWhat it is
media_idpathstringyesThe media's id, as POST /uploads/{upload_id}/complete or GET /media gave it. Example: med_4821.
If-None-MatchheaderstringnoThe last ETag (a 304 when nothing has changed).

What you get back: 200 OK with a Media object, an ETag and a Retry-After header: 1 while the media is queued or analyzing (poll again after 1 second), 60 otherwise.

Good to know: A media whose analysis failed is still a 200: status is failed and error says why. Delete it and upload the file again.

Request
curl -sS -u "$AUTH" "$API/media/med_4821"
Response: 200 OK
{
  "id": "med_4821",
  "name": "A001_C003.mov",
  "status": "ready",
  "stage": null,
  "percent": 100.0,
  "eta_seconds": null,
  "size": 35181362,
  "purpose": "job",
  "duration_seconds": 2.0,
  "frames": 50,
  "frame_rate": { "num": 25, "den": 1 },
  "width": 1440,
  "height": 1080,
  "source_geometry": { "width": 1440, "height": 1080, "sar": "1:1", "field_order": "progressive" },
  "pixel_aspect": "1:1",
  "field_order": "progressive",
  "pulldown": false,
  "crop": { "left": 0, "top": 0, "right": 0, "bottom": 0, "inner_width": 1440, "inner_height": 1080, "ratio": "1.33:1" },
  "color": "bw",
  "audio": { "channels": 2, "sample_rate": 48000, "silent": false },
  "timecode": "01:00:10:00",
  "scene_count": 1,
  "warnings": [],
  "source": { "clip_name": "A001_C003", "reel_name": "A001", "timecode_in": "01:00:10:00" },
  "jobs": [],
  "previews": [],
  "created_at": "2026-10-06T09:03:39Z",
  "error": null
}

Error codes it can answer (each links to what it means and what to do): NotFound. Any endpoint can also answer RateLimited and InternalError.

GET /media/{media_id}/scenes #

Credential: API key and secret (HTTP Basic) or an access token (Bearer) · scope media:read

Lists the scenes the analysis found (a scene is a stretch of film between two cuts), with their first and last frame, start time, whether they are black-and-white or colour, and a sharpness score.

Parameters
NameWhereTypeRequiredWhat it is
media_idpathstringyesThe media's id, as POST /uploads/{upload_id}/complete or GET /media gave it. Example: med_4821.

What you get back: 200 OK with data, one entry per scene: index (from 0), first_frame and last_frame (frame numbers from 0; the last frame belongs to the scene), start_seconds (seconds from the start), color (bw or color) and quality (the scene's sharpness score, 0 to 1; higher is sharper).

Good to know: A sound-only media has no scenes (data is empty).

Request
curl -sS -u "$AUTH" "$API/media/med_4821/scenes"
Response: 200 OK
{ "data": [ { "index": 0, "first_frame": 0, "last_frame": 49, "start_seconds": 0.0, "color": "bw", "quality": 0.82 } ] }

Error codes it can answer (each links to what it means and what to do): NotFound, MediaNotReady. Any endpoint can also answer RateLimited and InternalError.

GET /media/{media_id}/thumbnail.jpg #

Credential: API key and secret (HTTP Basic) or an access token (Bearer) · scope media:read

Gives you a still image (JPEG) of the media at a point in time - for a list or a preview picture in your own program.

Parameters
NameWhereTypeRequiredWhat it is
media_idpathstringyesThe media's id, as POST /uploads/{upload_id}/complete or GET /media gave it. Example: med_4821.
at_secondsquerynumbernoWhere, in seconds from the start: a number from 0, for example 1.5. A time after the end is taken as 0.2 seconds before the end; a negative time or text answers ValidationFailed. Default: the middle of the media.
widthqueryintegernoThe image's width in pixels: 64 to 960 (an odd number is made one smaller). The height follows the picture's shape. Default: 480.

What you get back: 200 OK with the JPEG image (image/jpeg), cached by your client for up to 300 seconds.

Good to know: A sound-only media has no picture: NotFound.

Request
curl -sS --fail -u "$AUTH" -o thumbnail.jpg "$API/media/med_4821/thumbnail.jpg?at_seconds=1&width=320" || rm -f thumbnail.jpg
Response: 200 OK
HTTP/2 200
content-type: image/jpeg
cache-control: private, max-age=300
etag: "599620f9aa4903bb419f679b"

Error codes it can answer (each links to what it means and what to do): NotFound, MediaNotReady, ValidationFailed. Any endpoint can also answer RateLimited and InternalError.

DELETE /media/{media_id} #

Credential: API key and secret (HTTP Basic) or an access token (Bearer) · scope media:write

Deletes a media together with its previews and jobs, as the workspace does. Running jobs are stopped, and what they were charged without delivering a result is given back.

Parameters
NameWhereTypeRequiredWhat it is
media_idpathstringyesThe media's id, as POST /uploads/{upload_id}/complete or GET /media gave it. Example: med_4821.

What you get back: 204 No Content: deleted; no body. This cannot be undone: download the results you want to keep first.

Request
curl -sS -u "$AUTH" -X DELETE "$API/media/med_4821"
Response: 204 No Content
HTTP/2 204

Error codes it can answer (each links to what it means and what to do): NotFound. Any endpoint can also answer RateLimited and InternalError.

Previews

Free before/after images of one frame, to try settings before you pay for a job.

POST /media/{media_id}/previews #

Credential: API key and secret (HTTP Basic) or an access token (Bearer) · scope previews:write

Makes a free before/after image of one frame of the media with the settings you are considering - so you can see the effect before you pay for a job. A preview is never charged.

Parameters
NameWhereTypeRequiredWhat it is
media_idpathstringyesThe media's id, as POST /uploads/{upload_id}/complete or GET /media gave it. Example: med_4821.
Idempotency-Keyheaderstringno (recommended)Your own value of 1 to 64 printable characters: a new random one (a UUID) for every new request, and the same one when you send that request again after a lost response. The shell examples keep it in a variable that a second run of the same block does not change (see Idempotency keys). Example: 9b2f0c7e-3d4a-4f61-8a5e-2c1b7d9e0f43.
settingsbody (JSON)objectyesWhat the job does: either preset_id or filters (not both), optionally output_format and output_scaling.
settings.preset_idbody (JSON)stringone of the twoA preset of GET /catalogue (presets[].id): its filters, output format and output scaling. Example: pre_90. A preset's upload server (a website setting) is never used by an API job: the result stays here for you to download.
settings.filtersbody (JSON)array of stringsone of the twoFilter keys of GET /catalogue (filters[].key; the full list is in Filters). At most one of light_dust_and_scratches, medium_dust_and_scratches and heavy_dust_and_scratches. An empty list [] runs no filter: the job only re-encodes the media into the output format (and scales it, with output_scaling), at the full price of its tier. Example: ["noise", "medium_dust_and_scratches"].
settings.output_formatbody (JSON)stringnoThe result's file type: mov_prores_422_hq, mov_prores_4444, mxf_dnxhr_hqx, mxf_dnxhr_444, mp4_h264 or mp4_hevc. Default: the preset's format, or mov_prores_422_hq with filters.
settings.output_scalingbody (JSON)stringnoThe result's height: source (the same as the media), 1080 (HD) or 2160 (4K). Default: the preset's scaling, or source with filters. A larger output can raise the price (see Billing).
at_framebody (JSON)integernoThe frame to show, from 0 to the media's frames - 1.
at_secondsbody (JSON)numbernoOr the time to show, in seconds from 0 to the media's duration_seconds. Use one of the two. Default (neither): the middle of the media.
kindbody (JSON)stringnoOnly image. Default: image.

What you get back: 202 Accepted with a Preview object (status queued) and a Location header. Poll GET /previews/{preview_id} until ready; in our tests a preview took 3 to 30 seconds.

Good to know: Limits: 20 restoration and 5 colorization previews per company in 24 hours (PreviewLimitReached), 2 being made at the same time per user (PreviewsBusy). Every plan has colorization previews; the Free plan has no daily count (previews_left_today null). A still image cannot show motion or sound: image stabilization, content stabilization, natural speed, high frame rate and the sound filters are left out of a preview.

Request
KEY_PREVIEWS=${KEY_PREVIEWS:-$(openssl rand -hex 16)}
curl -sS -u "$AUTH" "$API/media/med_4821/previews" -H "Content-Type: application/json" -H "Idempotency-Key: $KEY_PREVIEWS" \
     -d '{"settings": {"filters": ["noise", "medium_dust_and_scratches"]}, "at_frame": 24}'
Response: 202 Accepted
{
  "id": "prv_3171", "media_id": "med_4821", "status": "queued", "kind": "image", "frame": 24, "progress": null,
  "before_url": null, "after_url": null, "width": null, "height": null, "charged": false,
  "previews_left_today": { "restoration": 19, "colorization": 5 }, "expires_at": null, "error": null
}

Error codes it can answer (each links to what it means and what to do): MediaNotReady, PreviewsBusy, PreviewLimitReached, RangeInvalid, ValidationFailed, IdempotencyKeyReused, NotFound. Any endpoint can also answer RateLimited and InternalError.

GET /previews/{preview_id} #

Credential: API key and secret (HTTP Basic) or an access token (Bearer) · scope media:read

Shows a preview: whether it is still being made, and - once it is ready - the addresses of its two images.

Parameters
NameWhereTypeRequiredWhat it is
preview_idpathstringyesThe preview's id, as POST /media/{media_id}/previews gave it. Example: prv_3171.
If-None-MatchheaderstringnoThe last ETag (a 304 when nothing has changed).

What you get back: 200 OK with a Preview object, an ETag and a Retry-After header (1 while queued or rendering, 60 otherwise).

Good to know: A ready preview is kept for 6 hours (expires_at).

Request
curl -sS -u "$AUTH" "$API/previews/prv_3171"
Response: 200 OK
{
  "id": "prv_3171", "media_id": "med_4821", "status": "ready", "kind": "image", "frame": 24, "progress": null,
  "before_url": "https://www.recuromedia.com/api/1.0/previews/prv_3171/before.jpg",
  "after_url": "https://www.recuromedia.com/api/1.0/previews/prv_3171/after.jpg",
  "width": 1440, "height": 1080, "charged": false,
  "previews_left_today": { "restoration": 19, "colorization": 5 }, "expires_at": "2026-10-06T15:04:02Z", "error": null
}

Error codes it can answer (each links to what it means and what to do): NotFound. Any endpoint can also answer RateLimited and InternalError.

GET /previews/{preview_id}/{which}.jpg #

Credential: API key and secret (HTTP Basic) or an access token (Bearer) · scope media:read

Downloads one of the two images of a ready preview: before.jpg (the frame as it is now, cropped and scaled as the result will be) or after.jpg (the same frame with the settings).

Parameters
NameWhereTypeRequiredWhat it is
preview_idpathstringyesThe preview's id, as POST /media/{media_id}/previews gave it. Example: prv_3171.
whichpathstringyesbefore or after.

What you get back: 200 OK with the JPEG image, cached by your client for up to 600 seconds. Before the preview is ready: PreviewNotReady (Retry-After: 5).

Request
curl -sS --fail -u "$AUTH" -o after.jpg "$API/previews/prv_3171/after.jpg" || rm -f after.jpg
Response: 200 OK
HTTP/2 200
content-type: image/jpeg
cache-control: private, max-age=600

Error codes it can answer (each links to what it means and what to do): NotFound, PreviewNotReady. Any endpoint can also answer RateLimited and InternalError.

DELETE /previews/{preview_id} #

Credential: API key and secret (HTTP Basic) or an access token (Bearer) · scope previews:write

Deletes a preview; one that is still being made is stopped. It does not give back a preview of the day's count.

Parameters
NameWhereTypeRequiredWhat it is
preview_idpathstringyesThe preview's id, as POST /media/{media_id}/previews gave it. Example: prv_3171.

What you get back: 204 No Content: deleted; no body.

Request
curl -sS -u "$AUTH" -X DELETE "$API/previews/prv_3171"
Response: 204 No Content
HTTP/2 204

Error codes it can answer (each links to what it means and what to do): NotFound. Any endpoint can also answer RateLimited and InternalError.