# Media API reference

> Canonical HTML: https://screenrig.ai/docs/api/media
> Source SHA-256: e0c997a2ab813502f0d99a41d2ed1d3d6b26dbaf1a3d7e514c1cce0278ff8422

Media uploads. This group has 38 operations, each with its authentication, parameters, request body fields, request template and responses. The [API overview](https://screenrig.ai/docs/api.md) covers services, authentication, requests and retries, problem codes and versioning.

Replace uppercase placeholders. Save a schema-valid JSON body in request.json when required. Cookie examples use a local cookie jar from an authenticated session.

## Media uploads

### GET /api/media

**List Media**

Lists ready media newest first, at most 1000 per page. folder keeps only the files directly inside one media folder (its ID), or with root only those at the library root; a well-formed folder ID that names no folder lists nothing. next_cursor is the position of the last returned row while more rows remain; pass it back as after, with the same filters, for the next page. next_cursor is null on the last page.


Operation: listMedia.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| tag | query | optional | Exact media tag. Untagged objects are omitted when this filter is present. |
| primitive | query | optional |  |
| folder | query | optional | A media folder ID keeps only the files directly inside that folder, not those of its subfolders; root keeps only the files at the library root. Omitted lists every file. |
| ids | query | optional | Comma-separated media ids, at most 200 distinct; more is invalid_request at ids. Keeps only those files: an id that names no ready file is absent, and the other filters and paging still apply. An empty value lists nothing. |
| library | query | optional | true lists the organization's Library project instead of this project: its items are read-only here and carry library true. An organization with no Library, or the Library project itself, lists nothing with true. |
| after | query | optional | next_cursor of the previous page of the same list, with the same filters. Omitted starts at the newest row. A value that is not such a cursor is invalid_request. |

#### Request template

~~~sh
curl --request GET --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/media'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Media | MediaList (application/json) |

### POST /api/media/generations

**Create Media Generation**

Generate one still image from a prompt, store it as ready private project media, and bill its usage. The POST answers 202 at once with the Operation, with or without Prefer: respond-async, because a proxy in front of the API drops a request that sends no byte for 100 s and a high-quality still can take longer.

Poll GET /api/operations/{id} until it is succeeded, when its result is {media_id, usage: {quality, credits, usd}} (MediaGenerationUsage), or failed, when its error is the problem. A refusal decided before any vendor work (payment_required, reseller_generation_limit, rate_limited, not_ready, invalid_request) still answers the POST.

It never includes image bytes, b64_json, object keys, signed URLs, vendor URLs, vendor cost, vendor tokens, or the deployment model name. The prompt is customer bytes and is not echoed.

Before the prompt reaches the image vendor the server appends fixed framing requirements to it: the artwork must fill the frame edge to edge, and no television, monitor, screen, display, bezel, frame, border, mounting, stand, wall, room, device mockup, drop shadow, or photograph of an installed sign may be depicted, and any wording in the caller's prompt describing where the sign will hang or what device will show it is treated as placement metadata rather than subject matter.

Decorative rules and borders inside the design itself remain available. The caller's prompt is never rewritten or stripped and is stored unchanged. Idempotency-Key replays the same Operation, never a second billed attempt. Clients do not PUT or commit generated bytes.

The stored rendition is lossy WebP (quality 90) at the exact aspect size with a 1080 px short edge (16:9 is 1920x1080, 9:16 1080x1920, 1:1 1080x1080, 4:3 1440x1080, 3:4 1080x1440, 3:2 1620x1080, 2:3 1080x1620); the vendor canvas is centre-cropped and resampled to that size.

The filename is distinctive per generation, generated-16x9-1a2b3c4d.webp, with the suffix taken from the media id. Fetch the bytes with GET /api/media/{id}/content. Own-gen-then-upload remains valid.

On Standard, remaining that cannot cover twice the vendor cost of the still, rounded up to whole credits, after the intro floor (negative 1,000,000 credits until 2027-01-01, then zero) returns payment_required, except SCREENRIG__ENVIRONMENT=development, and admission before the vendor call requires remaining above that floor by the per-quality minimum: 200 credits for low, 800 for medium, 2000 for high.

Other plans are never refused for their balance.

An organization billed through a reseller is refused with reseller_generation_limit (402) once its reseller's delegated customers have been billed the month's image generation limit ($100 at list price while the reseller awaits review, $1,000 after, counted per UTC month), unless the organization's own credit covers the image; a generation already admitted finishes.

Empty selected-vendor configuration returns not_ready. Project bearer only. One in-flight generation per organization; further parallelism is rate_limited.


Operation: createMediaGeneration.

- Authentication: projectBearer OR dashboardSession
- Request body: MediaGenerationRequest

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| Idempotency-Key | header | required |  |
| Prefer | header | optional | Optional. The route answers 202 with the Operation at once with or without it; poll the Operation. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| prompt | required | string | minLength: 1 maxLength: 4000 |
| aspect_ratio | optional | string | Allowed: "1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3" default: "16:9" |
| quality | optional | string | Allowed: "low", "medium", "high" default: "medium" |
| tag | optional | string | pattern: "^[A-Za-z0-9]{1,32}$" |
| references | optional | array |  |

#### Request template

~~~sh
curl --request POST --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt --header 'Idempotency-Key: REQUEST_ID' --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/media/generations'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 202 | The generation was accepted and runs on; poll GET /api/operations/{id} until the Operation is succeeded (result media_id and usage) or failed (error). The same Idempotency-Key answers with the same Operation. | Operation (application/json); headers: Preference-Applied, RateLimit-Policy, RateLimit |
| 402 | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |
| 404 | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |
| 500 | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### GET /api/media-folders

**List Media Folders**

Lists every media folder in the project, at most 1000, ordered by name. Folders organize the media library, and a playlist selector by:folder plays the files directly inside one folder. A folder's parent_id is null at the library root.

Build the tree from parent_id; a file's folder is Media.folder_id. library=true lists the organization Library's folders instead (read-only; a by:folder selector may name one).


Operation: listMediaFolders.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| library | query | optional | true lists the organization's Library project instead of this project: its items are read-only here and carry library true. An organization with no Library, or the Library project itself, lists nothing with true. |

#### Request template

~~~sh
curl --request GET --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/media-folders'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Media folders | MediaFolderList (application/json) |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### POST /api/media-folders

**Create Media Folder**

Creates one media folder at the library root (parent_id null or absent) or inside parent_id. Names are trimmed, hold 1 to 100 Unicode characters with no controls, are not "." or "..", and may repeat. An organization holds at most 1000 folders across its projects (quota_exceeded) nested at most 10 deep (invalid_request). Emits media.folder_created.


Operation: createMediaFolder.

- Authentication: projectBearer OR dashboardSession
- Request body: MediaFolderCreate

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| Idempotency-Key | header | optional | Omit for a fresh operation. Supply and reuse a key to deduplicate retries; the CLI manages keys automatically. |
| project_id | query | optional | With dashboardSession, the project shown when creating the folder. If supplied, must match the session's selected project, otherwise resource_conflict; an empty or repeated value is invalid_request. Omission preserves older dashboard clients. Project bearer requests use their authorized project. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| name | required | string | Trimmed; 1 to 100 Unicode characters, no controls, not . or .. minLength: 1 maxLength: 100 |
| parent_id | optional | inline | The folder to create it in; null or absent for the library root. |

#### Request template

~~~sh
curl --request POST --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/media-folders'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Created folder. | MediaFolder (application/json); headers: ETag |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### GET /api/media-folders/{id}

**Get Media Folder**


Operation: getMediaFolder.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| id | path | required |  |

#### Request template

~~~sh
curl --request GET --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/media-folders/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | One media folder. | MediaFolder (application/json); headers: ETag |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### PATCH /api/media-folders/{id}

**Update Media Folder**

Renames a folder (name) or moves it with everything inside (parent_id, null for the library root). A folder cannot move into itself or a folder inside it, and the move must keep every folder within 10 levels (invalid_request). A change advances the folder revision and emits media.folder_updated; the same values are a no-op.

If-Match optionally guards the revision.


Operation: updateMediaFolder.

- Authentication: projectBearer OR dashboardSession
- Request body: MediaFolderPatch

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| id | path | required |  |
| If-Match | header | optional | Optional positive resource revision. Omit to write the current resource; a supplied stale revision is revision_conflict. |
| Idempotency-Key | header | optional | Omit for a fresh operation. Supply and reuse a key to deduplicate retries; the CLI manages keys automatically. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| name | optional | string | Trimmed; 1 to 100 Unicode characters, no controls, not . or .. minLength: 1 maxLength: 100 |
| parent_id | optional | inline | Move the folder into this one; null for the library root. |

#### Request template

~~~sh
curl --request PATCH --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/media-folders/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Updated folder. | MediaFolder (application/json); headers: ETag |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### DELETE /api/media-folders/{id}

**Delete Media Folder**

Deletes one folder. Nothing inside is deleted - its files and folders move up into the deleted folder's parent (the library root when it had none), each moved file and folder advancing its revision. A playlist selector by:folder that names the deleted folder then matches nothing; playing screens drop that primitive at their next selector resolve.

Emits media.folder_deleted. If-Match optionally guards the revision.


Operation: deleteMediaFolder.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| id | path | required |  |
| If-Match | header | optional | Optional positive resource revision. Omit to write the current resource; a supplied stale revision is revision_conflict. |
| Idempotency-Key | header | optional | Omit for a fresh operation. Supply and reuse a key to deduplicate retries; the CLI manages keys automatically. |

#### Request template

~~~sh
curl --request DELETE --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/media-folders/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | Deleted; its contents now sit in its parent. | no body |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### POST /api/media/move

**Move Media**

Moves files (media_ids) and folders (folder_ids) into one folder (folder_id), or to the library root when folder_id is null, in one all-or-nothing request of at most 1000 ids. Every id must exist (not_found). A folder cannot move into itself or a folder inside it, and the move must keep every folder within 10 levels (invalid_request).

Items already in the destination are left as they are; each moved file and folder advances its revision. Moving files changes what the by:folder selectors of their old and new folders play, from each screen's next selector resolve; moving a folder changes no folder's own files. Emits media.moved.

A Propose credential's move makes each moved file pending and queues its media review, and approved playlists' selectors resolve it by the folder it was approved with until a person approves the change (docs/reviews.md).


Operation: moveMedia.

- Authentication: projectBearer OR dashboardSession
- Request body: MediaMove

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| Idempotency-Key | header | optional | Omit for a fresh operation. Supply and reuse a key to deduplicate retries; the CLI manages keys automatically. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| folder_id | required | inline | Destination folder; null for the library root. |
| media_ids | optional | array |  |
| folder_ids | optional | array |  |

#### Request template

~~~sh
curl --request POST --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/media/move'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | How many files and folders moved. | MediaMoveResult (application/json) |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### POST /api/media/uploads

**Create Media Upload**


Operation: createMediaUpload.

- Authentication: projectBearer OR dashboardSession
- Request body: MediaUploadDeclaration

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| Idempotency-Key | header | required |  |
| project_id | query | optional | Required with dashboardSession: the project where the file was queued. Must match the session's selected project, otherwise resource_conflict. This binds upload admission across tabs. The returned upload id remains project-scoped for byte upload and commit. Omitted with a project bearer. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| filename | required | string | Name of the bytes being uploaded, as they will be sent: a bare file name of at most 255 UTF-8 bytes, without a directory, backslash or control characters. minLength: 1 maxLength: 255 |
| source_filename | optional | string | Caller's original file name before any client-side transcode. Bare file name only, at most 255 UTF-8 bytes. Stored verbatim on the ready media as source_filename and used to derive filename. minLength: 1 maxLength: 255 |
| content_type | required | string | Allowed: "image/png", "image/jpeg", "image/webp", "image/gif", "video/mp4", "video/webm", "audio/mpeg" |
| bytes | required | integer | minimum: 1 maximum: 1073741824 |
| sha256 | required | string | pattern: "^[a-f0-9]{64}$" |
| tag | optional | string | pattern: "^[A-Za-z0-9]{1,32}$" |
| folder_id | optional | string | The media folder the ready media lands in; omitted lands it at the library root. The folder must exist when the upload is declared (invalid_request at folder_id otherwise) and when it commits (the operation fails with invalid_request: declare it again). An uploader (docs/uploaders.md) must send one of its folders (outside_upload_scope otherwise). |

#### Request template

~~~sh
curl --request POST --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt --header 'Idempotency-Key: REQUEST_ID' --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/media/uploads'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Short-lived signed authorization for exactly one private object PUT. Send the returned method and every returned header verbatim. This is also how the dashboard uploads. Its browser PUTs the bytes straight to the object store, never through the API, because a proxy in front of the API refuses a request body over 100 MB. A browser sets Content-Length itself from the body, so it sends every returned header except that one. | MediaUploadSession (application/json); headers: Cache-Control, RateLimit-Policy, RateLimit |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### POST /api/media/uploads/{id}/commit

**Commit Media Upload**


Operation: commitMediaUpload.

- Authentication: projectBearer OR dashboardSession
- Request body: MediaCommit

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| id | path | required |  |
| Idempotency-Key | header | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| content_type | required | string | Allowed: "image/png", "image/jpeg", "image/webp", "image/gif", "video/mp4", "video/webm", "audio/mpeg" |
| bytes | required | integer | minimum: 1 maximum: 1073741824 |
| sha256 | required | string | pattern: "^[a-f0-9]{64}$" |

#### Request template

~~~sh
curl --request POST --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt --header 'Idempotency-Key: REQUEST_ID' --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/media/uploads/RESOURCE_ID/commit'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 202 | Exact declaration persisted for asynchronous verification and immutable private publication. | Operation (application/json) |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### POST /api/selectors/preview

**Preview Selector**

Read-only preview of current project-owned ready image or video matches. Returns filename ordering and normalized policy; random playback shuffles on each new pass on the Player. Does not advance cursors, mint content grants, or alter manifests. More than 1024 matches returns invalid_request.


Operation: previewSelector.

- Authentication: projectBearer
- Request body: SelectorPreviewInput

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| timezone | optional | string | IANA zone media availability is read in; default UTC. |
| rect | optional | object | The primitive's rect size; required with selector.fit aspect. |
| primitive | required | string | Allowed: "image", "video" |
| selector | required | inline |  |

#### Request template

~~~sh
curl --request POST --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --header 'Content-Type: application/json' --data '@request.json' 'https://api.screenrig.ai/api/selectors/preview'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Current selector matches. | SelectorPreview (application/json) |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### GET /api/media/{id}

**Get Media**


Operation: getMedia.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| id | path | required |  |

#### Request template

~~~sh
curl --request GET --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/media/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Ready private media metadata. Original bytes are available to the owning project through the project export route and to a screen only through an exact runtime manifest grant. | Media (application/json); headers: ETag |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### PATCH /api/media/{id}

**Patch Media**

Sets or clears the media item's tag or tags. A Propose credential's change of the tag set makes the item pending and queues its media review; approved playlists' selectors resolve it by the tags it was approved with until a person approves the change (docs/reviews.md). A patch that keeps the same tag set applies and the item stays approved.


Operation: patchMedia.

- Authentication: projectBearer
- Request body: MediaTagPatch

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| id | path | required |  |
| If-Match | header | optional | Optional positive resource revision. Omit to write the current resource; a supplied stale revision is revision_conflict. |
| Idempotency-Key | header | optional | Omit for a fresh operation. Supply and reuse a key to deduplicate retries; the CLI manages keys automatically. |

#### Request template

~~~sh
curl --request PATCH --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/media/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Ready media with the updated tag. Runtime tag selectors observe the change at their next playlist-loop refresh. | Media (application/json); headers: ETag |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### DELETE /api/media/{id}

**Delete Media**


Operation: deleteMedia.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| id | path | required |  |
| If-Match | header | optional | Optional positive resource revision. Omit to write the current resource; a supplied stale revision is revision_conflict. |
| Idempotency-Key | header | optional | Omit for a fresh operation. Supply and reuse a key to deduplicate retries; the CLI manages keys automatically. |

#### Request template

~~~sh
curl --request DELETE --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/media/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | Tombstoned. Referenced desired or active grants cannot be deleted. | no body |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### PATCH /api/media/{id}/tags

**Patch Media Tags**

A signed-in person sets a media item's tags. tags is the complete new set, kept sorted: an item's single tag, when it has one, counts as one of its tags and is replaced with the rest, so send it again to keep it. An empty array clears every tag, and a malformed or repeated tag, or more than 32, is invalid_request.

It is the tag change patchMedia makes under a Manage credential, on the dashboard session: the media revision moves by one and media.updated carries the person as its actor, whether or not the set changed. A person's change is live, never a review.

Media waiting for a person's review (review_state pending, left by a Propose credential's upload or change) is refused with media_pending_review (409) and nothing changes: decide its review in Reviews first, so a tag edit never approves an agent's proposal. Playlist selectors by tag observe the new tags at their next playlist-loop refresh.

The body has no other member. If-Match carries the media revision and is required (invalid_request when absent, revision_conflict when stale). The dashboard session only; agents change tags with patchMedia. listMedia with tag finds the item by its new tags.


Operation: patchMediaTags.

- Authentication: dashboardSession
- Request body: MediaTagsPatch

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |
| If-Match | header | required | The media item's current revision, quoted or bare (the ETag of the last read). A stale revision is revision_conflict with current_revision. |
| Idempotency-Key | header | optional | Omit for a fresh operation. Supply and reuse a key to deduplicate retries; the CLI manages keys automatically. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| tags | required | array | Replacement tag set; an empty array clears it. |

#### Request template

~~~sh
curl --request PATCH --cookie cookies.txt --cookie-jar cookies.txt --header 'If-Match: "REVISION"' --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/media/RESOURCE_ID/tags'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The ready media item as stored now; ETag carries its revision. | Media (application/json); headers: ETag |
| 409 | media_pending_review — the item waits for a person's review; decide it first. | Problem (application/problem+json) |
| 412 | revision_conflict — If-Match is stale; the error's current_revision carries the current revision. | Problem (application/problem+json) |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### PATCH /api/media/{id}/availability

**Patch Media Availability**

Sets when a media item may be picked by the dynamic selectors (all, tag and folder) that play it, or clears the rule with availability null. The rule is the schedule's: from and until (civil, minute precision, until exclusive), windows, monthly and dates occurrences and except dates, read in the time zone of each screen that resolves the selector.

A file outside its rule is left out of the selection at the next playlist-loop boundary, and returns when the rule holds again; a window edge is followed within a minute of the Player's next loop. No playlist or manifest revision changes. Explicit ids (selector by id or ids) ignore the rule, as does everything that is not a dynamic selector.

A screen that is offline keeps its last selection until it reconnects. Setting the rule the item already has changes nothing. A Propose credential cannot set it (insufficient_access): when media plays is for a person with Manage access. Media waiting for a person's review is refused with media_pending_review.

If-Match carries the media revision and is required.


Operation: patchMediaAvailability.

- Authentication: projectBearer OR dashboardSession
- Request body: MediaAvailabilityPatch

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| id | path | required |  |
| If-Match | header | required | The media item's current revision, quoted or bare (the ETag of the last read). A stale revision is revision_conflict. |
| Idempotency-Key | header | optional | Omit for a fresh operation. Supply and reuse a key to deduplicate retries; the CLI manages keys automatically. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| availability | required | inline | The new rule, or null to make the file always available. |

#### Request template

~~~sh
curl --request PATCH --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt --header 'If-Match: "REVISION"' --header 'Content-Type: application/json' --data '@request.json' 'https://api.screenrig.ai/api/media/RESOURCE_ID/availability'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The ready media item as stored now; ETag carries its revision. | Media (application/json); headers: ETag |
| 409 | media_pending_review — the item waits for a person's review; decide it first. | Problem (application/problem+json) |
| 412 | revision_conflict — If-Match is stale. | Problem (application/problem+json) |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### POST /api/media/{id}/replace

**Replace Media**

Replaces a ready media item's file in place with an upload that was declared (createMediaUpload) and whose bytes were written, instead of committing it as new media.

The media keeps its id, so every playlist and schedule that names it plays the new file without being rescheduled, and the folder, tag and all-media selectors that pick it play it at their next resolve. The replacement must be the same kind of file (image for image, video for video, audio for audio); another kind is invalid_request.

A changed aspect ratio is allowed: the operation's result carries a warnings entry aspect_ratio_changed naming the playlists that use the item. Every upload rule applies (the plan's video byte limit, the dimension limits, the storage allowance).

The file in service stays in the media's version history (listMediaVersions) and counts toward stored bytes until its version is deleted. If-Match, when sent, is the media revision the caller read (revision_conflict when stale).

The answer is the upload's operation, like commitMediaUpload: poll getOperation; a succeeded operation's result is {media_id, id, version, warnings}. Bytes identical to the file in service replace nothing and fail the operation with resource_conflict.

Under effective Propose the replacement waits for review instead: the operation succeeds with result.status pending_review and result.review_id, the media keeps playing its file until a person approves, and a newer replacement of the same media supersedes the waiting one.


Operation: replaceMedia.

- Authentication: projectBearer OR dashboardSession
- Request body: MediaReplace

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| id | path | required |  |
| If-Match | header | optional | The media item's current revision, quoted or bare. Omitted means any. |
| Idempotency-Key | header | optional | Omit for a fresh operation. Supply and reuse a key to deduplicate retries; the CLI manages keys automatically. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| upload_id | required | string | The id of an upload declared with createMediaUpload and written to its upload_url, not committed. minLength: 1 |
| note | optional | string | A note kept with the new version. maxLength: 280 |

#### Request template

~~~sh
curl --request POST --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/media/RESOURCE_ID/replace'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 202 | The replacement was recorded; the upload's operation carries its outcome. | Operation (application/json) |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### GET /api/media/{id}/versions

**List Media Versions**

Lists the versions of a media item, newest first, the version in service first (current: true). A version is a file the item had: number is its content epoch plus one (1 for the upload, one more for each replacement, restore or media sync change), and its bytes are kept until the version is deleted.

Read a version's bytes with GET /api/media/{id}/content?version=N.


Operation: listMediaVersions.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| id | path | required |  |
| cursor | query | optional | The next_cursor of the previous page. |
| limit | query | optional |  |

#### Request template

~~~sh
curl --request GET --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/media/RESOURCE_ID/versions'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | One page of versions. | MediaVersionPage (application/json) |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### DELETE /api/media/{id}/versions/{version}

**Delete Media Version**

Drops a past version: its record goes, its bytes leave the project's stored bytes at once, and its object is removed after 24 hours unless another version or the version in service holds the same file. The version in service cannot be deleted (resource_conflict), and no version can be deleted while a replacement of the media waits for review.

A deleted version cannot be restored.


Operation: deleteMediaVersion.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| id | path | required |  |
| version | path | required | A version number of the media item (listMediaVersions). |

#### Request template

~~~sh
curl --request DELETE --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/media/RESOURCE_ID/versions/VERSION'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | The version was dropped. | no body |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### POST /api/media/{id}/versions/{version}/restore

**Restore Media Version**

Puts a past version back in service as a new version, in the media's next content epoch: the media keeps its id and revision moves by one, so every playlist and schedule that names it plays that file again without being rescheduled. The version it copies stays in the history and shares its bytes; the new version counts toward stored bytes like any other.

The version in service cannot be restored (resource_conflict). If-Match, when sent, is the media revision the caller read. The body may carry a note. Under effective Propose the restore becomes a review: 202 pending_review, and the media keeps its file until a person approves.


Operation: restoreMediaVersion.

- Authentication: projectBearer OR dashboardSession
- Request body: MediaRestore

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| id | path | required |  |
| version | path | required | A version number of the media item (listMediaVersions). |
| If-Match | header | optional | The media item's current revision, quoted or bare. Omitted means any. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| note | optional | string | A note kept with the new version. maxLength: 280 |

#### Request template

~~~sh
curl --request POST --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/media/RESOURCE_ID/versions/VERSION/restore'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The media as it is now, and warnings about the file put back. | MediaRestored (application/json); headers: ETag |
| 202 | The restore waits for a person's review. | MediaRestorePending (application/json) |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### GET /api/media/{id}/content

**Export Media Content**

Exports the original immutable rendition owned by the authenticated project. The project bearer is charged the normal authenticated API request credit, and completed response bytes are recorded as project_media_export egress. The media identifier is never authority: another project's identifier returns not_found.

The API streams from private storage and never publishes an object key, storage URL, or signed GET. The attachment filename is the media identifier plus the canonical extension for the verified content type. Reading content is never refused for balance.

A dashboard session views the same bytes inline: completed bytes are recorded as dashboard_media egress, and the response is private, no-cache so the browser keeps one copy and revalidates it with If-None-Match. A project bearer response stays private, no-store.


Operation: exportMediaContent.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| id | path | required |  |
| Range | header | optional | V1 accepts one byte range only. |
| If-None-Match | header | optional | Exact strong media SHA-256 ETag returned by this route. |
| version | query | optional | Read this version of the media (listMediaVersions) instead of the one in service. |

#### Request template

~~~sh
curl --request GET --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/media/RESOURCE_ID/content'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Complete original rendition. | string (image/png), string (image/jpeg), string (image/webp), string (image/gif), string (video/mp4), string (video/webm), string (audio/mpeg); headers: Accept-Ranges, Content-Length, Content-Type, Content-Disposition, ETag, Cache-Control, X-Content-Type-Options |
| 206 | One standards-correct byte range. | string (image/png), string (image/jpeg), string (image/webp), string (image/gif), string (video/mp4), string (video/webm), string (audio/mpeg); headers: Accept-Ranges, Content-Range, Content-Length, Content-Type, Content-Disposition, ETag, Cache-Control, X-Content-Type-Options |
| 304 | The exact immutable media ETag matches If-None-Match. No body or egress increment is produced. | no body; headers: ETag, Cache-Control |
| 402 | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |
| 416 | Unsatisfiable or malformed range. | Problem (application/problem+json); headers: Accept-Ranges, Content-Range |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### HEAD /api/media/{id}/content

**Head Media Content**

Applies the same project ownership, conditional, range, and private-storage rules as exportMediaContent without opening or transferring the stored rendition.


Operation: headMediaContent.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| id | path | required |  |
| Range | header | optional | V1 accepts one byte range only. |
| If-None-Match | header | optional | Exact strong media SHA-256 ETag returned by this route. |
| version | query | optional | Read this version of the media (listMediaVersions) instead of the one in service. |

#### Request template

~~~sh
curl --request HEAD --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/media/RESOURCE_ID/content'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Complete-rendition metadata with no body. | no body; headers: Accept-Ranges, Content-Length, Content-Type, Content-Disposition, ETag, Cache-Control, X-Content-Type-Options |
| 206 | Byte-range metadata with no body. | no body; headers: Accept-Ranges, Content-Range, Content-Length, Content-Type, Content-Disposition, ETag, Cache-Control, X-Content-Type-Options |
| 304 | The exact immutable media ETag matches If-None-Match. No body is produced. | no body; headers: ETag, Cache-Control |
| 402 | The 1-credit API meter refused a Standard project's bearer request past its allowance with no credit. A HEAD response carries no problem body. | no body |
| 416 | Unsatisfiable or malformed range. | Problem (application/problem+json); headers: Accept-Ranges, Content-Range |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### GET /api/media-sync-accounts

**List Media Sync Accounts**

Lists the project's media sync accounts, newest first (200 per page; next_cursor continues). Each shows its tenant and client id, its state and its last test; the client secret is never returned, only whether one is set and its last four characters.


Operation: listMediaSyncAccounts.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| cursor | query | optional | The next_cursor of the previous page. |

#### Request template

~~~sh
curl --request GET --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt 'https://api.screenrig.ai/api/media-sync-accounts'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The project's accounts. | MediaSyncAccountList (application/json); headers: Cache-Control |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### POST /api/media-sync-accounts

**Create Media Sync Account**

Adds an account that syncs read through. kind microsoft365 stores the credentials of the project's own Microsoft Entra app, so syncs can read a SharePoint or OneDrive for work folder: config.tenant_id is the tenant's directory id or primary domain (common, organizations and consumers are refused), config.client_id the app's application id, and secret the value of one of the app's client secrets; the app needs the Microsoft Graph application permission Sites.Selected (granted on each site) or Files.Read.All, with admin consent.

Instead of those three, config.connection_id names a Microsoft connection of the project (createConnection), in either mode, which signs in and holds the secret. kind canva takes only a name: the account is pending_consent and the answer carries authorize_url, a one-time link (valid for authorize_expires_at) that the person who owns the Canva account opens, signed in to Canva, to connect it through screenRIG's Canva integration.

The grant belongs to the project, so its syncs outlive the person who connected it.

A server without the Canva integration answers 409 canva_not_configured. kind dropbox works like canva: the account is pending_consent and authorize_url is a one-time link that the person who owns the Dropbox account opens, signed in to Dropbox, to allow read-only access (files.metadata.read, files.content.read, account_info.read, sharing.read).

It goes through screenRIG's Dropbox app, or through the customer's own Dropbox app when config.app_key names it (PKCE, so no app secret; the app must list https://API_ORIGIN/api/dropbox/callback as a redirect URI).

A server without screenRIG's Dropbox app answers 409 dropbox_not_configured to a dropbox account with no app_key. kind google_drive is ready at once and reads the folders shared with config.service_account_email: screenRIG's service account, or the customer's own when secret is that service account's JSON key.

A folder shared with screenRIG's service account syncs only when a member of the project with a verified email shared it or owns it. A server without screenRIG's service account answers 409 google_drive_not_configured to a google_drive account with no key. A secret is sealed at rest and never returned or logged. Appends media_sync_account.created.

An exact retry with the same Idempotency-Key returns the same account (a Canva or Dropbox retry gets a new link).


Operation: createMediaSyncAccount.

- Authentication: projectBearer OR dashboardSession
- Request body: MediaSyncAccountWrite

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| Idempotency-Key | header | optional | Omit for a fresh operation. Supply and reuse a key to deduplicate retries; the CLI manages keys automatically. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| kind | required | string | Allowed: "microsoft365", "canva", "dropbox", "google_drive" |
| name | required | string | minLength: 1 maxLength: 80 |
| config | optional | object | For microsoft365, either tenant_id and client_id (both) with secret, or connection_id alone: the id of a Microsoft connection of this project. For dropbox, app_key optionally names the customer's own Dropbox app. Not accepted for canva or google_drive. |
| secret | optional | string | Required for microsoft365 with tenant_id and client_id: the value of one of the app's client secrets (not its id), at most 1024 characters. For google_drive, optional: the JSON key of the customer's own Google Cloud service account (type service_account), whose client_email becomes the account's service_account_email; without it the account reads through screenRIG's service account. Not accepted with connection_id, nor for canva or dropbox. minLength: 1 maxLength: 8192 |

#### Request template

~~~sh
curl --request POST --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://api.screenrig.ai/api/media-sync-accounts'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Created, with its secret masked; a Canva or Dropbox account also carries authorize_url. | MediaSyncAccountConnect (application/json); headers: ETag, Cache-Control |
| 400 | invalid_request. | Problem (application/problem+json) |
| 402 | feature_not_in_plan — the project's plan does not include media sync; required_plan names the plan that does. | Problem (application/problem+json) |
| 409 | canva_not_configured (this server has no Canva integration; kind canva only), dropbox_not_configured (no screenRIG Dropbox app and no config.app_key; kind dropbox only), google_drive_not_configured (no screenRIG service account and no key; kind google_drive only), resource_conflict or idempotency_mismatch. | Problem (application/problem+json) |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### GET /api/media-sync-accounts/{id}

**Get Media Sync Account**

One media sync account, with its secret masked.


Operation: getMediaSyncAccount.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| id | path | required |  |

#### Request template

~~~sh
curl --request GET --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt 'https://api.screenrig.ai/api/media-sync-accounts/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | One account. | MediaSyncAccount (application/json); headers: ETag, Cache-Control |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### PATCH /api/media-sync-accounts/{id}

**Update Media Sync Account**

Changes the name, tenant, client id or secret; an omitted secret keeps the stored one. A change of credentials discards the last test. A Canva account takes only a name; media sync account connect renews its connection. Appends media_sync_account.updated. If-Match guards the revision.


Operation: updateMediaSyncAccount.

- Authentication: projectBearer OR dashboardSession
- Request body: MediaSyncAccountPatch

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| id | path | required |  |
| If-Match | header | optional | Optional positive resource revision. Omit to write the current resource; a supplied stale revision is revision_conflict. |
| Idempotency-Key | header | optional | Omit for a fresh operation. Supply and reuse a key to deduplicate retries; the CLI manages keys automatically. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| name | optional | string | minLength: 1 maxLength: 80 |
| config | optional | MediaSyncAccountConfig |  |
| secret | optional | string | Replaces the stored secret (a microsoft365 client secret, or a google_drive account's own service-account key, which must belong to the same client_email); omit it to keep the stored one. minLength: 1 maxLength: 8192 |

#### Request template

~~~sh
curl --request PATCH --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://api.screenrig.ai/api/media-sync-accounts/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Updated account. | MediaSyncAccount (application/json); headers: ETag, Cache-Control |
| 400 | invalid_request. | Problem (application/problem+json) |
| 402 | feature_not_in_plan — the project's plan does not include media sync; required_plan names the plan that does. A change that only replaces the secret is always allowed, so an account whose client secret expired stays current. | Problem (application/problem+json) |
| 412 | revision_conflict — If-Match is stale; the error's current_revision carries the current revision. | Problem (application/problem+json) |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### DELETE /api/media-sync-accounts/{id}

**Delete Media Sync Account**

Deletes the account. Refused 409 resource_conflict while a sync reads through it; the detail names those syncs. Deleting a Canva account also revokes its tokens at Canva (a failure to revoke is logged and does not undo the delete). Appends media_sync_account.deleted. If-Match guards the revision.


Operation: deleteMediaSyncAccount.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| id | path | required |  |
| If-Match | header | optional | Optional positive resource revision. Omit to write the current resource; a supplied stale revision is revision_conflict. |
| Idempotency-Key | header | optional | Omit for a fresh operation. Supply and reuse a key to deduplicate retries; the CLI manages keys automatically. |

#### Request template

~~~sh
curl --request DELETE --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt 'https://api.screenrig.ai/api/media-sync-accounts/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | Deleted. | no body |
| 409 | resource_conflict (the account is used by syncs) or idempotency_mismatch. | Problem (application/problem+json) |
| 412 | revision_conflict — If-Match is stale; the error's current_revision carries the current revision. | Problem (application/problem+json) |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### POST /api/media-sync-accounts/{id}/test

**Test Media Sync Account**

Signs in with the account's app and reports what Microsoft granted it: whether the credentials work and which Graph application permissions the token carries (Sites.Selected, Files.Read.All and the like). Sites.Selected is reported with a warning, because each site needs a grant for the app.

For a Canva account it reports whether Canva still accepts the connection. It does not judge a site's grant, a folder or a design, or consent beyond what the token carries. The result is stored as last_test. A failed check is ok false in the answer, not a problem.


Operation: testMediaSyncAccount.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| id | path | required |  |
| Idempotency-Key | header | optional | Omit for a fresh operation. Supply and reuse a key to deduplicate retries; the CLI manages keys automatically. |

#### Request template

~~~sh
curl --request POST --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt 'https://api.screenrig.ai/api/media-sync-accounts/RESOURCE_ID/test'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | What the app's token carries. | MediaSyncTest (application/json); headers: Cache-Control |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### POST /api/media-sync-accounts/{id}/connect

**Connect Media Sync Account**

Makes a new one-time consent link (authorize_url, valid until authorize_expires_at) for a Canva or Dropbox account, to send to the person who owns that account; they open it signed in to Canva or Dropbox and allow access. It replaces any earlier link.

A connected account stays connected until the new consent completes, so this also renews a connection (state reconnect_required) or moves it to another Canva or Dropbox account. The link is a credential of the consent: send it to that person only.

A server without the Canva integration answers 409 canva_not_configured; a Dropbox account with no app_key on a server without screenRIG's Dropbox app answers 409 dropbox_not_configured.


Operation: connectMediaSyncAccount.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| id | path | required |  |
| Idempotency-Key | header | optional | Omit for a fresh operation. Supply and reuse a key to deduplicate retries; the CLI manages keys automatically. |

#### Request template

~~~sh
curl --request POST --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt 'https://api.screenrig.ai/api/media-sync-accounts/RESOURCE_ID/connect'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The account with a new authorize_url. | MediaSyncAccountConnect (application/json); headers: ETag, Cache-Control |
| 400 | invalid_request — the account is not a Canva or Dropbox account. | Problem (application/problem+json) |
| 409 | canva_not_configured (this server has no Canva integration) or dropbox_not_configured (no screenRIG Dropbox app and the account has no app_key). | Problem (application/problem+json) |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### GET /api/media-sync-accounts/{id}/designs

**List Media Sync Designs**

Lists the Canva designs the connected person can see, most recently changed first (50 per page; continuation continues), optionally those matching query. Each shows its id, title, last change, page count and a view address. An account that is not connected is 409 canva_reconnect_required; a server without the Canva integration is 409 canva_not_configured.


Operation: listMediaSyncDesigns.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| id | path | required |  |
| query | query | optional | Lists only the designs whose title or text matches. |
| continuation | query | optional | The continuation of the previous page. |

#### Request template

~~~sh
curl --request GET --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt 'https://api.screenrig.ai/api/media-sync-accounts/RESOURCE_ID/designs'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | One page of designs. | MediaSyncDesignList (application/json); headers: Cache-Control |
| 400 | invalid_request — the account is not a Canva account, or query or continuation is not valid. | Problem (application/problem+json) |
| 409 | canva_reconnect_required (the account is not connected, or Canva no longer honors the connection) or canva_not_configured (this server has no Canva integration). | Problem (application/problem+json) |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### GET /api/canva/callback

**Complete Canva Consent**

Where Canva sends the person after they allow (or refuse) access in a consent link.

It carries no credential: the one-time state in the query finds the Canva account, must be unexpired and is used once, in the same write that stores the connection. screenRIG exchanges the code (PKCE, client authentication with the integration's credentials), reads whose Canva account it is, seals the tokens and appends media_sync_account.connected.

The answer is a short page naming the account and the project it was connected to; an error, an unknown, expired or used state, or a refused code renders the same page with the reason and changes nothing. Answers only on the API host.


Operation: completeCanvaConsent.

- Authentication: none
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| code | query | optional |  |
| state | query | optional |  |
| error | query | optional |  |

#### Request template

~~~sh
curl --request GET 'https://api.screenrig.ai/api/canva/callback'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The page that says the account is connected. | string (text/html) |
| 400 | The page that says why nothing was connected. | string (text/html) |
| 502 | Canva could not be reached; the same link works again. | string (text/html) |
| 409 | Canva is not configured on this server. | string (text/html) |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### GET /api/dropbox/callback

**Complete Dropbox Consent**

Where Dropbox sends the person after they allow (or refuse) access in a consent link.

It carries no credential: the one-time state in the query finds the Dropbox account, must be unexpired and is used once, in the same write that stores the connection. screenRIG exchanges the code (PKCE, with screenRIG's Dropbox app or the account's own app_key), reads whose Dropbox account it is, seals the tokens and appends media_sync_account.connected.

The answer is a short page naming the account and the project it was connected to; an error, an unknown, expired or used state, or a refused code renders the same page with the reason and changes nothing. Answers only on the API host.


Operation: completeDropboxConsent.

- Authentication: none
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| code | query | optional |  |
| state | query | optional |  |
| error | query | optional |  |

#### Request template

~~~sh
curl --request GET 'https://api.screenrig.ai/api/dropbox/callback'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The page that says the account is connected. | string (text/html) |
| 400 | The page that says why nothing was connected. | string (text/html) |
| 502 | Dropbox could not be reached; the same link works again. | string (text/html) |
| 409 | The account has no app_key and screenRIG's Dropbox app is not configured on this server. | string (text/html) |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### GET /api/media-syncs

**List Media Syncs**

Lists the project's media syncs, newest first (200 per page; next_cursor continues), optionally of one account_id or state. Each shows its source folder, the media folder it fills, its cadence, state, next run and the summary of its last run.


Operation: listMediaSyncs.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| cursor | query | optional | The next_cursor of the previous page. |
| account_id | query | optional | Lists only the syncs that read through this account. |
| state | query | optional | Lists only the syncs in this state. |

#### Request template

~~~sh
curl --request GET --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt 'https://api.screenrig.ai/api/media-syncs'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The project's syncs. | MediaSyncList (application/json); headers: Cache-Control |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### POST /api/media-syncs

**Create Media Sync**

provider canva: reads the design (design is its id or its canva.com/design/ID/... address) through a connected Canva account, and stores the sync, due now.

Each run asks Canva for the design's last change and, when it moved, exports the pages (pages, default all; format png gives one image per page and mp4 one video) and imports them into the media folder, replacing the bytes of a page already imported under the same media id so the playlists that use it update; a page the design no longer has leaves play like a removed SharePoint file.

A design Canva no longer shows stops the sync in state error (run error design_unavailable) and keeps the media.

A design or page the account cannot see is 409 media_sync_source_refused. provider dropbox: url is the folder's address in Dropbox (https://www.dropbox.com/home/...), a shared folder link the connected account has added to its Dropbox, or a path starting with /; it is read once through a connected Dropbox account and the sync is stored, due now.

Folders in a team space are read from the team's root. Each run continues from Dropbox's saved position in the folder, so only changes are read.

A folder the account cannot open is 409 media_sync_source_refused; an account whose connection Dropbox no longer honors is 409 dropbox_reconnect_required. provider google_drive: url is the folder's Google Drive address (https://drive.google.com/drive/folders/ID) or its id, shared (Viewer is enough) with the account's service_account_email.

Each run lists the folder and compares each file's checksum. Google Docs, Sheets and Slides files are skipped as unsupported_type. A folder the address cannot open is 409 media_sync_source_refused.

Through screenRIG's service account the folder must be shared or owned by a member of the project with a verified email (403 media_sync_sharer_not_member), and every run checks it again (run error sharer_not_member stops the sync in state error). provider sharepoint: reads the folder's address through the account (a SharePoint site address such as https://TENANT.sharepoint.com/sites/SITE/LIBRARY/FOLDER, or a sharing link; the app must be able to see it: Sites.Selected needs a grant on the site, a sharing link needs Files.Read.All) and stores the sync, due now. folder_id is the media folder the files land in; without it the sync creates one named after the source folder.

Each run imports the images (png, jpeg, webp, gif), videos (mp4, webm) and audio (mp3) directly in the source folder, replaces the bytes of a file that changed under the same media id so playlists that use it update, and takes a file removed at the source out of play: its media is deleted, or, while screens still hold it, moved to a Removed subfolder and deleted later.

Subfolders are ignored; other files are listed as skipped with the reason. every is the seconds between runs, 300 to 86400, default 900. A folder the provider refuses is 409 media_sync_source_refused and one it cannot reach is 500 media_sync_source_unavailable; nothing is stored. Appends media_sync.created.


Operation: createMediaSync.

- Authentication: projectBearer OR dashboardSession
- Request body: MediaSyncWrite

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| Idempotency-Key | header | optional | Omit for a fresh operation. Supply and reuse a key to deduplicate retries; the CLI manages keys automatically. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| provider | required | string | Allowed: "sharepoint", "canva", "dropbox", "google_drive" |
| account_id | required | string | An account of the provider's kind: microsoft365 for sharepoint, canva, dropbox or google_drive. |
| url | optional | string | sharepoint (required): the folder's SharePoint address or sharing link, read once to find the folder and not stored. dropbox (required): the folder's Dropbox address, a shared folder link the account has added, or a path starting with /. google_drive (required): the folder's Google Drive address or its id. maxLength: 4096 |
| design | optional | string | canva (required): the design's id, or its canva.com/design/ID/... address. maxLength: 2048 |
| pages | optional | array | canva: the pages to sync; omit for every page. A design that later has fewer pages takes the missing ones out of play. |
| format | optional | string | canva: png gives one image per page; mp4 one video of the pages. Allowed: "png", "mp4" default: "png" |
| folder_id | optional | string | The media folder the files land in. Omit it to create one named after the source folder. |
| every | optional | integer | Seconds between runs; defaults to 900. minimum: 300 maximum: 86400 |
| name | optional | string | Defaults to the source folder's name. minLength: 1 maxLength: 80 |

#### Request template

~~~sh
curl --request POST --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://api.screenrig.ai/api/media-syncs'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Created, due now. | MediaSync (application/json); headers: ETag, Cache-Control |
| 400 | invalid_request. | Problem (application/problem+json) |
| 402 | feature_not_in_plan — the project's plan does not include media sync; required_plan names the plan that does. | Problem (application/problem+json) |
| 403 | media_sync_sharer_not_member — a Google Drive folder read through screenRIG's service account was not shared or owned by a member of the project with a verified email. | Problem (application/problem+json) |
| 409 | media_sync_source_refused (the provider refused the app, its permission or the folder), dropbox_reconnect_required (the Dropbox account must be connected again), dropbox_not_configured, google_drive_not_configured, resource_conflict (the same source is already synced into that media folder) or idempotency_mismatch. | Problem (application/problem+json) |
| 500 | media_sync_source_unavailable — the provider could not be reached or answered with a failure. | Problem (application/problem+json) |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### GET /api/media-syncs/{id}

**Get Media Sync**

One media sync with the summary of its last run.


Operation: getMediaSync.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| id | path | required |  |

#### Request template

~~~sh
curl --request GET --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt 'https://api.screenrig.ai/api/media-syncs/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | One sync. | MediaSync (application/json); headers: ETag, Cache-Control |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### PATCH /api/media-syncs/{id}

**Update Media Sync**

Changes the name, every (seconds between runs), state (active or paused; active also resumes a sync an error stopped) or folder_id. A new folder_id points the sync at another media folder and makes its next run read the whole source again, which puts every synced file there. Appends media_sync.updated. If-Match guards the revision.


Operation: updateMediaSync.

- Authentication: projectBearer OR dashboardSession
- Request body: MediaSyncPatch

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| id | path | required |  |
| If-Match | header | optional | Optional positive resource revision. Omit to write the current resource; a supplied stale revision is revision_conflict. |
| Idempotency-Key | header | optional | Omit for a fresh operation. Supply and reuse a key to deduplicate retries; the CLI manages keys automatically. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| name | optional | string | minLength: 1 maxLength: 80 |
| every | optional | integer | Seconds between runs. minimum: 300 maximum: 86400 |
| state | optional | string | Allowed: "active", "paused" |
| folder_id | optional | string |  |

#### Request template

~~~sh
curl --request PATCH --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://api.screenrig.ai/api/media-syncs/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Updated sync. | MediaSync (application/json); headers: ETag, Cache-Control |
| 400 | invalid_request. | Problem (application/problem+json) |
| 402 | feature_not_in_plan — the project's plan does not include media sync; required_plan names the plan that does. Pausing a sync is always allowed. | Problem (application/problem+json) |
| 412 | revision_conflict — If-Match is stale; the error's current_revision carries the current revision. | Problem (application/problem+json) |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### DELETE /api/media-syncs/{id}

**Delete Media Sync**

Stops the sync and forgets which source file became which media. The media and its folder stay. Appends media_sync.deleted. If-Match guards the revision.


Operation: deleteMediaSync.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| id | path | required |  |
| If-Match | header | optional | Optional positive resource revision. Omit to write the current resource; a supplied stale revision is revision_conflict. |
| Idempotency-Key | header | optional | Omit for a fresh operation. Supply and reuse a key to deduplicate retries; the CLI manages keys automatically. |

#### Request template

~~~sh
curl --request DELETE --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt 'https://api.screenrig.ai/api/media-syncs/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | Deleted. | no body |
| 412 | revision_conflict — If-Match is stale; the error's current_revision carries the current revision. | Problem (application/problem+json) |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |

### POST /api/media-syncs/{id}/run

**Run Media Sync**

Makes the sync due now and answers 202; a worker runs it within about 30 seconds and the answer of GET /api/media-syncs/{id} shows its last_run.

A sync an error stopped runs again; a paused sync is refused until it is resumed. force=true makes a Canva sync export the design even when Canva reports it unchanged (a SharePoint sync is refused: it already downloads every changed file).


Operation: runMediaSync.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Deprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange. |
| id | path | required |  |
| Idempotency-Key | header | optional | Omit for a fresh operation. Supply and reuse a key to deduplicate retries; the CLI manages keys automatically. |
| force | query | optional | A Canva sync exports the design even when Canva reports it unchanged. |

#### Request template

~~~sh
curl --request POST --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt 'https://api.screenrig.ai/api/media-syncs/RESOURCE_ID/run'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 202 | The sync, due now. | MediaSync (application/json); headers: ETag, Cache-Control |
| 400 | invalid_request. | Problem (application/problem+json) |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |


## Sitemap

See the [public documentation sitemap](https://screenrig.ai/sitemap.md).
