# screenRIG API reference

> Canonical HTML: https://screenrig.ai/docs/api/
> Source SHA-256: 2bb3656bed9281fd3b30a454348a157d8e7a073e09abe6963dab509d550662ee

Find an endpoint, check what it needs, and understand the result. These are the HTTP operations used by the screenRIG plugin, Players, and dashboard. For publishing to a screen, start with the plugin setup and CLI guide.

## Raw contracts

- [OpenAPI YAML](https://screenrig.ai/docs/api/openapi.yaml)
- [OpenAPI JSON](https://screenrig.ai/docs/api/openapi.json)
- [Problem registry YAML](https://screenrig.ai/docs/api/problems.yaml)
- [Problem registry JSON](https://screenrig.ai/docs/api/problems.json)

## Semantics

The endpoint determines the base URL and credential. Request templates use the service URL declared by the contract.

| Task | Service | Authentication |
| --- | --- | --- |
| Manage screens and content | api.screenrig.ai | Agent bearer token |
| Run a Player | play.screenrig.ai | Browser cookies or native Player credentials |
| Manage a login and projects | screenrig.ai/dashboard | Dashboard cookies |
| Load an application release | Its exact host under apps.screenrig.ai | Launch ticket or release cookie |

Native Players use `ScreenRig-Pairing` during setup and `ScreenRig-Session` afterward. These requests must not include cookies. Browser endpoints use their own cookies.

Authentication names in the endpoint reference:

- `projectBearer`: the active agent token used by the bundled CLI, bound to exactly one project.
- `pendingAgentBearer`: a newly collected token that can activate its agent installation.
- `revocableAgentBearer`: the calling agent token, accepted for retrying its own disconnection.
- Capability areas: each agent credential carries a fixed set of areas - `screens`, `content`, `playlists`, `advertising`, `reports`, and `project`. A credential calls only routes in its areas; `reports` also reads any area. A request naming a missing area returns `403 forbidden` with detail "This agent credential lacks the <name> capability." Connect a new agent installation with the needed capabilities to recover.
- `agentConnectionAuthorization`: a temporary ScreenRig-Agent-Connect credential for status and credential collection. It never appears in the browser approval URL.
- Pairing, device, and runtime cookies: browser Player credentials; each endpoint specifies which it accepts.
- Dashboard session cookie: the signed-in person's session and selected project. The separate `__Host-screenrig-signup` cookie binds link-invitation signup, email verification, credential options, and acceptance. Neither cookie is an agent credential. The dashboard does not author playlists.
- `demoSession`: a private website demo session, separate from project and dashboard credentials. The website sets it after intake or a private invitation; flow identifiers never authorize access.
- `primitiveCapability`: short-lived access to one application's key/value storage from a playlist primitive.
- `operatorBearer`: service operator access.

## People, projects, and invitations

A person has one login and can belong to many projects. Enrollment always creates a new project and emails a project-member invitation to its contact address. The agent proposes a project name; each agent token remains bound to that project.

The dashboard lists the person's projects and switches the selected project for cookie-authenticated operations. Personal email and passkeys belong to the login, not the project.

Invitations share one create, list, revoke, and acceptance flow. Project members receive email by default; an explicitly requested member link is returned only to its creator. Ad-buyer invitations are email-only. Email invitations expire after seven days and member links after twenty-four hours. A new person creates a passkey; an existing person signs in before accepting. Link signup verifies the person's email before creating a login.

Sign-in reset is mailbox-only, expires after one hour, and replaces the person's sign-in credentials while revoking their other sessions. Request acknowledgment does not reveal whether an email has a login. Opening the dashboard is navigation, not a grant of authority.

- **Retries:** where an endpoint accepts `Idempotency-Key`, reuse the same key and identical request after a lost response. A different request with that key returns `409 idempotency_mismatch`.
- **Updates:** where an endpoint requires `If-Match`, send the current resource revision. A stale revision returns `412 revision_conflict`.
- **Pagination:** pass list and event cursors back unchanged.
- **Events:** resume with the last cursor or `Last-Event-ID`. On `stream.resync_required`, refetch current state and resume at the supplied head cursor.


## Endpoint inventory

Filter endpoints in the HTML reference, or search this page by path or action. Request templates need your own values and a schema-valid body.

## Service metadata and capabilities

### GET /.health

**Get Health**


Operation: getHealth.

- Authentication: none
- Request body: none

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Alive | HealthResponse (application/json) |

### GET /.ready

**Get Readiness**

Serving readiness. A draining host answers 421 server_draining with Retry-After: 1. A required dependency failure answers 500 not_ready with Retry-After. A live database schema that does not satisfy this release (a desired index missing or different for more than two minutes) answers 500 schema_incompatible without Retry-After: it does not clear by waiting.


Operation: getReadiness.

- Authentication: none
- Request body: none

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Ready | ReadyResponse (application/json) |
| 421 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 500 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### GET /.version

**Get Version**


Operation: getVersion.

- Authentication: none
- Request body: none

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Version | VersionResponse (application/json) |

### GET /api/capabilities

**Get Capabilities**


Operation: getCapabilities.

- Authentication: none
- Request body: none

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Capability and archive limits | Capabilities (application/json) |

## Projects, invitations, and sign-in

### GET /api/project/deletion-preview

**Preview Project Deletion Agent**

Returns current confirmation details, deletion blockers and archived devices that will be disconnected.


Operation: previewProjectDeletionAgent.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| project_id | query | optional | Expected project ID; a changed selection is refused. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Project lifecycle result | ProjectDeletionPreview (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) |

### PUT /api/project/owner

**Transfer Project Owner Agent**

The owner transfers ownership to a verified existing project member; this grants no new membership.


Operation: transferProjectOwnerAgent.

- Authentication: projectBearer OR dashboardSession
- Request body: ProjectOwnerTransfer

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| project_id | optional | string | Expected project ID. The server refuses the action if the current credential or cookie session targets another project; browser clients send the page's project ID. minLength: 1 maxLength: 200 |
| user_id | required | string | minLength: 1 maxLength: 200 |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Project lifecycle result | ProjectContext (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/project/move

**Move Project Agent**

Owner-authorized move transfers screen occupancy and new usage to the destination payer atomically; historical charges keep their payer.


Operation: moveProjectAgent.

- Authentication: projectBearer OR dashboardSession
- Request body: ProjectMove

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| project_id | optional | string | Expected project ID. The server refuses the action if the current credential or cookie session targets another project; browser clients send the page's project ID. minLength: 1 maxLength: 200 |
| organization_id | required | string | minLength: 1 maxLength: 200 |

#### Request template

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.

~~~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/project/move'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Project lifecycle result | ProjectContext (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/project/moves

**Allowed Project Moves Agent**

Lists destinations the owner administers or whose verified domain matches their verified email.


Operation: allowedProjectMovesAgent.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| project_id | query | optional | Expected project ID; a changed selection is refused. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Project lifecycle result | ProjectMoveDestinations (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/projects

**List Agent Projects**

Lists only the identity’s active project memberships, with organization and owner context. Under an agent access JWT, only those its grant was approved for.


Operation: listAgentProjects.

- Authentication: agentIdentityBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/projects'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Project lifecycle result | ProjectContextList (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/projects

**Create Agent Project**

Creates a named project owned by the verified responsible person. With no organization named, it goes to the person's verified-domain organization, else the current project's organization, else the organization the person used most recently among those they administer or hold as their free one, else a new organization.

An organization name selects exactly one visible match, otherwise creates a new organization. A person holds one free Standard organization; a new organization beyond it is free_organization_limit, naming the one they hold. Projects inside an organization are not limited. An exact idempotent retry redelivers its sealed membership credential for ten minutes.

Enrollment is exempt from the limit. Under an agent access JWT (identity scope, access:manage, a grant with identity access Manage) no project token is delivered; the project joins the grant with Manage and every capability, and a refresh with its project_id returns a token for it.


Operation: createAgentProject.

- Authentication: agentIdentityBearer OR dashboardSession
- Request body: ProjectCreate

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| Idempotency-Key | header | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| name | required | ProjectName | Trimmed printable project name. Rejects controls, URL-looking text, @, www., and ://. minLength: 1 maxLength: 60 |
| organization_id | optional | string | minLength: 1 maxLength: 200 |
| organization_name | optional | ProjectName | Trimmed printable project name. Rejects controls, URL-looking text, @, www., and ://. minLength: 1 maxLength: 60 |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Idempotency-Key: REQUEST_ID' --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/projects'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Project lifecycle result | CreatedProject (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/invitations

**List Invitations**

Lists invitation lifecycle records for the current project, excluding sign-in resets. Recipient addresses are visible to the owning project, which supplied them. Tokens and credential URLs are never listed.


Operation: listInvitations.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| kind | query | optional |  |
| status | query | optional |  |
| cursor | query | optional |  |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Lists invitation lifecycle records for the current project, excluding sign-in resets. | InvitationList (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/invitations

**Create Invitations**

Creates member or ad-buyer invitations. Email delivery never returns a credential. Link delivery is member-only and returns one URL, including on exact idempotent replay within 24 hours. Email invitations expire after seven days; links expire after 24 hours. Outstanding email invitations of the same kind, address, and project are reused.


Operation: createInvitations.

- Authentication: projectBearer OR dashboardSession
- Request body: InvitationCreate

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| Idempotency-Key | header | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| kind | required | string | Allowed: "project_member", "ad_buyer" |
| delivery | optional | string | Allowed: "email", "link" default: "email" |
| emails | optional | array |  |
| advertising | optional | InvitationAdvertising | Seller-owned scope: at least one screen or slot. Screens only, slots only, or both are accepted. |

#### Request template

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.

~~~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/invitations'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Creates member or ad-buyer invitations. | InvitationCreated (application/json); headers: 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) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, RateLimit-Policy, RateLimit |

### POST /api/invitations/{id}/revoke

**Revoke Invitation**

Revokes an outstanding invitation. An accepted invitation returns invitation_consumed.


Operation: revokeInvitation.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | Revokes an outstanding invitation. | 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/sign-in-resets

**Request Sign In Reset**

Accepts a sign-in reset request without revealing whether a person holds the address. A live reset is superseded and expires after one hour. With or without a person, the request also re-sends at most ten invitations addressed to the email, and any one invitation at most once an hour.

Of the ten most recent non-deleted projects whose contact address matches, each active project with zero members and a catalog plan gets its enrollment invitation back, even when it expired.

Pending emailed member and ad-buyer invitations to active projects with a catalog plan, and pending organization admin invitations to live organizations, fill the rest, newest first. A re-sent invitation keeps its ID and gets a new link and expiry; the old link stops working and the project's outstanding invitation count is unchanged.

An expired enrollment returns as a new invitation that takes an invitation slot again, so the count rises by one, and it is skipped when the project is full. Other expired invitations, and revoked, accepted and link invitations, are not re-sent.


Operation: requestSignInReset.

- Authentication: none
- Request body: SignInResetRequest

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| Idempotency-Key | header | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| email | required | string | minLength: 3 maxLength: 254 format: "email" |

#### Request template

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.

~~~sh
curl --request POST --header 'Idempotency-Key: REQUEST_ID' --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/sign-in-resets'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 202 | Accepts a sign-in reset request without revealing whether a person holds the address. | SignInResetAccepted (application/json); headers: 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) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, RateLimit-Policy, RateLimit |

### POST /api/enrollments

**Enroll CLI**

Creates a new project and its first agent through explicit bundled-CLI enrollment, even when the contact address already belongs to a person or project. The contact email stays unverified and grants no authority; malformed addresses return invalid_request.

Queues an enrollment invitation in the same transaction. project_name is optional; omission uses a readable two-word name. Exact idempotent replay precedes rate limits. Persist the permanent credential before reporting success; its replay envelope expires at issuance_expires_at and later retries return credential_issuance_expired.

The idempotency record lasts 24 hours.


Operation: enrollCLI.

- Authentication: none
- Request body: CLIEnrollmentRequest

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| Idempotency-Key | header | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| credential_format | optional | string | Must be oauth, which answers the agent JWT pair (CLIEnrollmentOAuth). legacy, or omitting it, answers 410 client_update_required, because the legacy permanent bearers are retired. A server that is not issuing agent JWTs answers not_ready. Allowed: "oauth", "legacy" |
| intent | optional | string | Optional enrollment purpose. Omit or signage keeps screens=true, advertiser=false. advertising sets advertiser=true, screens=false, and never changes the billing-plan assignment. Allowed: "signage", "advertising" |
| client_id | required | string | Stable anonymous installation identity generated from 32 CSPRNG bytes and persisted before the first request. It is rate-limit material, not a project credential. pattern: "^cli_[A-Za-z0-9_-]{43}$" |
| email | optional | string | Unverified project contact address. Exactly one of email or agentid_claim is required. Enrollment always creates a new project and queues its invitation; an address alone grants no authority. minLength: 3 maxLength: 254 format: "email" |
| agentid_claim | optional | string | Single-use AgentID claim code from the AgentID sign-in page. Exactly one of email or agentid_claim is required. The verified AgentID owner address becomes the project contact and invitation recipient. pattern: "^agid_[A-Za-z0-9_-]{43}$" |
| beta_key | optional | string | Required when the server has a configured enrollment beta key. Omit when the server does not gate enrollment. |
| name | optional | string | default: "Agent" minLength: 1 maxLength: 80 |
| agent_type | optional | string | default: "cli" pattern: "^[A-Za-z0-9][A-Za-z0-9._-]{0,31}$" |
| platform | optional | string | maxLength: 80 |
| version | optional | string | maxLength: 40 |
| project_name | optional | ProjectName | Trimmed printable project name. Rejects controls, URL-looking text, @, www., and ://. minLength: 1 maxLength: 60 |
| organization | optional | ProjectName | Trimmed printable project name. Rejects controls, URL-looking text, @, www., and ://. minLength: 1 maxLength: 60 |

#### Request template

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.

~~~sh
curl --request POST --header 'Idempotency-Key: REQUEST_ID' --header 'Content-Type: application/json' --data '@request.json' 'https://api.screenrig.ai/api/enrollments'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | New project and permanent bearer credential. The project carries the unverified contact address in its trimmed supplied form. Persist and verify the token before reporting enrollment success. The credential has no scheduled expiry but its encrypted exact-retry delivery envelope exists only until issuance_expires_at. | CLIEnrollmentOAuth (application/json); headers: Cache-Control, RateLimit-Policy, RateLimit |
| 400 | 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) |
| 409 | 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) |
| 410 | 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) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, RateLimit-Policy, RateLimit |
| 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 /agentid/start

**Start Agent IDSign In**

Starts an AgentID (OIDC) browser sign-in on the API host derived from the runtime public origin (https://play.<domain> -> https://api.<domain>). Sets a single-use binding cookie and redirects to the AgentID authorization endpoint with PKCE S256. Answers only on the API host.

While the relying party has no client id and secret the route renders a plain unavailable page with 503.


Operation: startAgentIDSignIn.

- Authentication: none
- Request body: none

#### Request template

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.

~~~sh
curl --request GET 'https://api.screenrig.ai/agentid/start'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 302 | Redirect to the AgentID authorization endpoint. | no body |
| 503 | AgentID sign-in is unavailable. | 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 /agentid/callback

**Complete Agent IDSign In**

Completes an AgentID browser sign-in on the API host. Requires iss equal to the configured issuer, the binding cookie, and an unexpired single-use transaction.

Exchanges the code with client_secret_basic and PKCE, verifies the ES256 id_token (iss, aud, exp with 60 s skew, nonce) against the cached JWKS, calls userinfo and enforces the owner rules, then renders a single-use claim-code page. A subject already bound to an agent renders the already-enrolled page.

This route never creates a project, identity, or invitation.


Operation: completeAgentIDSignIn.

- Authentication: none
- Request body: none

#### Parameters

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

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Single-use AgentID claim-code page with plugin install steps. | string (text/html) |
| 409 | The AgentID subject is already enrolled. | string (text/html) |
| 503 | AgentID sign-in is unavailable. | 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/project

**Get Project**

The calling project. Does not debit the 1-credit API meter, so a zero-remaining project can still read remaining and recover. After a successful project principal, authenticated /api JSON and problem responses include ScreenRig-Credits-Remaining, ScreenRig-Credits-Reset, and ScreenRig-Credits-Included as whole credits.

SSE, runtime, content, and unauthenticated responses omit them.


Operation: getProject.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Project | Project (application/json); headers: ScreenRig-Credits-Remaining, ScreenRig-Credits-Reset, ScreenRig-Credits-Included, ScreenRig-Credits-Low |
| 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/project

**Update Project**

Renames the current project. Names contain 1–60 printable characters and no URL-looking text.


Operation: updateProject.

- Authentication: projectBearer OR dashboardSession
- Request body: ProjectPatch

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| project_id | optional | string | Expected project ID. The server refuses the action if the current credential or cookie session targets another project; browser clients send the page's project ID. minLength: 1 maxLength: 200 |
| name | required | ProjectName | Trimmed printable project name. Rejects controls, URL-looking text, @, www., and ://. minLength: 1 maxLength: 60 |

#### Request template

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.

~~~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/project'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Renames the current project. | Project (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/project

**Delete Project Agent**

Requires the target membership project capability, the current name and revision, zero non-archived screens, no credit, reservations or debt in its own wallet and no live advertising. A negative balance run up under free or launch terms does not block; deletion writes it off with a project_delete_writeoff ledger entry.

Queues member notices and uses the irreversible tombstone.


Operation: deleteProjectAgent.

- Authentication: projectBearer OR dashboardSession
- Request body: ProjectDeleteConfirmation

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| project_id | optional | string | Expected project ID. The server refuses the action if the current credential or cookie session targets another project; browser clients send the page's project ID. minLength: 1 maxLength: 200 |
| empty_only | optional | boolean | Require an empty Screens project in the deletion transaction, including content, other agents or members, work and correspondence. The CLI additionally requires its saved enrollment provenance before using this cleanup guard. Omission keeps explicit member deletion. |
| name | required | ProjectName | Trimmed printable project name. Rejects controls, URL-looking text, @, www., and ://. minLength: 1 maxLength: 60 |
| revision | required | integer | minimum: 1 |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Project lifecycle result | DeletedProject (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/project/capabilities

**Get Project Capabilities**

The calling project's plan, independent advertiser/screens flags with their revision, and the derived effective capability set. Never reflects a client claim; an unknown plan fails closed as forbidden.


Operation: getProjectCapabilities.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Project capabilities. | ProjectCapabilities (application/json); headers: Cache-Control |
| 403 | forbidden - the project plan is not in the catalog. | 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/organizations/{org_id}/billing/balance

**Get Organization Billing Balance**

The organization wallet. Requires a designated organization admin or staff session. Does not expose a reseller wallet or grant project membership.


Operation: getOrganizationBillingBalance.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| org_id | path | required |  |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/organizations/ORG_ID/billing/balance'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Organization billing balance. | BillingBalance (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) |

### GET /api/organizations/{org_id}/billing/statement

**Get Organization Billing Statement**

Journal entries attributed to this organization at charge time, with project and payer identities. Moves do not transfer historical access. Entries predating recorded organization context remain in their project statements.


Operation: getOrganizationBillingStatement.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| org_id | path | required |  |
| cursor | query | optional |  |
| limit | query | optional |  |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/organizations/ORG_ID/billing/statement'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Organization billing statement. | BillingStatement (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) |

### GET /api/organizations/{org_id}/billing/usage

**Get Organization Billing Usage**

The organization allowance pool and each project share for a UTC month. Covers usage paid by this organization; other payer costs are in the organization statement.


Operation: getOrganizationBillingUsage.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| org_id | path | required |  |
| cursor | query | optional |  |
| limit | query | optional |  |
| period | query | optional | UTC month in YYYY-MM form; defaults to the current month. |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/organizations/ORG_ID/billing/usage'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Organization billing usage. | OrganizationBillingUsage (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) |

### GET /api/organizations

**Agent Organizations List**

Visible organizations, including empty organizations for their admins. Names are not unique.


Operation: agentOrganizationsList.

- Authentication: agentIdentityBearer OR dashboardSession
- Request body: none

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://api.screenrig.ai/api/organizations'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Organization result. | OrganizationList (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) |

### PATCH /api/organizations/{org_id}

**Agent Organizations Rename**

Designated admins and agents whose verified responsible person is an admin may rename an organization. No project permission or membership is granted.


Operation: agentOrganizationsRename.

- Authentication: agentIdentityBearer OR dashboardSession
- Request body: OrganizationRename

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| org_id | path | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| name | required | ProjectName | Trimmed printable project name. Rejects controls, URL-looking text, @, www., and ://. minLength: 1 maxLength: 60 |

#### Request template

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.

~~~sh
curl --request PATCH --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://api.screenrig.ai/api/organizations/ORG_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Organization result. | OrganizationView (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) |

### PUT /api/project/review-policy

**Set Project Review Policy**

Turns Require review for agent changes on or off. While on, every agent and service client on the project is held to Propose access on every request, whatever its credential carries. The project owner or an admin of the project's organization only (forbidden otherwise); staff shadow sessions cannot change it.

If-Match carries review_policy_revision (0 allowed) and is required; a stale value is revision_conflict. Turning it off requires proof, the fresh passkey or password proof agent connection approval uses; turning it on does not. Open reviews stay decidable either way. Appends project.review_policy_changed.


Operation: setProjectReviewPolicy.

- Authentication: dashboardSession
- Request body: ReviewPolicyUpdate

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| If-Match | header | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| agent_changes_require_review | required | boolean |  |
| proof | optional | DashboardProof |  |

#### Request template

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.

~~~sh
curl --request PUT --cookie cookies.txt --cookie-jar cookies.txt --header 'If-Match: "REVISION"' --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/project/review-policy'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The policy now in force. | ReviewPolicy (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) |

## Credit balances and statements

### GET /api/billing/balance

**Get Billing Balance**

The calling project's source-aware balance snapshot. All millicredit amounts are canonical decimal strings. It is a snapshot, not authority to pay; rails_available is false while card, tax and cash-out rail are unavailable.


Operation: getBillingBalance.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Balance snapshot. | BillingBalance (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/billing/statement

**Get Billing Statement**

Stable cursor pagination over immutable posted journal entries for the calling project. Holds are not posted entries. Corrections link to the original event instead of rewriting it. There is no arbitrary grant or payout mutation on this surface.


Operation: getBillingStatement.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| cursor | query | optional |  |
| limit | query | optional |  |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Statement page. | BillingStatement (application/json) |
| 400 | invalid_request - limit is out of range. | 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/billing/usage

**Get Billing Usage**

The calling project's use of its monthly allowance (10 GB bandwidth, 1 GB-month storage, and 10,000 API requests, billed project stream events and webhook delivery attempts) for one UTC month. A project paying for itself reads its own pool.

A project another payer pays for shares that payer's pool with the payer's other projects; it reads its own share, and payer names the payer. The payer's pool total stays on the payer's billing reads.


Operation: getBillingUsage.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| period | query | optional | UTC month in YYYY-MM form; defaults to the current month. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Project allowance usage. | ProjectBillingUsage (application/json); headers: Cache-Control |
| 400 | invalid_request - period is not a UTC month in YYYY-MM form. | 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) |

## Agent installations and connections

### POST /api/agent-identity/exchange

**Exchange Agent Identity**

Retired with the legacy agent credentials (project tokens and identity credentials). Always answers 401 credential_retired with next screenrig login; an installation signs in with the device grant (POST /oauth/device_authorization).


Operation: exchangeAgentIdentity.

- Authentication: projectBearer
- Request body: none

#### Request template

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.

~~~sh
curl --request POST --header 'Authorization: Bearer [SCREENRIG_TOKEN]' 'https://api.screenrig.ai/api/agent-identity/exchange'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 401 | 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) |

### POST /api/agent-identity/revoke

**Revoke Agent Identity**

Revokes the global identity, all its identity credentials and project tokens, and its pending connections. An exact replay with a revoked identity credential succeeds without another transition. Project ownership and content remain. No cookie, Origin, query or body is accepted. This route does not debit the API meter.

An agent access JWT carrying the identity scope, whose grant holds identity access (Read only, Propose or Manage, whatever the access level of the token's project), revokes the same and every OAuth grant of the identity; its tokens are refused within seconds. A retry with the same JWT after the revocation is a no-op success.


Operation: revokeAgentIdentity.

- Authentication: agentIdentityBearer
- Request body: none

#### Request template

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.

~~~sh
curl --request POST 'https://api.screenrig.ai/api/agent-identity/revoke'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | Identity revoked. | no body; 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/agent-connections

**Start Agent Connection**

Retired with the legacy agent credentials. Always answers 410 client_update_required; an agent signs in with the device grant (POST /oauth/device_authorization, screenrig login).


Operation: startAgentConnection.

- Authentication: none OR agentIdentityBearer
- Request body: none

#### Request template

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.

~~~sh
curl --request POST 'https://api.screenrig.ai/api/agent-connections'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 410 | 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 /stream/agent-connections/{id}

**Stream Agent Connection Events**

Retired with the legacy agent connection. Always answers 410 client_update_required.


Operation: streamAgentConnectionEvents.

- Authentication: agentConnectionAuthorization
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |

#### Request template

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.

~~~sh
curl --request GET --header 'Authorization: ScreenRig-Agent-Connect [SCREENRIG_AGENT_CONNECTION_TOKEN]' 'https://api.screenrig.ai/stream/agent-connections/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 410 | 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) |

### POST /api/agent-connections/{id}/credential

**Collect Agent Credential**

Retired with the legacy agent connection. Always answers 410 client_update_required.


Operation: collectAgentCredential.

- Authentication: agentConnectionAuthorization
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |

#### Request template

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.

~~~sh
curl --request POST --header 'Authorization: ScreenRig-Agent-Connect [SCREENRIG_AGENT_CONNECTION_TOKEN]' 'https://api.screenrig.ai/api/agent-connections/RESOURCE_ID/credential'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 410 | 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) |

### POST /api/agent-connections/{id}/cancel

**Cancel Agent Connection**

Retired with the legacy agent connection. Always answers 410 client_update_required.


Operation: cancelAgentConnection.

- Authentication: agentConnectionAuthorization
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |

#### Request template

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.

~~~sh
curl --request POST --header 'Authorization: ScreenRig-Agent-Connect [SCREENRIG_AGENT_CONNECTION_TOKEN]' 'https://api.screenrig.ai/api/agent-connections/RESOURCE_ID/cancel'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 410 | 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) |

### POST /api/agents/self/activate

**Activate Current Agent**

Retired with the legacy project tokens, so no pending bearer is issued any more. Always answers 401 credential_retired with next screenrig login.


Operation: activateCurrentAgent.

- Authentication: pendingAgentBearer
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |

#### Request template

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.

~~~sh
curl --request POST --header 'Authorization: Bearer [SCREENRIG_TOKEN]' 'https://api.screenrig.ai/api/agents/self/activate'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 401 | 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/agents/self

**Get Current Agent**

Returns the agent installation linked to the calling project bearer plus connection_ready. A project is ready when at least one member has a passkey or password. Identity status is diagnostic and does not debit the API meter.


Operation: getCurrentAgent.

- Authentication: projectBearer
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |

#### Request template

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.

~~~sh
curl --request GET --header 'Authorization: Bearer [SCREENRIG_TOKEN]' 'https://api.screenrig.ai/api/agents/self'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Current agent and project connection readiness. | AgentSelfStatus (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/agents/self/disconnect

**Disconnect Current Agent**

Revokes only the calling project membership and its project token, preserving the global identity, other memberships, project and content. Repeating with the same cryptographically valid revoked bearer is a no-op success. Disconnecting the last active agent returns agent_lockout_risk unless allow_last_agent is explicit. Does not debit the API meter.

An agent access JWT, Read only included, disconnects the agent from its token's project; the membership ends, the project's consent leaves the agent's grants and every access token of the agent is refused within seconds (refresh for the projects that remain). A retry with the same JWT after the disconnect is a no-op success.


Operation: disconnectCurrentAgent.

- Authentication: revocableAgentBearer
- Request body: AgentDisconnectRequest

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| allow_last_agent | optional | boolean | Explicitly accept loss of the last active agent bearer. A registered dashboard passkey remains independent administrative authority. default: false |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | Agent disconnected. | no body; headers: Cache-Control |
| 401 | 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) |
| 409 | 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) |

## OAuth sign-in, tokens and revocation

### POST /oauth/token

**Oauth Token**

OAuth 2.0 token endpoint (RFC 6749 §3.2) for agent JWTs. grant_type refresh_token rotates a refresh JWT (RFC 9700): every use returns a new pair; a request_id (16 to 200 characters) makes a retry within 60 seconds return the same response, and any other reuse of a superseded refresh token revokes the whole family (invalid_grant). grant_type urn:ietf:params:oauth:grant-type:token-exchange (RFC 8693) with subject_token_type urn:screenrig:token-type:identity-credential or urn:screenrig:token-type:project-token answers unsupported_token_type: legacy credentials are retired, and the installation signs in again with screenrig login. resource (RFC 8707) is https://api.<domain>/api or absent; project_id selects the project of a multi-project grant.

A connected app's grant has the single resource https://api.<domain>/mcp, which a refresh may omit.

Clients are public (client_id only), or a connected app whose metadata declares private_key_jwt authenticates with client_assertion_type urn:ietf:params:oauth:client-assertion-type:jwt-bearer and a client_assertion (iss and sub its client_id, aud the issuer or this endpoint as one string, exp within 5 minutes of iat, a jti used once, kid naming one of its keys; EdDSA, ES256 or RS256). grant_type authorization_code redeems a connected app's code within 60 seconds with the exact redirect_uri and the PKCE code_verifier; resource must be absent or the one authorized; a second redemption revokes the grant the code created. grant_type client_credentials is a service client's: it authenticates with a client assertion (one of its registered keys) or its secret by HTTP Basic (client_secret_basic; a secret in the body is refused) and receives a 15-minute service token for its project, audience api or (resource https://api.<domain>/mcp) mcp, scope within its capabilities and access level, and no refresh token; 600 per client per hour.

Errors are RFC 6749 JSON {error, error_description} with Cache-Control no-store; while the server does not issue it answers temporarily_unavailable (503).

Rate limits answer 429 rate_limited with Retry-After: 1200 requests per client address per hour on this and the revocation endpoint, 60 refreshes per grant per hour, 30 exchanges per legacy credential per hour. grant_type urn:ietf:params:oauth:grant-type:device_code (RFC 8628 §3.4) polls a device sign-in: authorization_pending until a person approves it, slow_down when polled sooner than the interval (which then grows by 5 seconds), access_denied when denied, expired_token after 10 minutes, then the token response.

A new installation receives a token pair; a sign-in started with an existing installation's access token adds the approved project to that grant and receives an access token only, without rotating the refresh token. Polling the same device_code again returns the same response until the grant first rotates or 10 minutes pass, then invalid_grant.

POST only, form-encoded, no query string.


Operation: oauthToken.

- Authentication: none
- Request body: object

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| grant_type | required | string | Allowed: "authorization_code", "client_credentials", "refresh_token", "urn:ietf:params:oauth:grant-type:token-exchange", "urn:ietf:params:oauth:grant-type:device_code" |
| client_id | optional | string |  |
| client_assertion_type | optional | string | Allowed: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" |
| client_assertion | optional | string |  |
| code | optional | string |  |
| redirect_uri | optional | string |  |
| code_verifier | optional | string | minLength: 43 maxLength: 128 |
| refresh_token | optional | string |  |
| device_code | optional | string |  |
| request_id | optional | string | minLength: 16 maxLength: 200 |
| subject_token | optional | string |  |
| subject_token_type | optional | string | Allowed: "urn:screenrig:token-type:identity-credential", "urn:screenrig:token-type:project-token" |
| scope | optional | string |  |
| resource | optional | string | format: "uri" |
| project_id | optional | string |  |

#### Request template

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.

~~~sh
curl --request POST --header 'Content-Type: application/x-www-form-urlencoded' --data-binary '@request.bin' 'https://api.screenrig.ai/oauth/token'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The token response (RFC 6749 §5.1). | OAuthTokenResponse (application/json) |
| default | An RFC 6749 §5.2 error. | OAuthError (application/json) |

### POST /oauth/device_authorization

**Oauth Device Authorization**

RFC 8628 §3.1 device authorization for `screenrig login`. scope is access:manage (the default), access:propose or access:read; project_id suggests the project the person approves; name, agent_type, platform and version describe the installation and are shown to the approver as reported by the device. Without Authorization it starts a new installation.

With Authorization: Bearer and an agent access token carrying identity scope it asks to add a project to that installation's grant, or to raise a Read only project to Manage.

The person enters user_code at verification_uri (or follows verification_uri_complete) and approves it in the dashboard; the device polls the token endpoint with the device_code every interval seconds.

Rate limits answer 429 rate_limited with Retry-After: 10 per client address per hour and 600 per hour fleet-wide, behind the endpoints' 1200 per address per hour. Errors are RFC 6749 JSON with Cache-Control no-store; while the server does not issue it answers temporarily_unavailable (503). POST only, form-encoded, no query string.


Operation: oauthDeviceAuthorization.

- Authentication: none
- Request body: object

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| client_id | required | string |  |
| scope | optional | string | Allowed: "access:manage", "access:propose", "access:read" |
| project_id | optional | string | maxLength: 200 |
| name | optional | string | maxLength: 80 |
| agent_type | optional | string | maxLength: 32 |
| platform | optional | string | maxLength: 80 |
| version | optional | string | maxLength: 40 |

#### Request template

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.

~~~sh
curl --request POST --header 'Content-Type: application/x-www-form-urlencoded' --data-binary '@request.bin' 'https://api.screenrig.ai/oauth/device_authorization'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The device authorization response (RFC 8628 §3.2). | object (application/json) |
| default | An RFC 6749 §5.2 error. | OAuthError (application/json) |

### GET /oauth/authorize

**Oauth Authorize**

OAuth 2.0 authorization endpoint for connected apps (MCP clients). client_id is a client ID metadata document URL (https, port 443, fetched with no redirects, 64 KiB and 5 seconds at most, public addresses only) or a registered client id.

The request is validated in order: the client, the exact redirect_uri (an http loopback redirect of 127.0.0.1, [::1] or localhost matches on any port), response_type code, code_challenge with code_challenge_method S256, resource (https://api.<domain>/mcp, or absent), then scope (access:read, the default, access:propose, access:manage and capability names).

An unknown client or redirect_uri answers an HTML error page and never redirects; any later error redirects to the redirect_uri with error, error_description, state and iss (RFC 9207).

Success answers 303 to the dashboard consent page, where a person chooses the project, the access level (Read only by default, Propose or Manage) and the capabilities; approval redirects with code, state and iss. 30 requests per client address per minute.


Operation: oauthAuthorize.

- Authentication: none
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| client_id | query | required |  |
| redirect_uri | query | required |  |
| response_type | query | required |  |
| code_challenge | query | required |  |
| code_challenge_method | query | required |  |
| state | query | optional |  |
| resource | query | optional |  |
| scope | query | optional |  |

#### Request template

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.

~~~sh
curl --request GET 'https://api.screenrig.ai/oauth/authorize?client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&response_type=RESPONSE_TYPE&code_challenge=CODE_CHALLENGE&code_challenge_method=CODE_CHALLENGE_METHOD'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 303 | To the consent page, or back to the client's redirect_uri with error, state and iss. | no body |
| 400 | An HTML error page for an unknown client or redirect_uri; never a redirect. | string (text/html) |
| 429 | Too many authorization requests from this address. | no body |

### POST /oauth/revoke

**Oauth Revoke**

RFC 7009 revocation. A refresh JWT, or an agent access JWT, issued to the client revokes its grant; the grant's access tokens are refused within seconds. An unknown, expired or foreign token also answers 200. POST only, form-encoded.


Operation: oauthRevoke.

- Authentication: none
- Request body: object

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| client_id | optional | string |  |
| client_assertion_type | optional | string | Allowed: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" |
| client_assertion | optional | string |  |
| token | required | string |  |
| token_type_hint | optional | string |  |

#### Request template

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.

~~~sh
curl --request POST --header 'Content-Type: application/x-www-form-urlencoded' --data-binary '@request.bin' 'https://api.screenrig.ai/oauth/revoke'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Revoked, or nothing to revoke. | no body |
| default | An RFC 6749 §5.2 error. | OAuthError (application/json) |

### GET /.well-known/oauth-authorization-server

**Oauth Authorization Server Metadata**

RFC 8414 authorization server metadata for the agent authorization server.


Operation: oauthAuthorizationServerMetadata.

- Authentication: none
- Request body: none

#### Request template

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.

~~~sh
curl --request GET 'https://api.screenrig.ai/.well-known/oauth-authorization-server'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The metadata document. | object (application/json) |

### GET /.well-known/oauth-protected-resource/mcp

**Oauth Protected Resource MCP**

RFC 9728 protected resource metadata for /mcp, named by the resource_metadata of every /mcp 401.


Operation: oauthProtectedResourceMCP.

- Authentication: none
- Request body: none

#### Request template

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.

~~~sh
curl --request GET 'https://api.screenrig.ai/.well-known/oauth-protected-resource/mcp'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The metadata document. | object (application/json) |

### GET /.well-known/oauth-protected-resource/api

**Oauth Protected Resource API**

RFC 9728 protected resource metadata for /api.


Operation: oauthProtectedResourceAPI.

- Authentication: none
- Request body: none

#### Request template

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.

~~~sh
curl --request GET 'https://api.screenrig.ai/.well-known/oauth-protected-resource/api'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The metadata document. | object (application/json) |

### GET /api/oauth/authorizations/{id}

**Get Dashboard OAuth Authorization**

A pending connected-app authorization for the consent page: the client's name and domain as its metadata declares them (no logo is ever fetched), whether it is a registered client, the redirect host, and the access level and capabilities it asked for. Unknown, expired or settled authorizations are not_found.


Operation: getDashboardOAuthAuthorization.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/oauth/authorizations/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The authorization. | OAuthAuthorization (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/oauth/authorizations/{id}/approve

**Approve Dashboard OAuth Authorization**

Approves a connected app for one of the person's projects after a fresh proof (kind oauth_authorization_approval, subject the authorization id): it creates the app's agent identity (agent type mcp_client, the person responsible), its membership and its grant with the chosen access (read or manage; omitted keeps the requested level, which defaults to read) and capabilities (a subset of the requested), and answers the redirect_uri, carrying code, state and iss, that the page navigates to.

The code is valid for 60 seconds.


Operation: approveDashboardOAuthAuthorization.

- Authentication: dashboardSession
- Request body: DashboardAgentAction

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| project_id | required | string | minLength: 1 |
| proof | required | DashboardProof |  |
| capabilities | optional | array | Optional on agent connection and connected-app approval only. Omitted approves the full requested set; a non-empty subset of the connection's capabilities narrows the granted credential; anything else is invalid_request. |
| user_code | optional | string | Required when approving a device sign-in (flow device): the code the person confirms matches the terminal. A mismatch is agent_connection_code_mismatch; the fifth expires the sign-in (agent_connection_expired). Ignored otherwise. maxLength: 32 |
| access | optional | string | The access level a device sign-in or a connected app is approved with (Read only, Propose or Manage); omitted keeps the level asked for. Ignored for other connections. A person who cannot approve reviews may not approve Manage (403 approver_required). Allowed: "read", "propose", "manage" |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/oauth/authorizations/RESOURCE_ID/approve'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The redirect back to the application. | OAuthAuthorizationRedirect (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/oauth/authorizations/{id}/deny

**Deny Dashboard OAuth Authorization**

Denies a connected app after a fresh proof (kind oauth_authorization_denial) and answers the redirect_uri, carrying error=access_denied, state and iss, that the page navigates to.


Operation: denyDashboardOAuthAuthorization.

- Authentication: dashboardSession
- Request body: DashboardAgentAction

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| project_id | required | string | minLength: 1 |
| proof | required | DashboardProof |  |
| capabilities | optional | array | Optional on agent connection and connected-app approval only. Omitted approves the full requested set; a non-empty subset of the connection's capabilities narrows the granted credential; anything else is invalid_request. |
| user_code | optional | string | Required when approving a device sign-in (flow device): the code the person confirms matches the terminal. A mismatch is agent_connection_code_mismatch; the fifth expires the sign-in (agent_connection_expired). Ignored otherwise. maxLength: 32 |
| access | optional | string | The access level a device sign-in or a connected app is approved with (Read only, Propose or Manage); omitted keeps the level asked for. Ignored for other connections. A person who cannot approve reviews may not approve Manage (403 approver_required). Allowed: "read", "propose", "manage" |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/oauth/authorizations/RESOURCE_ID/deny'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The redirect back to the application. | OAuthAuthorizationRedirect (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) |

## Service clients

### GET /api/service-clients

**List Service Clients**

The project's service clients (spec: a customer's server, script or CI job owned by the project), newest first, live and revoked, with their public keys, secret ids and usage; never a secret or its digest. Needs the project capability.


Operation: listServiceClients.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The service client. | ServiceClientList (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/service-clients

**Create Service Client**

Creates a service client owned by the project with a name, capabilities (omitted means all six; each must be one the manager holds, else 403), access (read, the default, or manage), and a public JWK key (OKP Ed25519, EC P-256 or RSA of at least 2048 bits) and/or secret true. The secret is returned once, here only. At most 50 live clients per project.

Agents need the project capability and Manage; a person uses the dashboard session with project_id and a fresh proof (kind service_client_change, subject the client id, or the project id when creating) in the body. A service token is refused 403.


Operation: createServiceClient.

- Authentication: projectBearer OR dashboardSession
- Request body: ServiceClientChange

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| project_id | optional | string | Dashboard session only. |
| proof | optional | DashboardProof |  |
| name | optional | string | minLength: 1 maxLength: 80 |
| capabilities | optional | array |  |
| access | optional | string | Manage needs a person who can approve reviews (403 approver_required). Allowed: "read", "propose", "manage" |
| key | optional | object | A public JWK: OKP Ed25519, EC P-256 or RSA of at least 2048 bits. |
| secret | optional | boolean | Generate a secret (creation only). |

#### Request template

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.

~~~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/service-clients'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | The service client. | ServiceClientResult (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/service-clients/{id}

**Get Service Client**

One of the project's service clients. Needs the project capability.


Operation: getServiceClient.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The service client. | ServiceClient (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) |

### PATCH /api/service-clients/{id}

**Update Service Client**

Renames a service client or changes its capabilities (within the manager's) or access. Narrowing either ends the client's current tokens within seconds.

Agents need the project capability and Manage; a person uses the dashboard session with project_id and a fresh proof (kind service_client_change, subject the client id, or the project id when creating) in the body. A service token is refused 403.


Operation: updateServiceClient.

- Authentication: projectBearer OR dashboardSession
- Request body: ServiceClientChange

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| project_id | optional | string | Dashboard session only. |
| proof | optional | DashboardProof |  |
| name | optional | string | minLength: 1 maxLength: 80 |
| capabilities | optional | array |  |
| access | optional | string | Manage needs a person who can approve reviews (403 approver_required). Allowed: "read", "propose", "manage" |
| key | optional | object | A public JWK: OKP Ed25519, EC P-256 or RSA of at least 2048 bits. |
| secret | optional | boolean | Generate a secret (creation only). |

#### Request template

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.

~~~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/service-clients/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The service client. | ServiceClientResult (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/service-clients/{id}/keys

**Add Service Client Key**

Registers a second public JWK (key in the body) for a zero-downtime rotation; a client holds at most two. Agents need the project capability and Manage; a person uses the dashboard session with project_id and a fresh proof (kind service_client_change, subject the client id, or the project id when creating) in the body. A service token is refused 403.


Operation: addServiceClientKey.

- Authentication: projectBearer OR dashboardSession
- Request body: ServiceClientChange

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| project_id | optional | string | Dashboard session only. |
| proof | optional | DashboardProof |  |
| name | optional | string | minLength: 1 maxLength: 80 |
| capabilities | optional | array |  |
| access | optional | string | Manage needs a person who can approve reviews (403 approver_required). Allowed: "read", "propose", "manage" |
| key | optional | object | A public JWK: OKP Ed25519, EC P-256 or RSA of at least 2048 bits. |
| secret | optional | boolean | Generate a secret (creation only). |

#### Request template

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.

~~~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/service-clients/RESOURCE_ID/keys'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The service client. | ServiceClientResult (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/service-clients/{id}/keys/{kid}/remove

**Remove Service Client Key**

Removes a key; the client's current tokens end within seconds. A client keeps at least one key or secret. Agents need the project capability and Manage; a person uses the dashboard session with project_id and a fresh proof (kind service_client_change, subject the client id, or the project id when creating) in the body. A service token is refused 403.


Operation: removeServiceClientKey.

- Authentication: projectBearer OR dashboardSession
- Request body: ServiceClientChange

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |
| kid | path | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| project_id | optional | string | Dashboard session only. |
| proof | optional | DashboardProof |  |
| name | optional | string | minLength: 1 maxLength: 80 |
| capabilities | optional | array |  |
| access | optional | string | Manage needs a person who can approve reviews (403 approver_required). Allowed: "read", "propose", "manage" |
| key | optional | object | A public JWK: OKP Ed25519, EC P-256 or RSA of at least 2048 bits. |
| secret | optional | boolean | Generate a secret (creation only). |

#### Request template

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.

~~~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/service-clients/RESOURCE_ID/keys/KID/remove'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The service client. | ServiceClientResult (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/service-clients/{id}/secrets

**Add Service Client Secret**

Generates a second secret, returned once, for a zero-downtime rotation; a client holds at most two. Agents need the project capability and Manage; a person uses the dashboard session with project_id and a fresh proof (kind service_client_change, subject the client id, or the project id when creating) in the body. A service token is refused 403.


Operation: addServiceClientSecret.

- Authentication: projectBearer OR dashboardSession
- Request body: ServiceClientChange

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| project_id | optional | string | Dashboard session only. |
| proof | optional | DashboardProof |  |
| name | optional | string | minLength: 1 maxLength: 80 |
| capabilities | optional | array |  |
| access | optional | string | Manage needs a person who can approve reviews (403 approver_required). Allowed: "read", "propose", "manage" |
| key | optional | object | A public JWK: OKP Ed25519, EC P-256 or RSA of at least 2048 bits. |
| secret | optional | boolean | Generate a secret (creation only). |

#### Request template

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.

~~~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/service-clients/RESOURCE_ID/secrets'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The service client. | ServiceClientResult (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/service-clients/{id}/secrets/{secret_id}/remove

**Remove Service Client Secret**

Removes a secret; the client's current tokens end within seconds. Agents need the project capability and Manage; a person uses the dashboard session with project_id and a fresh proof (kind service_client_change, subject the client id, or the project id when creating) in the body. A service token is refused 403.


Operation: removeServiceClientSecret.

- Authentication: projectBearer OR dashboardSession
- Request body: ServiceClientChange

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |
| secret_id | path | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| project_id | optional | string | Dashboard session only. |
| proof | optional | DashboardProof |  |
| name | optional | string | minLength: 1 maxLength: 80 |
| capabilities | optional | array |  |
| access | optional | string | Manage needs a person who can approve reviews (403 approver_required). Allowed: "read", "propose", "manage" |
| key | optional | object | A public JWK: OKP Ed25519, EC P-256 or RSA of at least 2048 bits. |
| secret | optional | boolean | Generate a secret (creation only). |

#### Request template

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.

~~~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/service-clients/RESOURCE_ID/secrets/SECRET_ID/remove'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The service client. | ServiceClientResult (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/service-clients/{id}/revoke

**Revoke Service Client**

Revokes a service client permanently: its keys and secret digests are deleted, the record stays for audit, and its tokens end within seconds (propagation applied or pending).

Agents need the project capability and Manage; a person uses the dashboard session with project_id and a fresh proof (kind service_client_change, subject the client id, or the project id when creating) in the body. A service token is refused 403.


Operation: revokeServiceClient.

- Authentication: projectBearer OR dashboardSession
- Request body: ServiceClientChange

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| project_id | optional | string | Dashboard session only. |
| proof | optional | DashboardProof |  |
| name | optional | string | minLength: 1 maxLength: 80 |
| capabilities | optional | array |  |
| access | optional | string | Manage needs a person who can approve reviews (403 approver_required). Allowed: "read", "propose", "manage" |
| key | optional | object | A public JWK: OKP Ed25519, EC P-256 or RSA of at least 2048 bits. |
| secret | optional | boolean | Generate a secret (creation only). |

#### Request template

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.

~~~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/service-clients/RESOURCE_ID/revoke'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The service client. | ServiceClientResult (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) |

## Applications and operations

### GET /api/applications

**List Applications**

Lists applications newest first, at most 500 per page. Names are labels and need not be unique; address an application by id. 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: listApplications.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Applications | ApplicationList (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/applications

**Upload Application**

Streams one CLI-produced tar.gz directly into bounded durable staging. Metadata is carried in headers so the archive is never base64-wrapped or materialized as JSON by the server.


Operation: uploadApplication.

- Authentication: projectBearer
- Request body: string

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| Idempotency-Key | header | required |  |
| ScreenRig-Archive-SHA256 | header | required |  |
| ScreenRig-Expanded-Bytes | header | required |  |
| ScreenRig-File-Count | header | required |  |
| ScreenRig-SDK-Version | header | optional |  |
| ScreenRig-Application-Name | header | optional | ASCII application name; mutually exclusive with ScreenRig-Application-Name*. Leading and trailing whitespace is trimmed. Controls and duplicate header values are rejected. The decoded name is limited to 120 Unicode scalar values. |
| ScreenRig-Application-Name* | header | optional | Unicode application name encoded as RFC 8187 UTF-8'' followed by percent-encoded UTF-8 bytes (empty language, exact UTF-8 charset). Mutually exclusive with ScreenRig-Application-Name. Invalid escapes, invalid UTF-8, controls, and duplicate header values are rejected. Leading and trailing whitespace is trimmed; the decoded name is limited to 120 Unicode scalar values. Neither name header may rename a release update. This avoids raw Unicode in HTTP header values. |

#### Request template

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.

~~~sh
curl --request POST --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --header 'Idempotency-Key: REQUEST_ID' --header 'ScreenRig-Archive-SHA256: VALUE' --header 'ScreenRig-Expanded-Bytes: VALUE' --header 'ScreenRig-File-Count: VALUE' --header 'Content-Type: application/gzip' --data-binary '@request.bin' 'https://screenrig.ai/api/applications'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 202 | Upload accepted | OperationAccepted (application/json); headers: 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/applications/{id}/releases

**Upload Application Release**

Publishes an immutable new release of an owned application using the same bounded tar.gz upload pipeline. If-Match guards the application revision; acceptance increments it, and successful publication increments it again when latest_ready_release changes. Name and application identity are preserved. Existing playlist release pins are not changed.

An identical idempotent retry returns the original operation even after revision changes.


Operation: uploadApplicationRelease.

- Authentication: projectBearer
- Request body: string

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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 | required |  |
| ScreenRig-Archive-SHA256 | header | required |  |
| ScreenRig-Expanded-Bytes | header | required |  |
| ScreenRig-File-Count | header | required |  |
| ScreenRig-SDK-Version | header | optional |  |

#### Request template

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.

~~~sh
curl --request POST --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --header 'Idempotency-Key: REQUEST_ID' --header 'ScreenRig-Archive-SHA256: VALUE' --header 'ScreenRig-Expanded-Bytes: VALUE' --header 'ScreenRig-File-Count: VALUE' --header 'Content-Type: application/gzip' --data-binary '@request.bin' 'https://api.screenrig.ai/api/applications/RESOURCE_ID/releases'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 202 | Upload accepted | OperationAccepted (application/json); headers: 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) |

### GET /api/applications/{id}

**Get Application**


Operation: getApplication.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Application | Application (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) |

### PATCH /api/applications/{id}

**Rename Application**

Changes the display name of an application owned by the authenticated project. Names are trimmed, contain 1 to 120 Unicode characters with no controls, and may repeat within a project. Application identity, releases, K/V data, and playlist release pins are preserved.

A changed name advances the application revision and emits application.updated; the same name is a no-op. If-Match optionally guards the revision. An identical idempotent retry returns the original response after subsequent metadata changes.


Operation: renameApplication.

- Authentication: projectBearer
- Request body: ApplicationRename

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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 | required | string | Display name after trimming; controls and empty names are rejected. minLength: 1 maxLength: 120 |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Renamed application. | Application (application/json); headers: ETag |
| 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/applications/{id}

**Delete Application**

Tombstones one application owned by the authenticated project and all of its releases. The project bearer is authoritative; an unknown or other-project id is not_found. If-Match guards the application revision. A live manifest reference blocks deletion with application_in_use; there is no force-delete path.


Operation: deleteApplication.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | Application and releases tombstoned. | no body |
| 409 | application_in_use — a live manifest still references one of the application's releases. | 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) |

### GET /api/operations/{id}

**Get Operation**


Operation: getOperation.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Operation | 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/operations/{id}/cancel

**Cancel Operation**


Operation: cancelOperation.

- Authentication: projectBearer
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |
| Idempotency-Key | header | required |  |

#### Request template

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.

~~~sh
curl --request POST --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --header 'Idempotency-Key: REQUEST_ID' 'https://api.screenrig.ai/api/operations/RESOURCE_ID/cancel'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Cancelled durable operation. A current worker fence can no longer finalize it. | 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) |

## Media uploads

### GET /api/media

**List Media**

Lists ready media newest first, at most 1000 per page. 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 | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| tag | query | optional | Exact media tag. Untagged objects are omitted when this filter is present. |
| primitive | query | optional |  |
| 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

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.

~~~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 return a MediaGeneration object on the original POST. The handler waits for the vendor still and does not return a pollable Operation. The response is MediaGeneration: the ready Media object plus usage (quality, credits, usd).

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 MediaGeneration. 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.

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.

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. Empty selected-vendor configuration returns not_ready. Project bearer only. One in-flight generation per project; further parallelism is rate_limited.


Operation: createMediaGeneration.

- Authentication: projectBearer OR dashboardSession
- Request body: MediaGenerationRequest

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| Idempotency-Key | header | required |  |

#### 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

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.

~~~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 |
| --- | --- | --- |
| 201 | Ready still stored as private project media, with billed usage (quality, credits, usd). Fetch original bytes with GET /api/media/{id}/content. GET /api/media/{id} remains Media-only. Never image bytes, b64_json, object keys, signed URLs, vendor URLs, vendor cost, or the prompt. | MediaGeneration (application/json); headers: RateLimit-Policy, RateLimit, ETag |
| 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.


Operation: listMediaFolders.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |

#### Request template

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.

~~~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. A project holds at most 1000 folders (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 | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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 | 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

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.

~~~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 | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |

#### Request template

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.

~~~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 | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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

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.

~~~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 | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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

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.

~~~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 | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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

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.

~~~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 | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| Idempotency-Key | header | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| filename | required | string | Name of the bytes being uploaded, as they will be sent. minLength: 1 maxLength: 255 |
| source_filename | optional | string | Caller's original file name before any client-side transcode. Bare file name only. 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}$" |

#### Request template

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.

~~~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. | 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 | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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

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.

~~~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 | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| primitive | required | string | Allowed: "image", "video" |
| selector | required | inline |  |

#### Request template

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.

~~~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 | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |

#### Request template

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.

~~~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 | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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

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.

~~~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 | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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

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.

~~~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) |

### 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.

Remaining at or below the intro floor (negative 1,000,000 credits until 2027-01-01, then zero) returns payment_required before the media is read or any byte is transferred.

A dashboard session views the same bytes inline: it is not prepaid-gated, 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 | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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. |

#### Request template

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.

~~~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 prepaid, 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 | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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. |

#### Request template

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.

~~~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 | Prepaid remaining is below the floor. 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) |

## Playlists

### GET /api/playlists

**List Playlists V2**

Versioned playlist list that may expose adslot pages. The v1 list refuses a page holding an ad-bearing document with version_required instead of filtering it.

Pages like listPlaylists, newest first, at most 200 per page and fewer once a page holds 8 MiB of stored playlists. 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: listPlaylistsV2.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Playlists with the adslot page union. | PlaylistV2List (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/playlists

**Create Playlist V2**

Authoring entry for the adslot page union. At most 16 adslot pages, at least one ordinary page with no visibility, and the reuse of one slot definition on different pages remain server checks. Reference resolution and DNS stay server checks.


Operation: createPlaylistV2.

- Authentication: projectBearer
- Request body: PlaylistWriteV2

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| Screenrig-Review-Note | header | optional | Optional proposer note on a review-class write, sent as percent-encoded UTF-8 (what JavaScript's encodeURIComponent writes). The server percent-decodes it (an invalid escape is 400 invalid_request); the decoded note is plain text, at most 280 characters after trimming, no control characters (else 400 invalid_request). Stored on the review when the write becomes one; ignored when it applies live. |
| 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 | required | string | minLength: 1 maxLength: 120 |
| pages | required | array | The pages of one playlist may resolve to at most 1024 media references in total. A by:ids selector counts its length; a by:all, by:tag or by:folder definition reserves 32 references independently of catalog size. The actual loop snapshot is bounded at 1024 media references in total. Above either budget is 422 invalid at path pages. |
| audio | optional | PlaylistAudioWrite | Optional playlist soundtrack: ordered ready audio (MP3) media that plays continuously while pages change (docs/playlist-audio.md). Page changes never stop or restart it. PUT replaces the whole playlist, so omitting audio removes the soundtrack. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 202 | The write became a review under Propose access and waits for a person's approval (docs/reviews.md). A person approves, rejects or requests changes in the dashboard at review.url. An Idempotency-Key replay answers the same body. | ReviewPending (application/json) |
| 201 | Playlist with its adslot pages. | PlaylistV2 (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/playlists/{id}

**Get Playlist V2**


Operation: getPlaylistV2.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Playlist with its adslot pages. | PlaylistV2 (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) |

### PUT /api/playlists/{id}

**Update Playlist V2**

Versioned update accepting the adslot page union. If-Match carries the playlist revision.


Operation: updatePlaylistV2.

- Authentication: projectBearer
- Request body: PlaylistWriteV2

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| Screenrig-Review-Note | header | optional | Optional proposer note on a review-class write, sent as percent-encoded UTF-8 (what JavaScript's encodeURIComponent writes). The server percent-decodes it (an invalid escape is 400 invalid_request); the decoded note is plain text, at most 280 characters after trimming, no control characters (else 400 invalid_request). Stored on the review when the write becomes one; ignored when it applies live. |
| 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 | required | string | minLength: 1 maxLength: 120 |
| pages | required | array | The pages of one playlist may resolve to at most 1024 media references in total. A by:ids selector counts its length; a by:all, by:tag or by:folder definition reserves 32 references independently of catalog size. The actual loop snapshot is bounded at 1024 media references in total. Above either budget is 422 invalid at path pages. |
| audio | optional | PlaylistAudioWrite | Optional playlist soundtrack: ordered ready audio (MP3) media that plays continuously while pages change (docs/playlist-audio.md). Page changes never stop or restart it. PUT replaces the whole playlist, so omitting audio removes the soundtrack. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 202 | The write became a review under Propose access and waits for a person's approval (docs/reviews.md). A person approves, rejects or requests changes in the dashboard at review.url. An Idempotency-Key replay answers the same body. | ReviewPending (application/json) |
| 200 | Updated playlist. | PlaylistV2 (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/playlists/{id}

**Delete Playlist V2**

Tombstones one playlist owned by the authenticated project. Refused with resource_conflict (409) while any screen, archived or pending included, has it as its default, in its playlist schedule, or as its takeover. The dashboard session may delete as well; it never creates or edits a playlist.


Operation: deletePlaylistV2.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | Playlist tombstoned. | no body |
| 409 | 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) |

## Screens

### GET /api/screens

**List Screens**

Default list returns pairing_pending and active screens. Pass state=archived to list archived screens only. Pass tag to list only screens whose tags contain that exact tag; untagged screens are omitted when the filter is present.

Screens are newest first, at most 500 per page. 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: listScreens.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| state | query | optional | Omit for pairing_pending and active. Pass archived to list archived screens only. |
| tag | query | optional | Exact screen tag. Untagged screens are omitted when this filter is present. |
| 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

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Screens | ScreenList (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/screens/pair

**Pair Screen**

Claims one fleet-global six-character Player pairing session. Exact Idempotency-Key retries return the durable original result without consuming quota again.

The dashboard session claims into its current project with the same body, Idempotency-Key, budgets and problems, except that its not_found for an unknown or already-claimed code names no CLI command; it is a state change, so it follows the dashboard mutation rules, a shadow session without staff write mode is refused, and the events the claim appends name the dashboard user.


Operation: pairScreen.

- Authentication: projectBearer OR dashboardSession
- Request body: PairScreen

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| Idempotency-Key | header | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| code | required | string | pattern: "^[23456789ABCDEFGHJKMNPQRSTUVWXYZ]{6}$" |
| label | optional | string | minLength: 1 maxLength: 120 |

#### Request template

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.

~~~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/screens/pair'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Durable pairing_pending screen claim. | PairingClaim (application/json); headers: Cache-Control, RateLimit-Policy, RateLimit |
| 400 | 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) |
| 401 | 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) |
| 409 | 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) |
| 413 | 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) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, RateLimit-Policy, RateLimit |
| 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) |

### POST /api/screens/provision

**Provision Screen**

Optional browser-only new-screen provisioning. Always creates one new quota-counted pairing_pending screen; exact retries redeliver the same fragment token for ten minutes. Six-character pairing remains the default and only recovery flow.


Operation: provisionScreen.

- Authentication: projectBearer
- Request body: ProvisionScreen

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| Idempotency-Key | header | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| label | optional | string | minLength: 1 maxLength: 120 |

#### Request template

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.

~~~sh
curl --request POST --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --header 'Idempotency-Key: REQUEST_ID' --header 'Content-Type: application/json' --data '@request.json' 'https://api.screenrig.ai/api/screens/provision'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | New browser provisioning link. | ScreenProvisioning (application/json); headers: Cache-Control, RateLimit-Policy, RateLimit |
| 400 | 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) |
| 401 | 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) |
| 409 | 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) |
| 410 | 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) |
| 413 | 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) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, RateLimit-Policy, RateLimit |
| 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) |

### POST /api/screens/actions

**Apply Screen Actions**

Applies one action to a selected fleet with no screen-count cap. Select unique screen_ids or a tag matching active screens in creation order. The resolved selection is pinned before any mutation, stored in bounded chunks, and kept for twenty-four hours under Idempotency-Key.

Each step uses its single-screen mutation path, preserving revisions, events, runtime signals and per-screen limits. A response contains one bounded page of results; matched, succeeded and failed describe that page. HTTP 202 includes total, offset and next_cursor. Continue with the same selector, action and Idempotency-Key, passing next_cursor as after.

Honor Retry-After when present, including a page with zero results. The response echoes Idempotency-Key, including a generated key when the caller omitted it; retain this key for continuations. HTTP 200 finishes the selection. Answered pages replay exactly, including per-screen failures; interrupted steps replay without repeating their effects.

Large actions are paced against the existing 600-per-minute project budgets for reload, toast, takeover, playlist schedules, reboot, display and display schedules; project exhaustion pauses the action instead of refusing the fleet. Per-screen exhaustion fails that screen alone. Assign and tag changes have no message budget. Partial failure is a normal result.

All pages and retries under one key are one metered action. Requests use a bounded byte payload; use a tag for a selection larger than the transport payload. A malformed selector or action is refused before any screen is touched.


Operation: applyScreenActions.

- Authentication: projectBearer
- Request body: ScreenActionRequest

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| Screenrig-Review-Note | header | optional | Optional proposer note on a review-class write, sent as percent-encoded UTF-8 (what JavaScript's encodeURIComponent writes). The server percent-decodes it (an invalid escape is 400 invalid_request); the decoded note is plain text, at most 280 characters after trimming, no control characters (else 400 invalid_request). Stored on the review when the write becomes one; ignored when it applies live. |
| Idempotency-Key | header | optional | Omit for a fresh operation. Supply and reuse a key to deduplicate retries; the CLI manages keys automatically. |
| after | query | optional | next_cursor from the preceding result page. Reuse the original Idempotency-Key and request body. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| selector | required | ScreenActionSelector |  |
| action | required | ScreenAction | One fleet action, discriminated by type. Each member carries only its own parameters; a member of another type is an unknown field and invalid_request. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Per-screen results. Partial success is a normal answer. | ScreenActionResult (application/json); headers: Idempotency-Key, Cache-Control, RateLimit-Policy, RateLimit |
| 202 | More screens remain (continue with next_cursor and the same Idempotency-Key and body, honoring Retry-After), or, with status pending_review, the action became a review under Propose access (docs/reviews.md). Only assign, takeover, takeover_clear, set_playlist_schedule and clear_playlist_schedule become reviews; every other action is insufficient_access for Propose. | inline (application/json); headers: Idempotency-Key, Retry-After, Cache-Control, RateLimit-Policy, RateLimit |
| 400 | 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) |
| 401 | 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) |
| 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) |
| 403 | 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) |
| 409 | 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) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, 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) |

### GET /api/screens/{id}

**Get Screen**

Returns pairing_pending, active, and archived screens. Deleted screens are not found.


Operation: getScreen.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Screen | Screen (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) |

### PATCH /api/screens/{id}

**Update Screen**

Updates the screen's name, playlist_id, timezone or tags. Under Propose access a patch of playlist_id alone becomes a review (202 pending_review); any other field is insufficient_access. A playlist with no approved version is playlist_not_approved.


Operation: updateScreen.

- Authentication: projectBearer
- Request body: ScreenPatch

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |
| Screenrig-Review-Note | header | optional | Optional proposer note on a review-class write, sent as percent-encoded UTF-8 (what JavaScript's encodeURIComponent writes). The server percent-decodes it (an invalid escape is 400 invalid_request); the decoded note is plain text, at most 280 characters after trimming, no control characters (else 400 invalid_request). Stored on the review when the write becomes one; ignored when it applies live. |
| 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: 120 |
| playlist_id | optional | string |  |
| timezone | optional | string | IANA time zone identifier, for example America/Los_Angeles. It is the zone every page visibility rule on this screen is evaluated in. Changing it remints the manifest revision. A screen assigned a playlist that uses page visibility must have one. minLength: 1 maxLength: 64 |
| tags | optional | ScreenTags | Fleet selector tags, 0 to 16 unique exact tags, each matching the media tag grammar. On ScreenPatch the array replaces the whole set and an empty array clears it; a duplicate or malformed tag is invalid_request. Changing tags bumps the screen revision (If-Match guards it) and appends screen.updated with details.tags; it never changes the manifest revision. Tags are never on the runtime manifest and never authorization. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 202 | The write became a review under Propose access and waits for a person's approval (docs/reviews.md). A person approves, rejects or requests changes in the dashboard at review.url. An Idempotency-Key replay answers the same body. | ReviewPending (application/json) |
| 200 | Screen | Screen (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/screens/{id}

**Delete Screen**

Soft-deletes an eligible screen and immediately revokes its cookie credentials and runtime grants. Ordinary reads then return not_found; the tombstoned row is retained for at least 90 days before cleanup. If-Match guards the revision and Idempotency-Key replays the result.

A native identity-bound screen returns screen_archive_required: use archive to retain that binding; an identity-bound screen that is already archived stays archived (the same problem, saying so). This route does not grant administrative break-glass permission to release native enrollment.


Operation: deleteScreen.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | Screen soft-deleted; its tombstone is retained for at least 90 days. | no body |
| 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) |
| 409 | screen_archive_required for a native identity-bound screen. | 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/screens/{id}/public-id/rotate

**Rotate Screen Public Id**


Operation: rotateScreenPublicId.

- Authentication: projectBearer
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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

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.

~~~sh
curl --request POST --header 'Authorization: Bearer [SCREENRIG_TOKEN]' 'https://api.screenrig.ai/api/screens/RESOURCE_ID/public-id/rotate'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Public URL credential rotated; player public-key enrollment preserved. Every runtime session is invalidated by the new content_access_generation and the live desired and active grants are reissued at that generation for their unchanged manifest revision, so a paired player that re-mints its session keeps authorizing the content it is already showing. | Screen (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/screens/{id}/recovery/confirm

**Confirm Screen Recovery**

Confirms a pending screen recovery. A native pairing start that presented a host duid or serial matching exactly one screen in exactly one project, under a different bound public key, records an offer on that screen (Screen.recovery_pending, project event screen.recovery_offered).

The host object is a hint, never a credential: nothing is rebound until the owning project calls this route. A DUID is readable by any application on the panel, so an offer is a phishing surface.

It is mitigated by liveness (no offer while the bound key showed life in the last ten minutes), a limit of three offers per screen per rolling hour, and this same-project confirmation; recovery_pending.host and the event carry the offering device's platform, model, firmware, and manufacturer so the operator can compare them with the display in front of them before confirming.

Confirmation completes the pending native pairing onto the EXISTING screen: the pairing device receives pairing.claimed as usual and proves possession with pairing.complete, the new key becomes the screen identity, and the previous key retires with a fifteen-minute grace window in which its runtime sessions keep working and it may still mint, after which it is refused.

Playlists, label, timezone, schedules, and history are preserved. Same project only; another project's screen is not_found. If-Match is optional. Emits screen.recovered. recovery_not_offered when nothing is pending, recovery_expired when the pairing session lapsed, recovery_ambiguous when the identifier is attached in more than one place.

Problem responses never echo device identifiers.


Operation: confirmScreenRecovery.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |
| If-Match | header | optional | Optional positive resource revision. Omit to create or overwrite a key; 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

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Screen recovered onto the pairing device's key; the response carries the current ETag. | Screen (application/json) |
| 401 | 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) |
| 409 | 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) |
| 410 | 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) |
| 412 | 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) |

### POST /api/screens/{id}/archive

**Archive Screen**

Hides the screen from the default list and clears the live glass to the "Screen archived" status. Does not unbind the player public key. Releases screen_count quota.


Operation: archiveScreen.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Screen archived. | Screen (application/json) |
| 401 | 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) |
| 409 | 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) |
| 412 | 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) |

### POST /api/screens/{id}/unarchive

**Unarchive Screen**

Restores an archived screen to the default list and remints a normal manifest. Must pass screen admission. Does not change the bound public key.

The manifest is issued again, so the screen's playlist is checked as an assignment is; a playlist that references missing or unready content answers resource_conflict naming the playlist, and the screen stays archived until the playlist is fixed.


Operation: unarchiveScreen.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Screen unarchived. | Screen (application/json) |
| 401 | 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) |
| 409 | 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) |
| 412 | 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) |
| 413 | 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) |

### POST /api/screens/{id}/reload

**Reload Screen**

Asks the Player of the named active or archived screen to reload (docs/player-compatibility.md §6.3). Appends the durable per-screen player.reload event with details.reason project, a new reload_id, spread_s 0, and expires_at ten minutes later. The runtime SSE stream relays it to every runtime session.

A Player ignores a reload within ten minutes of the last one it acted on. A web Player reloads at its next page boundary; a native Player remints, refetches its manifest, and checks for an update. The screen revision does not change.

If-Match and Idempotency-Key are optional, as on archive; an exact Idempotency-Key retry returns the original reload_id and expires_at for twenty-four hours. A pairing_pending screen has no Player yet and answers resource_conflict. Screen messages are exempt from the 1-credit API meter.


Operation: reloadScreen.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 202 | Accepted. The reload is durable until expires_at. The ETag is the unchanged screen revision. | ScreenReloadAccepted (application/json); headers: Cache-Control, ETag, RateLimit-Policy, RateLimit |
| 400 | 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) |
| 401 | 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) |
| 409 | 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) |
| 412 | 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) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, 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) |

### GET /api/screens/{id}/playlist-schedule

**Get Screen Playlist Schedule**

Returns the screen's server-evaluated playlist schedule: entries in priority order (empty when the screen has none), updated_at, and the effective_playlist they currently produce. The ETag is the screen revision. pairing_pending, active, and archived screens are readable; another project's screen is not_found.


Operation: getScreenPlaylistSchedule.

- Authentication: projectBearer
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |

#### Request template

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.

~~~sh
curl --request GET --header 'Authorization: Bearer [SCREENRIG_TOKEN]' 'https://api.screenrig.ai/api/screens/RESOURCE_ID/playlist-schedule'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Playlist schedule. | ScreenPlaylistScheduleView (application/json); headers: ETag |
| 401 | 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) |
| 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) |

### PUT /api/screens/{id}/playlist-schedule

**Set Screen Playlist Schedule**

Replaces the screen's playlist schedule. The server evaluates it, never the Player: the first entry whose rule matches the screen's civil time (screenrig.schedule/v1 windows and from/until, in the screen timezone, including daylight-saving transitions and windows that cross midnight) is the effective playlist; when none matches, the assigned playlist_id is.

A takeover still wins over every entry. The screen must have a timezone and an assigned default playlist_id (invalid_request otherwise), so there is always something to fall back to.

Every referenced playlist is validated against the screen exactly as an assignment is (owned, ready references, page visibility needs a timezone), so a playlist the screen would refuse is refused here; the check writes the playlist document, so a concurrent playlist delete conflicts instead of racing it.

If a scheduled playlist later cannot be shown (for example its content no longer resolves), its entries are skipped at evaluation and screen.playlist_unavailable (warning, details playlist_id, entry_ids, code) is appended.

When the effective playlist changes the manifest is re-minted from it and the Player is signalled with screen.manifest_changed, as for an assignment, and screen.playlist_switched is appended. The screen revision is bumped (If-Match guards it) and screen.updated is appended with details.playlist_schedule_entries.

An archived screen answers screen_archived; its schedule, takeover, and default stay referenced (they block playlist delete) and are re-resolved when it is unarchived. Rate limits: screen-control-project 600, screen-control-screen 20, and screen-control-ip 60 per minute, shared with DELETE and the takeover routes.


Operation: setScreenPlaylistSchedule.

- Authentication: projectBearer
- Request body: ScreenPlaylistScheduleWrite

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |
| Screenrig-Review-Note | header | optional | Optional proposer note on a review-class write, sent as percent-encoded UTF-8 (what JavaScript's encodeURIComponent writes). The server percent-decodes it (an invalid escape is 400 invalid_request); the decoded note is plain text, at most 280 characters after trimming, no control characters (else 400 invalid_request). Stored on the review when the write becomes one; ignored when it applies live. |
| 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 |
| --- | --- | --- | --- |
| entries | required | array | Priority order: the first matching entry wins. DELETE clears the schedule. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 202 | The write became a review under Propose access and waits for a person's approval (docs/reviews.md). An Idempotency-Key replay answers the same body. | ReviewPending (application/json); headers: RateLimit-Policy, RateLimit |
| 200 | Screen with the stored schedule and its effective_playlist. The ETag is the new revision. | Screen (application/json); headers: ETag, RateLimit-Policy, RateLimit |
| 400 | 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) |
| 401 | 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) |
| 409 | 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) |
| 412 | 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) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, 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) |

### DELETE /api/screens/{id}/playlist-schedule

**Clear Screen Playlist Schedule**

Removes the screen's playlist schedule; the takeover, else the assigned playlist, becomes effective, with the same manifest signalling as PUT. A screen without a schedule answers 200 unchanged (no revision bump, no event).


Operation: clearScreenPlaylistSchedule.

- Authentication: projectBearer
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |
| Screenrig-Review-Note | header | optional | Optional proposer note on a review-class write, sent as percent-encoded UTF-8 (what JavaScript's encodeURIComponent writes). The server percent-decodes it (an invalid escape is 400 invalid_request); the decoded note is plain text, at most 280 characters after trimming, no control characters (else 400 invalid_request). Stored on the review when the write becomes one; ignored when it applies live. |
| 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

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.

~~~sh
curl --request DELETE --header 'Authorization: Bearer [SCREENRIG_TOKEN]' 'https://api.screenrig.ai/api/screens/RESOURCE_ID/playlist-schedule'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 202 | The write became a review under Propose access and waits for a person's approval (docs/reviews.md). An Idempotency-Key replay answers the same body. | ReviewPending (application/json); headers: RateLimit-Policy, RateLimit |
| 200 | Screen without a playlist schedule. | Screen (application/json); headers: ETag, RateLimit-Policy, RateLimit |
| 401 | 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) |
| 409 | 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) |
| 412 | 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) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, 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/screens/{id}/takeover

**Set Screen Takeover**

Takes over the screen with one playlist ahead of its playlist schedule and assigned playlist, until until (RFC 3339, strictly in the future and at most 7 days ahead) or, when until is null or omitted, until the takeover is cleared. reason is optional, at most 120 characters. The screen must have an assigned default playlist_id (invalid_request otherwise).

A takeover replaces an earlier one (screen.takeover_ended with reason replaced, or expired when the earlier one had already ended). The playlist is validated as an assignment. A takeover whose playlist later cannot be shown ends (screen.takeover_ended with reason playlist_deleted or playlist_unavailable).

Appends screen.takeover_started and, when the effective playlist changes, screen.playlist_switched plus the Player's screen.manifest_changed. When until passes, the boundary worker ends the takeover within 60 seconds (screen.takeover_ended with reason expired). The screen revision is bumped (If-Match guards it).

An archived screen answers screen_archived; unarchive re-resolves first, so a takeover that ended while archived ends then. The same screen-control rate limits as the schedule routes apply.


Operation: setScreenTakeover.

- Authentication: projectBearer
- Request body: ScreenTakeoverWrite

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |
| Screenrig-Review-Note | header | optional | Optional proposer note on a review-class write, sent as percent-encoded UTF-8 (what JavaScript's encodeURIComponent writes). The server percent-decodes it (an invalid escape is 400 invalid_request); the decoded note is plain text, at most 280 characters after trimming, no control characters (else 400 invalid_request). Stored on the review when the write becomes one; ignored when it applies live. |
| 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 |
| --- | --- | --- | --- |
| playlist_id | required | string | minLength: 1 |
| until | optional | inline | Strictly in the future and at most 7 days ahead. null or omitted holds the takeover until it is cleared. |
| reason | optional | string | Operator note carried on the takeover and its events. No control characters. maxLength: 120 |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 202 | The write became a review under Propose access and waits for a person's approval (docs/reviews.md). An Idempotency-Key replay answers the same body. | ReviewPending (application/json); headers: RateLimit-Policy, RateLimit |
| 200 | Screen with the takeover and its effective_playlist. The ETag is the new revision. | Screen (application/json); headers: ETag, RateLimit-Policy, RateLimit |
| 400 | 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) |
| 401 | 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) |
| 409 | 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) |
| 412 | 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) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, 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) |

### DELETE /api/screens/{id}/takeover

**Clear Screen Takeover**

Ends the takeover (screen.takeover_ended with reason cleared); the schedule, else the assigned playlist, becomes effective with the same manifest signalling. A screen without a takeover answers 200 unchanged (no revision bump, no event).


Operation: clearScreenTakeover.

- Authentication: projectBearer
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |
| Screenrig-Review-Note | header | optional | Optional proposer note on a review-class write, sent as percent-encoded UTF-8 (what JavaScript's encodeURIComponent writes). The server percent-decodes it (an invalid escape is 400 invalid_request); the decoded note is plain text, at most 280 characters after trimming, no control characters (else 400 invalid_request). Stored on the review when the write becomes one; ignored when it applies live. |
| 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

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.

~~~sh
curl --request DELETE --header 'Authorization: Bearer [SCREENRIG_TOKEN]' 'https://api.screenrig.ai/api/screens/RESOURCE_ID/takeover'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 202 | The write became a review under Propose access and waits for a person's approval (docs/reviews.md). An Idempotency-Key replay answers the same body. | ReviewPending (application/json); headers: RateLimit-Policy, RateLimit |
| 200 | Screen without a takeover. | Screen (application/json); headers: ETag, RateLimit-Policy, RateLimit |
| 401 | 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) |
| 409 | 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) |
| 412 | 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) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, 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/screens/{id}/reboot

**Reboot Screen**

Asks the screen's Player to reboot the device (docs/player-compatibility.md §6.5). Appends the per-screen runtime command player.reboot (details reboot_id, reason project, at, expires_at ten minutes later; never delivered after expires_at) and the project-only screen.reboot_requested.

The screen must be active; an archived screen answers screen_archived and a pairing_pending one resource_conflict. The screen's host hint must declare the reboot capability, else 409 reboot_unsupported and nothing is sent. The screen revision does not change.

If-Match and Idempotency-Key are optional; an exact retry returns the same reboot_id for twenty-four hours. At most 2 per screen per 10 minutes; a refused request (reboot_unsupported, not_found, a conflict), a failed one, and an exact Idempotency-Key replay spend nothing.


Operation: rebootScreen.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 202 | Accepted. The reboot is actionable until expires_at. The ETag is the unchanged screen revision. | ScreenRebootAccepted (application/json); headers: Cache-Control, ETag, RateLimit-Policy, RateLimit |
| 400 | 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) |
| 401 | 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) |
| 409 | 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) |
| 412 | 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) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, 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/screens/{id}/display

**Set Screen Display**

Turns the display on or off now: a manual override of the display schedule (docs/player-compatibility.md §6.5). power is on or off. until (RFC 3339, strictly future, at most 7 days ahead) ends it; without until it ends at the display schedule's next boundary when a schedule is enabled, else it holds until replaced.

Stores the override (Screen.display.override), re-mints the manifest (its display member carries the override so an offline Player keeps it), appends the runtime command player.display (details override_id, power, until, at, expires_at) and the project-only screen.display_changed, and bumps the screen revision (If-Match guards it). The screen must be active.

What the Player achieved is reported back in its health display.power (Screen.display.reported).

Without until and without a schedule boundary inside the next eight days (no enabled schedule, or none that changes), the override holds until DELETE /api/screens/{id}/display or a replacement; a later display-schedule PUT or DELETE, or a timezone change, re-bounds an until-less override to the new next boundary.

A refused or failed request, and an exact Idempotency-Key replay, spend no budget.


Operation: setScreenDisplay.

- Authentication: projectBearer OR dashboardSession
- Request body: ScreenDisplayWrite

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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 |
| --- | --- | --- | --- |
| power | required | string | Allowed: true, false |
| until | optional | inline | Strictly future, at most 7 days ahead. Omitted or null: until the display schedule's next boundary, else until replaced. |

#### Request template

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.

~~~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/screens/RESOURCE_ID/display'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Screen with its display state. | Screen (application/json); headers: ETag, RateLimit-Policy, RateLimit |
| 400 | 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) |
| 401 | 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) |
| 409 | 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) |
| 412 | 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) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, 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) |

### DELETE /api/screens/{id}/display

**Clear Screen Display**

Ends the manual display override so the display schedule (else on) applies again. Re-mints the manifest (its display member drops the override; the Player follows screen.manifest_changed), appends screen.display_changed with change override_cleared, and bumps the revision. A screen without an active override answers 200 unchanged.


Operation: clearScreenDisplay.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Screen with its display state. | Screen (application/json); headers: ETag, RateLimit-Policy, RateLimit |
| 400 | 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) |
| 401 | 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) |
| 409 | 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) |
| 412 | 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) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, 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) |

### GET /api/screens/{id}/display-schedule

**Get Screen Display Schedule**

Returns display_schedule (null when none) and the screen's display state. The ETag is the screen revision.


Operation: getScreenDisplaySchedule.

- Authentication: projectBearer
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |

#### Request template

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.

~~~sh
curl --request GET --header 'Authorization: Bearer [SCREENRIG_TOKEN]' 'https://api.screenrig.ai/api/screens/RESOURCE_ID/display-schedule'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Display schedule. | ScreenDisplayScheduleView (application/json); headers: ETag |
| 401 | 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) |
| 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) |

### PUT /api/screens/{id}/display-schedule

**Set Screen Display Schedule**

Replaces the display schedule: enabled and 1 to 16 screenrig.schedule/v1 windows (days, start/end HH:MM, end <= start crosses midnight owned by the start day, whole day without edges) when the display is ON; outside every window the Player puts the display to standby.

Evaluated on the device in the screen timezone (required, invalid_request otherwise), so it keeps working offline: the manifest's display member carries it. enabled false keeps the windows and leaves the display on. Re-mints the manifest, appends screen.display_changed, bumps the revision (If-Match guards it). The screen must be pairing_pending or active.

A screen with no manifest yet (never minted, or no playlist) stores the schedule and its first manifest carries it; a screen without a playlist applies it once a playlist is assigned. An until-less override is re-bounded to the new schedule's next boundary.


Operation: setScreenDisplaySchedule.

- Authentication: projectBearer
- Request body: ScreenDisplayScheduleWrite

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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 |
| --- | --- | --- | --- |
| enabled | required | boolean |  |
| windows | required | array | ON windows. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Screen with its display state. | Screen (application/json); headers: ETag, RateLimit-Policy, RateLimit |
| 400 | 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) |
| 401 | 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) |
| 409 | 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) |
| 412 | 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) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, 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) |

### DELETE /api/screens/{id}/display-schedule

**Clear Screen Display Schedule**

Removes the display schedule (the display stays on unless overridden). Clearing none answers 200 unchanged.


Operation: clearScreenDisplaySchedule.

- Authentication: projectBearer
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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

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.

~~~sh
curl --request DELETE --header 'Authorization: Bearer [SCREENRIG_TOKEN]' 'https://api.screenrig.ai/api/screens/RESOURCE_ID/display-schedule'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Screen with its display state. | Screen (application/json); headers: ETag, RateLimit-Policy, RateLimit |
| 400 | 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) |
| 401 | 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) |
| 409 | 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) |
| 412 | 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) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, 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/screens/{id}/toast

**Post Screen Toast**

Posts one transient stage-chrome toast to the named screen. A toast is not a primitive: it does not occupy a canvas slot, has no layer, and never participates in readiness or crossfade. The durable event type is screen.toast on the existing runtime SSE stream. Runtime scan never delivers a toast after expires_at, so a persisted cursor cannot replay it later.

Level colours are player chrome and are not API fields. Text is 1 to 120 characters, line feed is the only accepted line break, and at most three lines are accepted. Recognizable ScreenRig credential material and other control characters are rejected. duration_ms defaults to 10000 and must be between 2000 and 60000 inclusive.

An exact Idempotency-Key retry returns the original expires_at for twenty-four hours. Latest-wins: there is no queue and no cancel verb. Screen messages are exempt from the 1-credit API meter.


Operation: postScreenToast.

- Authentication: projectBearer
- Request body: ScreenToastWrite

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |
| Idempotency-Key | header | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| level | required | string | Closed severity token. Player chrome chooses the fill; the API never carries a colour. Allowed: "error", "alert", "info" |
| text | required | string | 1 to 120 characters. Line feed is the only accepted line break. At most three lines. Other control characters and recognizable ScreenRig credential material are rejected. minLength: 1 maxLength: 120 |
| duration_ms | optional | integer | Visible duration in milliseconds. Omitted values default to 10000. default: 10000 minimum: 2000 maximum: 60000 |

#### Request template

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.

~~~sh
curl --request POST --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --header 'Idempotency-Key: REQUEST_ID' --header 'Content-Type: application/json' --data '@request.json' 'https://api.screenrig.ai/api/screens/RESOURCE_ID/toast'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 202 | Accepted. The toast is durable until expires_at. An exact idempotent retry returns this same expiry. | ScreenToastAccepted (application/json); headers: Cache-Control, RateLimit-Policy, RateLimit |
| 400 | 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) |
| 401 | 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) |
| 409 | 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) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, 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/screens/{id}/storage-forecast

**Forecast Screen Storage**

Pre-assignment fit dry run: answers whether one playlist would fit the named screen before it is assigned, with the same target selection as Screen.storage_forecast (the screen's last reported capacity, plan A.3 only, no transition prediction, nothing treated as local).

It writes nothing: no assignment, no screen revision, no event, and the screen's storage_forecast read model is untouched. The project bearer is authoritative; a screen or playlist of another project is not_found. fit is unknown when the screen has no storage report, or when the playlist's content references are not ready, and both byte counts are null then.

An optional playlist_revision refuses a playlist that changed since the caller read it with revision_conflict. A report older than 24 hours is stale but still forecast from, exactly like Screen.storage_forecast.


Operation: forecastScreenStorage.

- Authentication: projectBearer
- Request body: ScreenStorageForecastRequest

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| playlist_id | required | string | minLength: 1 |
| playlist_revision | optional | integer | Optional expected playlist revision. A different stored revision is revision_conflict and no forecast is returned. minimum: 1 |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Dry-run forecast. fit unknown means there is nothing to forecast from; the byte counts and received_at are null. | ScreenStorageForecastDryRun (application/json); headers: Cache-Control, RateLimit-Policy, RateLimit |
| 400 | 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) |
| 401 | 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) |
| 412 | revision_conflict — playlist_revision is not the playlist's current revision; no forecast is returned. | Problem (application/problem+json) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, 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) |

### GET /api/screens/{id}/screenshot

**Get Screen Screenshot**

Returns the current ready still WebP for the named screen. Optional capture_id must match that ready slot. A named capture_id that expired with no upload is screenshot_unavailable. A named capture_id replaced by a later request is resource_conflict. Image bytes are never a signed URL or object key. Range is not accepted.

Screen messages are exempt from the 1-credit API meter. The ETag is the strong SHA-256 of the stored WebP. A dashboard session response is private, no-cache, and a dashboard If-None-Match that matches the current image, checked after the screen and capture resolve in the session's project, returns 304 without opening the stored bytes.

A project bearer response stays private, no-store.


Operation: getScreenScreenshot.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |
| capture_id | query | optional |  |
| If-None-Match | header | optional | Strong ETag of a screenshot this route returned. Honored for a dashboard session. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Current ready still WebP. | string (image/webp); headers: Content-Type, Content-Length, ETag, X-ScreenRig-Capture-ID, X-ScreenRig-Captured-At, Cache-Control, Content-Disposition |
| 304 | Dashboard session only. The current image's ETag matches If-None-Match. No body is produced and object storage is not read. The capture headers name the current capture, which can differ from the stored response's when a later capture encoded to the same bytes. | no body; headers: ETag, Cache-Control, X-ScreenRig-Capture-ID, X-ScreenRig-Captured-At |
| 400 | 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) |
| 401 | 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) |
| 409 | 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) |

### POST /api/screens/{id}/screenshot

**Request Screen Screenshot**

Requests one still screenshot of the named active screen. Latest-wins: a new request replaces any in-flight capture_id and does not 409. The current ready slot is kept until a later upload succeeds. capture_id is an optional environment prefix, then shot_ and a random suffix. expires_at is thirty seconds after accept.

The durable event type is screen.screenshot_requested on the existing runtime SSE stream; runtime scan never delivers it after expires_at. Event details name capture_id, expires_at, max_width_divisor 2, max_height_divisor 2, format image/webp, quality 80, and max_bytes 2097152. Image bytes never appear on SSE.

Prepaid remaining credit is not a gate, and this screen-message path is exempt from the 1-credit API meter. An exact Idempotency-Key retry returns the original capture_id and expires_at for twenty-four hours.


Operation: requestScreenScreenshot.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |
| Idempotency-Key | header | required |  |

#### Request template

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.

~~~sh
curl --request POST --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt --header 'Idempotency-Key: REQUEST_ID' 'https://screenrig.ai/api/screens/RESOURCE_ID/screenshot'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 202 | Accepted. The request is durable until expires_at. An exact idempotent retry returns this same capture_id and expiry. | ScreenScreenshotAccepted (application/json); headers: Cache-Control, RateLimit-Policy, RateLimit |
| 400 | 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) |
| 401 | 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) |
| 409 | 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) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, 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) |

### GET /api/screens/{id}/screenshot/status

**Get Screen Screenshot Status**

Returns the screenshot state machine for the named screen when it exists for this project. idle has no request and no ready slot. pending is the current capture_id before expires_at with no upload. ready means the last successful upload matches the current capture_id. timed_out means the current id reached expires_at with no upload.

Timeout is lazy: the first status GET or next POST after expiry records it and appends screen.screenshot_failed once with reason expired. A timed-out request does not delete an older ready slot. Screen messages are exempt from the 1-credit API meter.


Operation: getScreenScreenshotStatus.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Screenshot status for this project's screen. | ScreenScreenshotStatus (application/json); headers: Cache-Control |
| 401 | 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) |
| 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/screens/{id}/health/history

**Get Screen Health History**

The health a screen reported over time. resolution=raw (default) returns each accepted report in time order, shaped like Screen.health without stale; resolution=hour returns one summary per UTC hour (reports, peak and average temperature, peak load and memory ratio, weakest Wi-Fi signal, whether the display was disconnected, peak crash and renderer-restart counts). from (inclusive) and to (exclusive) default to the last 24 hours; the range is at most 31 days for raw and 93 days for hour.

A page holds at most limit rows (default 100, at most 500) with next_cursor, null at the end. History is kept for the plan's event retention. Raw reports reach this view within seconds, hourly summaries within about 5 minutes. A screen of another project is not_found. Screen.health, the latest report, is unchanged.


Operation: getScreenHealthHistory.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |
| from | query | optional | Inclusive lower bound on the report time. Defaults to 24 hours before to. |
| to | query | optional | Exclusive upper bound on the report time. Defaults to now. |
| resolution | query | optional | raw returns each report; hour returns one summary per UTC hour. |
| limit | query | optional | Page size. |
| cursor | query | optional | next_cursor of the previous page, with the same filters. Opaque. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | A page of reports or hourly summaries. | ScreenHealthHistory (application/json); headers: Cache-Control |
| 400 | 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) |
| 401 | 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) |
| 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/screens/{id}/uptime

**Get Screen Uptime**

Daily availability of one screen over inclusive UTC days (day_to defaults to today and day_from to 29 days before it, at most 366 days). Each row holds the online and offline seconds recorded that day, availability (online over recorded time, null when nothing was recorded) and coverage (the share of the day with a recorded state).

Time with no recorded state is never counted as downtime: it lowers coverage only. The screen's current open interval is included up to now and marks its days live. Days with nothing recorded have no row. Offline is recorded after the screen has been unreachable for 60 seconds. A screen of another project is not_found.


Operation: getScreenUptime.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |
| day_from | query | optional | First UTC day, inclusive. Defaults to 29 days before day_to. With day_to spans at most 366 days. |
| day_to | query | optional | Last UTC day, inclusive. Defaults to today (UTC). |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Daily uptime rows, oldest first. | ScreenUptime (application/json); headers: Cache-Control |
| 400 | 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) |
| 401 | 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) |
| 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/screens/{id}/presence

**Get Screen Presence**

The screen's online and offline transitions, newest first, from its screen.online and screen.offline events whatever the log lane stores. since (inclusive) and until (exclusive) bound the event time; until defaults to now and since to 30 days before it, at most 366 days. Transitions are kept for the plan's event retention.

A page holds at most limit transitions (default 100, at most 500) with next_cursor, null at the end; pass it back as before. An offline transition the server noticed only when the screen reconnected carries detected_on_reconnect, and its at is when the presence lease ended. Offline is recorded after the screen has been unreachable for 60 seconds.

A screen of another project is not_found.


Operation: getScreenPresence.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |
| since | query | optional | Inclusive lower bound on the transition event time. Defaults to 30 days before until. |
| until | query | optional | Exclusive upper bound on the transition event time. Defaults to now. until - since is at most 366 days. |
| limit | query | optional | Page size. A value outside 1-500 is refused with invalid_request naming limit. |
| before | query | optional | next_cursor of the previous page, with the same since and until. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Presence transitions, newest first. | ScreenPresenceHistory (application/json); headers: Cache-Control |
| 400 | 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) |
| 401 | 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) |
| 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/health/trend

**Get Health Trend**

The project's fleet health by UTC hour: how many screens reported, and how many of them were hot (at or above 80 °C), had a disconnected display, or were crashing (3 or more crashes in 24 hours). from (inclusive) and to (exclusive) default to the last 24 hours, at most 93 days; a page holds at most limit rows (default 100, at most 500) with next_cursor, null at the end.

Hours follow reports by up to about 5 minutes.


Operation: getHealthTrend.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| from | query | optional | Inclusive lower bound on the report time. Defaults to 24 hours before to. |
| to | query | optional | Exclusive upper bound on the report time. Defaults to now. |
| limit | query | optional | Page size. |
| cursor | query | optional | next_cursor of the previous page, with the same filters. Opaque. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | A page of hourly fleet health. | HealthTrend (application/json); headers: Cache-Control |
| 400 | 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) |
| 401 | 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/uptime/daily

**Get Project Uptime**

Daily availability of the project's screens over inclusive UTC days (defaults and range as GET /api/screens/{id}/uptime). Each row sums the online and offline seconds of the screens with a recorded state that day; coverage is the share of those screens' day with a recorded state.

Intervals count when they end, so today's row covers intervals that ended so far; the live online count is in the dashboard summary.


Operation: getProjectUptime.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| day_from | query | optional | First UTC day, inclusive. Defaults to 29 days before day_to. With day_to spans at most 366 days. |
| day_to | query | optional | Last UTC day, inclusive. Defaults to today (UTC). |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Daily uptime rows, oldest first. | ProjectUptime (application/json); headers: Cache-Control |
| 400 | 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) |
| 401 | 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/fleet-links

**List Fleet Links**

Lists the project's fleet links, oldest first (at most 50). For a Read only credential each url is empty and url_redacted is true, since the URL creates screens.


Operation: listFleetLinks.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The project's fleet links. | FleetLinkList (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/fleet-links

**Create Fleet Link**

Creates a fleet link: one Player URL (https://play.<domain>/player/#fleet=<secret>) that turns every device opening it into its own screen in this project, without a pairing code. Put it in a device manager, such as a Google Admin console kiosk app.

Each new screen gets the link's tags and playlist, and a label from the device (its administrator asset ID, then location), else the next default label. A Player that already holds a screen never makes another: its key stays the screen's identity across reloads and relaunches.

After a powerwash or reinstall, a device that reports the serial or DUID of a screen a fleet link of this project created recovers that screen (screen.recovered) instead of creating a new one, once the old key has been silent for ten minutes.

The secret travels in the URL fragment, which browsers never send to a server. max_screens caps the screens the link creates; the project's screen limit applies as for any screen. A project has at most 50 fleet links (fleet_link_limit_reached). Appends fleet_link.created.

An exact retry with the same Idempotency-Key returns this same answer for twenty-four hours.


Operation: createFleetLink.

- Authentication: projectBearer OR dashboardSession
- Request body: FleetLinkWrite

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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 |
| --- | --- | --- | --- |
| label | required | string | minLength: 1 maxLength: 80 |
| enabled | optional | boolean | Defaults to true. |
| tags | optional | ScreenTags | Fleet selector tags, 0 to 16 unique exact tags, each matching the media tag grammar. On ScreenPatch the array replaces the whole set and an empty array clears it; a duplicate or malformed tag is invalid_request. Changing tags bumps the screen revision (If-Match guards it) and appends screen.updated with details.tags; it never changes the manifest revision. Tags are never on the runtime manifest and never authorization. |
| playlist_id | optional | string |  |
| max_screens | optional | integer | minimum: 1 maximum: 10000 |

#### Request template

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.

~~~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/fleet-links'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Created, with its url. | FleetLink (application/json); headers: Cache-Control |
| 400 | invalid_request, including an unknown playlist_id or a malformed tag. | Problem (application/problem+json) |
| 409 | fleet_link_limit_reached 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/fleet-links/{id}

**Get Fleet Link**

One fleet link. For a Read only credential url is empty and url_redacted is true.


Operation: getFleetLink.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | One fleet link. | FleetLink (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/fleet-links/{id}

**Update Fleet Link**

Changes any of label, enabled, tags, playlist_id and max_screens; null clears playlist_id or max_screens. A disabled link creates and recovers no screens; a device opening it shows its pairing code instead. Changes apply to screens the link creates from now on, never to screens it already created. Appends fleet_link.updated. If-Match guards the revision.


Operation: updateFleetLink.

- Authentication: projectBearer OR dashboardSession
- Request body: FleetLinkPatch

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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 |
| --- | --- | --- | --- |
| label | optional | string | minLength: 1 maxLength: 80 |
| enabled | optional | boolean |  |
| tags | optional | ScreenTags | Fleet selector tags, 0 to 16 unique exact tags, each matching the media tag grammar. On ScreenPatch the array replaces the whole set and an empty array clears it; a duplicate or malformed tag is invalid_request. Changing tags bumps the screen revision (If-Match guards it) and appends screen.updated with details.tags; it never changes the manifest revision. Tags are never on the runtime manifest and never authorization. |
| playlist_id | optional | string | null clears the playlist. |
| max_screens | optional | integer | null removes the cap. minimum: 1 maximum: 10000 |

#### Request template

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.

~~~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/fleet-links/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Updated fleet link. | FleetLink (application/json); headers: ETag |
| 400 | invalid_request. | 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/fleet-links/{id}

**Delete Fleet Link**

Deletes the fleet link. Its URL stops working at once; screens it created stay, with their playlists and tags. Appends fleet_link.deleted. If-Match guards the revision.


Operation: deleteFleetLink.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | Deleted. | no body |
| 412 | revision_conflict. | 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/fleet-links/{id}/rotate

**Rotate Fleet Link**

Replaces the link's secret and returns the new url. The old URL stops working at once; screens already created keep playing, since a paired Player never presents the link again. Appends fleet_link.updated with details.rotated true. If-Match guards the revision.


Operation: rotateFleetLink.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The fleet link with its new url. | FleetLink (application/json); headers: ETag, Cache-Control |
| 412 | revision_conflict. | 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) |

## Reviews of agent changes

### GET /api/reviews

**List Reviews**

The project's reviews (docs/reviews.md): playlist and screen changes proposed under Propose access, and for an ad network the creatives buyers submitted (kind ad_creative).

Newest first, at most limit (default 50, at most 100) per page; pass next_cursor back as cursor with the same filters. state pending (the default), decided (every other state) or all; kind narrows to one kind.

A dashboard session reads every kind; a bearer reads the kinds its capabilities cover (playlists for playlist_change, screens for screen_change, advertising for ad_creative), and a kind it cannot read is an empty page. pending_count counts the pending reviews of every kind the caller sees.


Operation: listReviews.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| state | query | optional |  |
| kind | query | optional |  |
| limit | query | optional |  |
| cursor | query | optional |  |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | One page of reviews. | ReviewList (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/reviews/{id}

**Get Review**

One review. A playlist change adds preview: the live and the proposed playlist in the stored shape (pages with resolved_media); live is null for a create. A bearer that cannot read the review's kind (see listReviews) gets not_found.


Operation: getReview.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The review. | Review (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/reviews/{id}/locate

**Locate Review**

The project a review belongs to, for the email link: a signed-in person who is a member of that project gets its id and name and switches to it (PUT /api/session/project) before reading the review. Anyone else gets not_found. Works whatever project the session has selected.


Operation: locateReview.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/reviews/RESOURCE_ID/locate'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The review's project. | ReviewLocation (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/reviews/{id}/approve

**Approve Review**

Approves a pending review as a person who can approve (the project owner or a member with can_approve; else approver_required). The flag is read again inside the decision's transaction, so a person whose approval was turned off meanwhile decides nothing (approver_required). Staff shadow sessions cannot decide.

A playlist update is applied with the base revision as If-Match; when the live playlist moved on the review becomes stale and the call answers review_stale.

A create or update whose tag, folder or all selectors would now play media they did not resolve when proposed, that the review does not name and that the live version does not already play, also becomes stale (review_stale).

A screen change replays its stored request (a tag resolves now): the call applies screens for a few seconds and the rest continue in the background while screen_change.result.applying is true.

A screen change waiting on a pending playlist review answers review_blocked naming it; one whose playlist has no approved version answers playlist_not_approved. include_dependents also approves, in order, the screen changes waiting on this playlist review. Events of the applied change carry the person as actor and the proposing agent as agent.

A decided review is review_not_pending; repeating the same decision replays.


Operation: approveReview.

- Authentication: dashboardSession
- Request body: ReviewApproveRequest

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| 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 body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| include_dependents | optional | boolean | Also approve, in order, the screen changes waiting on this playlist review. |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/reviews/RESOURCE_ID/approve'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The approved review, and the dependents' outcomes. | ReviewDecisionResult (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/reviews/{id}/reject

**Reject Review**

Rejects a pending review with a reason the proposer reads. Approver only, as approveReview. Nothing changes on glass; a rejected playlist create stays unapproved.


Operation: rejectReview.

- Authentication: dashboardSession
- Request body: ReviewRejectRequest

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| 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 body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| reason | required | string | minLength: 1 maxLength: 500 |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/reviews/RESOURCE_ID/reject'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The rejected review. | ReviewDecisionResult (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/reviews/{id}/request-changes

**Request Review Changes**

Asks the proposer for changes with a note they read; the proposer proposes again. Approver only, as approveReview. For an ad creative the buyer reads the note on the creative.


Operation: requestReviewChanges.

- Authentication: dashboardSession
- Request body: ReviewChangesRequest

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| 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 body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| note | required | string | minLength: 1 maxLength: 500 |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/reviews/RESOURCE_ID/request-changes'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The review, state changes_requested. | ReviewDecisionResult (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/reviews/{id}/withdraw

**Withdraw Review**

Withdraws a pending playlist or screen review. A bearer withdraws only what its own agent or service client proposed (forbidden otherwise); a person withdraws as an approver or as the proposer's responsible person. A withdrawn playlist create stays unapproved.


Operation: withdrawReview.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The withdrawn review. | ReviewDecisionResult (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) |

## Private advertising networks and campaigns

### GET /api/advertising/network

**Get Advertising Network**

The calling seller's one network, including its resolved ScreenRig serving fee. Sellers cannot change that fee.


Operation: getAdvertisingNetwork.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Network. | AdvertisingNetwork (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/advertising/network

**Create Advertising Network**

Creates the calling seller's one network if absent. Requires screens=true. Idempotent; repeating returns the same network.


Operation: createAdvertisingNetwork.

- Authentication: projectBearer OR dashboardSession
- Request body: AdvertisingNetworkCreate

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| name | required | string | maxLength: 120 |

#### Request template

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.

~~~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/advertising/network'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Network. | AdvertisingNetwork (application/json) |
| 403 | forbidden - the project lacks screen capability or has an unknown plan. | 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/advertising/network/rate

**Set Advertising Default Rate**

Sets the seller's project default rate per 15 seconds of completed playback. A change that alters an accepted campaign's effective rate pauses that whole campaign with price_change_pending; a masked or no-op change does not. If-Match carries the network revision.


Operation: setAdvertisingDefaultRate.

- Authentication: projectBearer OR dashboardSession
- Request body: AdvertisingRate

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| If-Match | header | optional | Optional positive resource revision. Omit to write the current resource; a supplied stale revision is revision_conflict. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| rate_mcr_per_15s | required | McrString | Exact integer millicredit amount as a canonical decimal string. 1 credit is 1000 mcr. pattern: "^-?[0-9]+$" |

#### Request template

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.

~~~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/advertising/network/rate'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Updated network. | AdvertisingNetwork (application/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) |

### GET /api/advertising/inventory

**List Advertising Inventory**

The calling seller's opted-in inventory.


Operation: listAdvertisingInventory.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Inventory. | AdvertisingInventoryList (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) |

### PUT /api/advertising/inventory/{screen_id}

**Put Advertising Inventory**

Upserts one owned screen's inventory. Verifies screen ownership. A rate override of null restores inheritance, not zero. A change that alters an accepted campaign's effective rate pauses that campaign.


Operation: putAdvertisingInventory.

- Authentication: projectBearer
- Request body: AdvertisingInventoryWrite

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| screen_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 |
| --- | --- | --- | --- |
| ads_enabled | required | boolean |  |
| site_name | optional | string | maxLength: 120 |
| city | optional | string | maxLength: 120 |
| region | optional | string | maxLength: 120 |
| venue_type | optional | string | maxLength: 64 |
| audience_tags | optional | array |  |
| placement | optional | string | maxLength: 120 |
| public_description | optional | string | maxLength: 500 |
| rate_override_mcr_per_15s | optional | inline | Null restores inheritance from the project default; it never means zero. |

#### Request template

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.

~~~sh
curl --request PUT --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/advertising/inventory/SCREEN_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Stored inventory. | AdvertisingInventory (application/json) |
| 403 | forbidden - the screen is not owned by this project. | Problem (application/problem+json) |
| 412 | revision_conflict. | 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/advertising/slots

**List Advertising Slots**


Operation: listAdvertisingSlots.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Slots. | AdvertisingSlotList (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/advertising/slots

**Create Advertising Slot**

Creates a reusable seller slot. Images are 5-30 seconds and videos at most the platform ceiling of 120 seconds. Ads are always muted.


Operation: createAdvertisingSlot.

- Authentication: projectBearer OR dashboardSession
- Request body: AdvertisingSlotWrite

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| name | required | string | minLength: 1 maxLength: 120 |
| enabled | optional | boolean |  |
| accepted_media | required | array |  |
| max_image_duration_ms | required | integer | minimum: 5000 maximum: 30000 |
| max_video_duration_ms | required | integer | minimum: 1000 maximum: 120000 |
| rate_override_mcr_per_15s | optional | inline |  |
| vast_tag_url | optional | inline | The seller's own VAST 2-4 ad tag, tried when no invited campaign fills this slot on the seller's own screens. HTTPS on port 443 or 8443 to a public host only. The backend fetches it, follows up to five Wrappers, ingests the chosen progressive MP4 or WebM MediaFile into the project's media, and fires Impression, creativeView and start on an accepted start, complete on an accepted completion, and Error on failure. Quartile trackers are not fired. Macros [CACHEBUSTING], [TIMESTAMP], [SCREENRIG_SCREEN_ID], [SCREENRIG_SLOT_ID] and [SCREENRIG_PAGE_ID] are expanded. The slot must accept video. Null, absent, or empty clears the tag. Buyers never see it. |

#### Request template

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.

~~~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/advertising/slots'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Slotslot. | AdvertisingSlot (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/advertising/slots/{id}

**Update Advertising Slot**

Updates one seller slot. If-Match carries the slot revision. A rate override change re-runs the pricing fence.


Operation: updateAdvertisingSlot.

- Authentication: projectBearer OR dashboardSession
- Request body: AdvertisingSlotWrite

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |
| If-Match | header | optional | Optional positive resource revision. Omit to write the current resource; a supplied stale revision is revision_conflict. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| name | required | string | minLength: 1 maxLength: 120 |
| enabled | optional | boolean |  |
| accepted_media | required | array |  |
| max_image_duration_ms | required | integer | minimum: 5000 maximum: 30000 |
| max_video_duration_ms | required | integer | minimum: 1000 maximum: 120000 |
| rate_override_mcr_per_15s | optional | inline |  |
| vast_tag_url | optional | inline | The seller's own VAST 2-4 ad tag, tried when no invited campaign fills this slot on the seller's own screens. HTTPS on port 443 or 8443 to a public host only. The backend fetches it, follows up to five Wrappers, ingests the chosen progressive MP4 or WebM MediaFile into the project's media, and fires Impression, creativeView and start on an accepted start, complete on an accepted completion, and Error on failure. Quartile trackers are not fired. Macros [CACHEBUSTING], [TIMESTAMP], [SCREENRIG_SCREEN_ID], [SCREENRIG_SLOT_ID] and [SCREENRIG_PAGE_ID] are expanded. The slot must accept video. Null, absent, or empty clears the tag. Buyers never see it. |

#### Request template

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.

~~~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/advertising/slots/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Updated slot. | AdvertisingSlot (application/json) |
| 412 | revision_conflict. | 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/advertising/memberships

**List Advertising Memberships**


Operation: listAdvertisingMemberships.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Memberships. | AdvertisingMembershipList (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/advertising/memberships/{id}

**Update Advertising Membership**

Explicit seller policy/scope edit. An active membership is changed here, never by sending a second invitation. Scope changes stop new selection; financial history is preserved.


Operation: updateAdvertisingMembership.

- Authentication: projectBearer OR dashboardSession
- Request body: AdvertisingMembershipScope

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |
| If-Match | header | optional | Optional positive resource revision. Omit to write the current resource; a supplied stale revision is revision_conflict. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| policy | required | string | Allowed: "trusted", "review_required" |
| screen_ids | required | array |  |
| slot_ids | required | array |  |

#### Request template

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.

~~~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/advertising/memberships/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Updated membership. | AdvertisingMembership (application/json) |
| 412 | revision_conflict. | 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/advertising/memberships/{id}/revoke

**Revoke Advertising Membership**

Revokes one membership. Stops new selection for that network; it does not clear the advertiser flag, other memberships, or settled history.


Operation: revokeAdvertisingMembership.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Revoked membership. | AdvertisingMembership (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/advertising/networks

**List Advertising Joined Networks**

Networks the calling advertiser currently belongs to, with the scope and review policy it was granted. Screen count is not a guaranteed impression count.


Operation: listAdvertisingJoinedNetworks.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Joined networks. | AdvertisingJoinedNetworkList (application/json) |
| 403 | forbidden - the project lacks the advertiser flag. | 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/advertising/networks/{seller_project_id}/inventory

**List Advertising Joined Inventory**

Opted-in inventory and reusable slots inside the caller's membership. Never exposes the seller's screenshots, playlists, or project-wide data.


Operation: listAdvertisingJoinedInventory.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| seller_project_id | path | required |  |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Permitted inventory and slots. | AdvertisingJoinedInventory (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/advertising/creatives

**List Advertising Creatives**


Operation: listAdvertisingCreatives.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Creatives. | AdvertisingCreativeList (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/advertising/creatives

**Create Advertising Creative**

Binds one ready owned media revision to an immutable creative. Does not copy bytes, re-upload, or re-charge generation. Unknown or non-ready media is rejected.


Operation: createAdvertisingCreative.

- Authentication: projectBearer OR dashboardSession
- Request body: AdvertisingCreativeCreate

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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 |
| --- | --- | --- | --- |
| media_id | required | string |  |
| copy | optional | string | maxLength: 500 |

#### Request template

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.

~~~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/advertising/creatives'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Creative. | AdvertisingCreative (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/advertising/creatives/{id}

**Get Advertising Creative**


Operation: getAdvertisingCreative.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Creative. | AdvertisingCreative (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/advertising/campaigns

**List Advertising Campaigns**


Operation: listAdvertisingCampaigns.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Campaigns. | AdvertisingCampaignList (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/advertising/campaigns

**Create Advertising Campaign**

Creates a draft campaign. Drafts hold no money, reserve nothing, and authorize no delivery. Activation requires an explicitly accepted quote.


Operation: createAdvertisingCampaign.

- Authentication: projectBearer OR dashboardSession
- Request body: AdvertisingCampaignDraft

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| name | required | string | minLength: 1 maxLength: 120 |
| daily_cap_mcr | required | McrString | Exact integer millicredit amount as a canonical decimal string. 1 credit is 1000 mcr. pattern: "^-?[0-9]+$" |
| lifetime_cap_mcr | required | McrString | Exact integer millicredit amount as a canonical decimal string. 1 credit is 1000 mcr. pattern: "^-?[0-9]+$" |
| image_duration_ms | optional | integer | Campaign-wide image dwell time. Videos keep their server-verified duration. default: 10000 minimum: 5000 maximum: 30000 |
| max_play_price_mcr | optional | inline | Positive maximum completed-play price. Omit for no additional ceiling. |
| flight_start | required | string | format: "date-time" |
| flight_end | required | string | format: "date-time" |
| networks | required | array |  |

#### Request template

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.

~~~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/advertising/campaigns'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Draft campaign. | AdvertisingCampaign (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/advertising/campaigns/{id}

**Get Advertising Campaign**


Operation: getAdvertisingCampaign.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Campaign. | AdvertisingCampaign (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/advertising/campaigns/{id}

**Update Advertising Campaign**

Updates a draft campaign. If-Match carries the campaign revision. A budget reduction below already spent plus committed reservations is rejected with the minimum permitted value.


Operation: updateAdvertisingCampaign.

- Authentication: projectBearer OR dashboardSession
- Request body: AdvertisingCampaignDraft

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |
| If-Match | header | optional | Optional positive resource revision. Omit to write the current resource; a supplied stale revision is revision_conflict. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| name | required | string | minLength: 1 maxLength: 120 |
| daily_cap_mcr | required | McrString | Exact integer millicredit amount as a canonical decimal string. 1 credit is 1000 mcr. pattern: "^-?[0-9]+$" |
| lifetime_cap_mcr | required | McrString | Exact integer millicredit amount as a canonical decimal string. 1 credit is 1000 mcr. pattern: "^-?[0-9]+$" |
| image_duration_ms | optional | integer | Campaign-wide image dwell time. Videos keep their server-verified duration. default: 10000 minimum: 5000 maximum: 30000 |
| max_play_price_mcr | optional | inline | Positive maximum completed-play price. Omit for no additional ceiling. |
| flight_start | required | string | format: "date-time" |
| flight_end | required | string | format: "date-time" |
| networks | required | array |  |

#### Request template

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.

~~~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/advertising/campaigns/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Updated campaign. | AdvertisingCampaign (application/json) |
| 412 | revision_conflict. | 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/advertising/campaigns/{id}/quote

**Quote Advertising Campaign**

Prices the campaign's current effective rates, formats, durations, flight and caps before activation. The quote is time-bounded and becomes stale when any pricing generation or effective rate changes.


Operation: quoteAdvertisingCampaign.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |
| If-Match | header | optional | Optional positive resource revision. Omit to write the current resource; a supplied stale revision is revision_conflict. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Quote. | AdvertisingQuote (application/json) |
| 412 | revision_conflict. | 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/advertising/campaigns/{id}/activate

**Activate Advertising Campaign**

Explicitly accepts the named current quote and activates the campaign. Atomically checks ownership, membership, quote expiry, rate revisions, accepted scope and same-project denial, then records purchase terms. No wallet debit and no seller revenue occur from accepting terms. The same key with the same body replays; a changed body is a conflict.


Operation: activateAdvertisingCampaign.

- Authentication: projectBearer OR dashboardSession
- Request body: AdvertisingQuoteAccept

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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 |
| --- | --- | --- | --- |
| quote_id | required | string |  |

#### Request template

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.

~~~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/advertising/campaigns/RESOURCE_ID/activate'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Active or pending-review campaign. | AdvertisingCampaign (application/json) |
| 409 | quote_stale or self_deal or resource_conflict. | 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/advertising/campaigns/{id}/pause

**Pause Advertising Campaign**

Pauses new reservations for this campaign. Already-started valid ads normally finish and are charged on completion.


Operation: pauseAdvertisingCampaign.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |
| If-Match | header | optional | Optional positive resource revision. Omit to write the current resource; a supplied stale revision is revision_conflict. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Paused campaign. | AdvertisingCampaign (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/advertising/campaigns/{id}/resume

**Resume Advertising Campaign**

Resumes a manually paused campaign. It cannot clear a price_change_pending block; a top-up, schedule tick, or seller action cannot supply that consent.


Operation: resumeAdvertisingCampaign.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |
| If-Match | header | optional | Optional positive resource revision. Omit to write the current resource; a supplied stale revision is revision_conflict. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Resumed campaign. | AdvertisingCampaign (application/json) |
| 409 | price_change_pending - accept current rates first. | 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/advertising/campaigns/{id}/accept-rates

**Accept Advertising Rates**

The buyer's explicit acceptance of changed effective rates, bound to quote ID, campaign revision and current pricing generations. It atomically replaces future delivery terms, records the actor and time, and clears the sticky price-change block. Another concurrent rate change leaves the campaign paused.


Operation: acceptAdvertisingRates.

- Authentication: projectBearer OR dashboardSession
- Request body: AdvertisingQuoteAccept

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |
| If-Match | header | optional | Optional positive resource revision. Omit to write the current resource; a supplied stale revision is revision_conflict. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| quote_id | required | string |  |

#### Request template

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.

~~~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/advertising/campaigns/RESOURCE_ID/accept-rates'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Reactivated campaign. | AdvertisingCampaign (application/json) |
| 409 | price_change_pending or quote_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) |

### GET /api/advertising/reviews

**List Advertising Reviews**

Creative versions submitted to the calling seller for review. A seller sees only exact submitted creative, never the buyer's library.


Operation: listAdvertisingReviews.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Reviews. | AdvertisingReviewList (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/advertising/reviews/{id}/approve

**Approve Advertising Review**

Approves one exact submitted creative revision. Approval does not choose the buyer's budget or grant access to its media library. A person must be able to approve reviews (approver_required). The same review is also reachable as kind ad_creative under /api/reviews.


Operation: approveAdvertisingReview.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Approved review. | AdvertisingReview (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/advertising/reviews/{id}/reject

**Reject Advertising Review**

Rejects one exact submitted creative revision with a reason the buyer reads on the creative. A person must be able to approve reviews (approver_required).


Operation: rejectAdvertisingReview.

- Authentication: projectBearer OR dashboardSession
- Request body: AdvertisingReviewRejection

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| reason | required | string | minLength: 1 maxLength: 500 |

#### Request template

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.

~~~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/advertising/reviews/RESOURCE_ID/reject'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Rejected review. | AdvertisingReview (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/advertising/reports/spend

**Get Advertising Spend**

Buyer-scoped spend and delivery counts for one campaign. Amounts are decimal mcr strings. It never exposes the seller's income, bank status, or project-wide usage.


Operation: getAdvertisingSpend.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| campaign_id | query | required |  |

#### Request template

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.

~~~sh
curl --request GET --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/advertising/reports/spend?campaign_id=CAMPAIGN_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Spend report. | AdvertisingSpendReport (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/advertising/reports/delivery

**Get Advertising Delivery**

Seller-scoped gross, serving fee and net for one window, with its own delivery evidence. It never exposes the buyer's wallet or other networks. Only authorized completed-play evidence establishes billable delivery. The window is at most 92 days; a wider window is invalid_request.

When it holds more than 5000 settlements the response sets truncated, totals and counts still cover all of them and rows lists only the earliest 5000.


Operation: getAdvertisingDelivery.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| from | query | required |  |
| to | query | required |  |

#### Request template

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.

~~~sh
curl --request GET --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/advertising/reports/delivery?from=FROM&to=TO'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Delivery report. | AdvertisingDeliveryReport (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/advertising/reviews/{id}

**Get Advertising Review**

Seller detail for one review of its network. Returns the review and the exact submitted creative revision. It never exposes the buyer's other media.


Operation: getAdvertisingReview.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Review and its creative. | AdvertisingReviewDetail (application/json) |
| 404 | not_found - the review is unknown to this seller. | 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/advertising/reviews/{id}/content

**Get Advertising Review Content**

Streams only the exact creative revision this seller's own review refers to. Ownership stays with the buyer project; no object key or signed location is published, and no other buyer media is reachable. The ETag is the creative's strong SHA-256 and never changes for a review.

A dashboard session response is private, no-cache, so the browser keeps one copy and revalidates it; a project bearer response stays private, no-store.


Operation: getAdvertisingReviewContent.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |
| Range | header | optional | V1 accepts one byte range only. |
| If-None-Match | header | optional | Strong creative SHA-256 ETag returned by this route. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The review's exact creative bytes. | no body; headers: Accept-Ranges, Content-Length, ETag, Cache-Control |
| 206 | One standards-correct byte range. | no body; headers: Content-Range, ETag, Cache-Control |
| 304 | The creative's ETag matches If-None-Match, checked after the review resolves in the seller project. No body or egress increment is produced. | no body; 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) |

### HEAD /api/advertising/reviews/{id}/content

**Head Advertising Review Content**


Operation: headAdvertisingReviewContent.

- Authentication: projectBearer
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |
| Range | header | optional | V1 accepts one byte range only. |

#### Request template

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.

~~~sh
curl --request HEAD --header 'Authorization: Bearer [SCREENRIG_TOKEN]' 'https://screenrig.ai/api/advertising/reviews/RESOURCE_ID/content'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Metadata only. | no body; headers: Content-Length, 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) |

### POST /api/advertising/reviews/{id}/request-changes

**Request Advertising Review Changes**

The seller asks the buyer for changes to one submitted creative revision. The review becomes changes_requested with the note in reason; the buyer reads it on the creative, and the next review the campaign opens for this seller links previous_review_id. A person must be able to approve reviews (approver_required).


Operation: requestAdvertisingReviewChanges.

- Authentication: projectBearer OR dashboardSession
- Request body: ReviewChangesRequest

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| note | required | string | minLength: 1 maxLength: 500 |

#### Request template

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.

~~~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/advertising/reviews/RESOURCE_ID/request-changes'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The review, state changes_requested. | AdvertisingReview (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) |

## Comments

### GET /api/comment/screen/{id}

**Get Screen Comments**

Opaque agent comments on the named screen. ScreenRig does not read or use them. They are not authorization and are not on the runtime manifest. A screen with no comments returns comments null. Archived screens remain addressable. Authenticated /api control-plane read; costs 1 credit.


Operation: getScreenComments.

- Authentication: projectBearer
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |

#### Request template

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.

~~~sh
curl --request GET --header 'Authorization: Bearer [SCREENRIG_TOKEN]' 'https://api.screenrig.ai/api/comment/screen/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Comments | Comments (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) |

### PUT /api/comment/screen/{id}

**Put Screen Comments**

Replaces opaque agent comments on the named screen. The comments value must be a JSON object whose compact UTF-8 form is at most 1024 bytes. Last-write-wins on the comments field only; revision, updated_at, and the runtime manifest are unchanged.


Operation: putScreenComments.

- Authentication: projectBearer
- Request body: CommentsWrite

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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 body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| comments | required | object | Opaque agent JSON object. Compact UTF-8 serialization must be at most 1024 bytes. Nested objects, arrays, and strings are allowed. Arrays, strings, numbers, booleans, and null are rejected as the comments value itself. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Comments | Comments (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/comment/screen/{id}

**Delete Screen Comments**

Unsets screen comments. Idempotent if already unset. Does not bump revision or remint the runtime manifest.


Operation: deleteScreenComments.

- Authentication: projectBearer
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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

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.

~~~sh
curl --request DELETE --header 'Authorization: Bearer [SCREENRIG_TOKEN]' 'https://api.screenrig.ai/api/comment/screen/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | Unset | 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) |

### GET /api/comment/playlist/{id}

**Get Playlist Comments**

Opaque agent comments on the named playlist. ScreenRig does not read or use them. A playlist with no comments returns comments null. Authenticated /api control-plane read; costs 1 credit.


Operation: getPlaylistComments.

- Authentication: projectBearer
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |

#### Request template

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.

~~~sh
curl --request GET --header 'Authorization: Bearer [SCREENRIG_TOKEN]' 'https://api.screenrig.ai/api/comment/playlist/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Comments | Comments (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) |

### PUT /api/comment/playlist/{id}

**Put Playlist Comments**

Replaces opaque agent comments on the named playlist. Compact UTF-8 JSON of the comments object must be at most 1024 bytes. Last-write-wins on the comments field only; revision and the runtime manifest are unchanged.


Operation: putPlaylistComments.

- Authentication: projectBearer
- Request body: CommentsWrite

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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 body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| comments | required | object | Opaque agent JSON object. Compact UTF-8 serialization must be at most 1024 bytes. Nested objects, arrays, and strings are allowed. Arrays, strings, numbers, booleans, and null are rejected as the comments value itself. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Comments | Comments (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/comment/playlist/{id}

**Delete Playlist Comments**

Unsets playlist comments. Idempotent if already unset. Does not bump revision or remint the runtime manifest.


Operation: deletePlaylistComments.

- Authentication: projectBearer
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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

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.

~~~sh
curl --request DELETE --header 'Authorization: Bearer [SCREENRIG_TOKEN]' 'https://api.screenrig.ai/api/comment/playlist/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | Unset | 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) |

### GET /api/comment/playlist/{id}/page/{page_id}

**Get Playlist Page Comments**

Opaque agent comments on one playlist page, addressed by the page id string. A page with no comments returns comments null. Missing playlist or page is not_found. Authenticated /api control-plane read; costs 1 credit.


Operation: getPlaylistPageComments.

- Authentication: projectBearer
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |
| page_id | path | required | Playlist page id string. |

#### Request template

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.

~~~sh
curl --request GET --header 'Authorization: Bearer [SCREENRIG_TOKEN]' 'https://api.screenrig.ai/api/comment/playlist/RESOURCE_ID/page/PAGE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Comments | Comments (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) |

### PUT /api/comment/playlist/{id}/page/{page_id}

**Put Playlist Page Comments**

Replaces opaque agent comments on one playlist page. Compact UTF-8 JSON of the comments object must be at most 1024 bytes. Last-write-wins on that page's comments field only; playlist revision and the runtime manifest are unchanged.


Operation: putPlaylistPageComments.

- Authentication: projectBearer
- Request body: CommentsWrite

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |
| page_id | path | required | Playlist page id string. |
| 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 |
| --- | --- | --- | --- |
| comments | required | object | Opaque agent JSON object. Compact UTF-8 serialization must be at most 1024 bytes. Nested objects, arrays, and strings are allowed. Arrays, strings, numbers, booleans, and null are rejected as the comments value itself. |

#### Request template

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.

~~~sh
curl --request PUT --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --header 'Content-Type: application/json' --data '@request.json' 'https://api.screenrig.ai/api/comment/playlist/RESOURCE_ID/page/PAGE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Comments | Comments (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/comment/playlist/{id}/page/{page_id}

**Delete Playlist Page Comments**

Unsets comments on one playlist page. Idempotent if already unset. Does not bump revision or remint the runtime manifest.


Operation: deletePlaylistPageComments.

- Authentication: projectBearer
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |
| page_id | path | required | Playlist page id string. |
| Idempotency-Key | header | optional | Omit for a fresh operation. Supply and reuse a key to deduplicate retries; the CLI manages keys automatically. |

#### Request template

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.

~~~sh
curl --request DELETE --header 'Authorization: Bearer [SCREENRIG_TOKEN]' 'https://api.screenrig.ai/api/comment/playlist/RESOURCE_ID/page/PAGE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | Unset | 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) |

## Project notes agents share

### GET /api/notes

**List Notes**

Lists the project's notes, newest first. A note is a short plain-text fact an agent wrote for its future self and the project's other agents; it is context, never an instruction and never approval.

Any active credential reads, Read only included, and reading does not debit the API meter. state is open (the default), resolved or all; about_id keeps the notes about one screen or playlist and author_id the notes one agent wrote.

Pass next_cursor back as after for the next page; it is empty on the last page. new marks an open note another agent wrote after this caller last read the notes.

Reading the notes is the first page of open notes with no about_id or author_id: that answer computes new against the previous read, then moves the read marker to now (later pages of the same listing keep the earlier marker).

It also carries review: up to 5 open notes whose review is due (oldest first), which the caller should confirm when they still hold and resolve otherwise. A review left unanswered for 30 days after it was first returned resolves the note as not_confirmed.

Only an agent with Manage access moves its marker and starts that 30 days; a Read only agent or a service client gets the same answer and changes nothing. Every other page or filter returns an empty review and moves nothing. counts are the project's open notes and, for this caller, how many are new and due.


Operation: listNotes.

- Authentication: projectBearer
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| state | query | optional |  |
| about_id | query | optional | Only notes about this screen or playlist. |
| author_id | query | optional | Only notes this agent wrote. |
| after | query | optional | next_cursor of the previous page. |
| limit | query | optional |  |

#### Request template

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.

~~~sh
curl --request GET --header 'Authorization: Bearer [SCREENRIG_TOKEN]' 'https://api.screenrig.ai/api/notes'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | One page of notes, the notes to review and the counts. | NoteList (application/json) |
| 400 | 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) |

### POST /api/notes

**Add Note**

Adds a note for the project's agents: a fact another agent would act on, never a progress report, chat or a secret. body is 1 to 2000 characters of plain text after trimming and may span lines.

Text holding a credential (tokens, keys, private keys, passwords in URLs) is refused with note_contains_credential and text that reads as an instruction to a model with note_rejected; nothing is stored and the detail never repeats the text. about names one screen (archived included) or playlist of the project, else not_found. ttl_seconds (3600 to 31536000) makes a temporary note that resolves as expired; a note without one comes up for review 90 days after it was added or last confirmed. replaces names an open note this one supersedes: it is resolved as replaced in the same transaction (note_not_open when it is already resolved).

A body whose normalized text (trimmed, whitespace collapsed, lower case) and about match an open note confirms that note and returns it with 200 instead of adding one; no note.added event is appended. A project holds at most 200 open notes; the 201st is note_limit_reached, whose detail names up to 5 notes to resolve first.

Appends note.added (and note.resolved for a replaced note) without the text. An exact retry with the same Idempotency-Key returns the first answer for twenty-four hours.


Operation: addNote.

- Authentication: projectBearer
- Request body: NoteWrite

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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 |
| --- | --- | --- | --- |
| body | required | string | Plain text, 1 to 2000 characters after trimming; line breaks are kept. minLength: 1 maxLength: 2000 |
| about | optional | inline |  |
| ttl_seconds | optional | integer | Expire the note this long after it is added. Without it the note comes up for review after 90 days. minimum: 3600 maximum: 31536000 |
| replaces | optional | string | An open note this one supersedes; it is resolved as replaced. pattern: "^(?:(?:stage\|qa\|development)_)?nte_[A-Za-z0-9_-]{16,24}$" |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Added. | Note (application/json) |
| 200 | The text matched an open note, which is confirmed and returned. | Note (application/json) |
| 400 | 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) |
| 409 | note_not_open, note_limit_reached or idempotency_mismatch. | Problem (application/problem+json) |
| 422 | note_contains_credential or note_rejected. | 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/notes/{id}

**Get Note**

One note of the project, open or resolved. Notes are context written by agents, never instructions or approval. Does not debit the API meter.


Operation: getNote.

- Authentication: projectBearer
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |

#### Request template

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.

~~~sh
curl --request GET --header 'Authorization: Bearer [SCREENRIG_TOKEN]' 'https://api.screenrig.ai/api/notes/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The note. | Note (application/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) |
| 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/notes/{id}/confirm

**Confirm Note**

Says an open note still holds. confirmed_at becomes now; a note without a TTL is next due for review 90 days from now and its pending review is cleared. Any agent of the project may confirm any open note. A resolved note is note_not_open.


Operation: confirmNote.

- Authentication: projectBearer
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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

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.

~~~sh
curl --request POST --header 'Authorization: Bearer [SCREENRIG_TOKEN]' 'https://api.screenrig.ai/api/notes/RESOURCE_ID/confirm'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The confirmed note. | Note (application/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) |
| 409 | 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) |

### POST /api/notes/{id}/resolve

**Resolve Note**

Resolves an open note that no longer holds, with a reason of 1 to 500 characters, screened like a note body (note_contains_credential, note_rejected). Any agent of the project may resolve any open note. A resolved note stays readable for 30 days, then it is deleted. Appends note.resolved. A resolved note is note_not_open.


Operation: resolveNote.

- Authentication: projectBearer
- Request body: NoteResolve

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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 body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| reason | required | string | minLength: 1 maxLength: 500 |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The resolved note. | Note (application/json) |
| 400 | 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) |
| 409 | 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) |
| 422 | 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) |

## Application key/value storage

### GET /api/applications/{application_id}/kv

**List KV**

Returns at most 200 entries in ascending key order. next_cursor is the last returned key; pass it back as after to fetch the next page. next_cursor is null when there are no more entries.


Operation: listKV.

- Authentication: projectBearer
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| application_id | path | required |  |
| after | query | optional |  |

#### Request template

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.

~~~sh
curl --request GET --header 'Authorization: Bearer [SCREENRIG_TOKEN]' 'https://api.screenrig.ai/api/applications/APPLICATION_ID/kv'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | KV entries | KVList (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/applications/{application_id}/kv/{key}

**Get KV**


Operation: getKV.

- Authentication: projectBearer
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| application_id | path | required |  |
| key | path | required |  |

#### Request template

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.

~~~sh
curl --request GET --header 'Authorization: Bearer [SCREENRIG_TOKEN]' 'https://api.screenrig.ai/api/applications/APPLICATION_ID/kv/KEY'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | KV entry | KVEntry (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) |

### PUT /api/applications/{application_id}/kv/{key}

**Put KV**


Operation: putKV.

- Authentication: projectBearer
- Request body: KVWrite

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| application_id | path | required |  |
| key | path | required |  |
| If-Match | header | optional | Optional positive resource revision. Omit to create or overwrite a key; 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 |
| --- | --- | --- | --- |
| value_base64 | required | string | maxLength: 1398104 |
| content_type | optional | string | default: "application/octet-stream" minLength: 1 maxLength: 127 |

#### Request template

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.

~~~sh
curl --request PUT --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --header 'Content-Type: application/json' --data '@request.json' 'https://api.screenrig.ai/api/applications/APPLICATION_ID/kv/KEY'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | KV entry | KVEntry (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/applications/{application_id}/kv/{key}

**Delete KV**


Operation: deleteKV.

- Authentication: projectBearer
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| application_id | path | required |  |
| key | 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

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.

~~~sh
curl --request DELETE --header 'Authorization: Bearer [SCREENRIG_TOKEN]' 'https://api.screenrig.ai/api/applications/APPLICATION_ID/kv/KEY'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | 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) |

### GET /api/applications/{application_id}/kv-webhooks

**List KVWebhooks**

Lists the application's incoming key/value webhooks, oldest first (at most 20), each with the key it writes, its ingest url and last_used_at once it has written. The secret is never returned here.


Operation: listKVWebhooks.

- Authentication: projectBearer
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| application_id | path | required |  |

#### Request template

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.

~~~sh
curl --request GET --header 'Authorization: Bearer [SCREENRIG_TOKEN]' 'https://api.screenrig.ai/api/applications/APPLICATION_ID/kv-webhooks'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The application's key/value webhooks. | KVWebhookList (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/applications/{application_id}/kv-webhooks

**Create KVWebhook**

Creates an incoming key/value webhook: one URL that writes one key of this application, so an outside system (Zapier, Make, a point of sale, a cron job, curl) pushes data to screens with one HTTP POST; see POST /api/applications/{application_id}/kv-webhooks/{webhook_id}/write. The key need not exist yet.

The answer is the only place the secret appears (with the rotate answer): store it in the sending system at once; the server keeps a digest. An application has at most 20 webhooks (kv_webhook_limit_reached). Appends kv_webhook.created. An exact retry with the same Idempotency-Key returns this same answer, secret included, for twenty-four hours.


Operation: createKVWebhook.

- Authentication: projectBearer
- Request body: KVWebhookCreate

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| application_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 body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| key | required | string | minLength: 1 maxLength: 256 pattern: "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$" |
| description | optional | string | What sends to it, for the people reading the list. Trimmed; it must not hold a credential. maxLength: 200 |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Created, with its url and secret. | KVWebhookWithSecret (application/json); headers: Cache-Control |
| 400 | invalid_request, including a malformed key or a description that holds a credential. | Problem (application/problem+json) |
| 404 | not_found — the application is not in this project. | Problem (application/problem+json) |
| 409 | kv_webhook_limit_reached 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) |

### DELETE /api/applications/{application_id}/kv-webhooks/{webhook_id}

**Delete KVWebhook**

Deletes the webhook. Its URL stops working at once; the key it wrote keeps its value. Appends kv_webhook.deleted.


Operation: deleteKVWebhook.

- Authentication: projectBearer
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| application_id | path | required |  |
| webhook_id | path | required | The id of one of the application's key/value webhooks. |
| Idempotency-Key | header | optional | Omit for a fresh operation. Supply and reuse a key to deduplicate retries; the CLI manages keys automatically. |

#### Request template

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.

~~~sh
curl --request DELETE --header 'Authorization: Bearer [SCREENRIG_TOKEN]' 'https://api.screenrig.ai/api/applications/APPLICATION_ID/kv-webhooks/WEBHOOK_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | Deleted. | no body |
| 404 | not_found — the webhook is not on this application. | 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/applications/{application_id}/kv-webhooks/{webhook_id}/rotate-secret

**Rotate KVWebhook Secret**

Replaces the webhook's secret and returns the new one, once. The old secret stops working at once; update the sending system. Appends kv_webhook.secret_rotated. An exact retry with the same Idempotency-Key returns this same answer, secret included, for twenty-four hours.


Operation: rotateKVWebhookSecret.

- Authentication: projectBearer
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| application_id | path | required |  |
| webhook_id | path | required | The id of one of the application's key/value webhooks. |
| Idempotency-Key | header | optional | Omit for a fresh operation. Supply and reuse a key to deduplicate retries; the CLI manages keys automatically. |

#### Request template

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.

~~~sh
curl --request POST --header 'Authorization: Bearer [SCREENRIG_TOKEN]' 'https://api.screenrig.ai/api/applications/APPLICATION_ID/kv-webhooks/WEBHOOK_ID/rotate-secret'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The webhook with its new secret. | KVWebhookWithSecret (application/json); headers: Cache-Control |
| 404 | not_found — the webhook is not on this application. | 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/applications/{application_id}/kv-webhooks/{webhook_id}/write

**Receive KVWebhook**

The URL an outside system posts one key's new value to. It authenticates with the webhook's own secret in the Authorization header only: Bearer <secret>, or HTTP Basic with the webhook id as the username and the secret as the password. A secret in the URL or query is never accepted.

An unknown webhook, one of another application and a wrong secret all answer the same 401. The raw body, at most 1 MiB, becomes the value of the webhook's key, and the Content-Type header its content_type (application/octet-stream when absent; a header that is not one media type is invalid_request).

The write is PUT /api/applications/{application_id}/kv/{key} without If-Match: it replaces the value, increments the revision, is metered and quota-checked like any K/V write, is one billed API request (payment_required when no credit is left), and appends kv.updated.

Idempotency-Key (8 to 200 characters) deduplicates a sender's retries of one write for twenty-four hours, and the same key with another body or content type is idempotency_mismatch; without it every post is a new write. The answer is the key's new metadata, never its value. At most 120 writes per minute per webhook.


Operation: receiveKVWebhook.

- Authentication: kvWebhookBearer OR kvWebhookBasic
- Request body: string

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| application_id | path | required |  |
| webhook_id | path | required | The id of one of the application's key/value webhooks. |
| Idempotency-Key | header | optional | Omit for a fresh operation. Supply and reuse a key to deduplicate retries; the CLI manages keys automatically. |

#### Request template

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.

~~~sh
curl --request POST --header 'Content-Type: */*' --data-binary '@request.bin' 'https://api.screenrig.ai/api/applications/APPLICATION_ID/kv-webhooks/WEBHOOK_ID/write'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The key's new metadata. | KVWebhookWriteResult (application/json); headers: RateLimit-Policy, RateLimit, Cache-Control |
| 400 | invalid_request — the body is larger than 1 MiB, the Content-Type is not one media type, or the Idempotency-Key is malformed. | Problem (application/problem+json) |
| 401 | unauthorized — missing or wrong credentials, or an unknown webhook. WWW-Authenticate names the Bearer and Basic realms. | Problem (application/problem+json) |
| 409 | idempotency_mismatch — the Idempotency-Key was used for another body or content type. | Problem (application/problem+json) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, 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) |

## Card data for built-in cards

### GET /api/card-data

**List Card Data**

Lists the project's card data documents, at most 200 in ascending key order. next_cursor is the last returned key; pass it back as after for the next page, null when there are no more. Card data documents hold the dynamic properties of card placements (menu sections, ticker items, KPI tiles); the placement's data key names one.


Operation: listCardData.

- Authentication: projectBearer
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| after | query | optional |  |

#### Request template

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.

~~~sh
curl --request GET --header 'Authorization: Bearer [SCREENRIG_TOKEN]' 'https://api.screenrig.ai/api/card-data'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Card data documents | CardDataList (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/card-data/{key}

**Get Card Data**

Returns the document and its revision. ETag is the revision, for If-Match on the next write.


Operation: getCardData.

- Authentication: projectBearer
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| key | path | required | Card data key. May not start with feed:. |

#### Request template

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.

~~~sh
curl --request GET --header 'Authorization: Bearer [SCREENRIG_TOKEN]' 'https://api.screenrig.ai/api/card-data/KEY'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Card data document | CardDataEntry (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) |

### PUT /api/card-data/{key}

**Put Card Data**

Creates or replaces a card data document. When card placements in the project's playlists bind the key, the document must hold only each bound card's dynamic properties and validate against them; otherwise the write is refused with card_data_invalid naming the card and the property path, and nothing changes. A key no placement binds accepts any JSON object.

Screens showing a placement that reads the key receive screen.card_data_changed and refetch it in place; the project log records kv.updated with a card_data_entry resource, which never reaches a Player. Limits, quota, metering, revisions and idempotency are the application K/V store's.


Operation: putCardData.

- Authentication: projectBearer
- Request body: CardDataWrite

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| key | path | required | Card data key. May not start with feed:. |
| If-Match | header | optional | Optional positive resource revision. Omit to create or overwrite a key; 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 |
| --- | --- | --- | --- |
| document | required | object | The card data document: a JSON object of at most 1 MiB. When a card placement binds the key it holds only that card's dynamic properties. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Card data document | CardDataEntry (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/card-data/{key}

**Delete Card Data**

Deletes a card data document. A card bound to the key is hidden on glass until a document is written again.


Operation: deleteCardData.

- Authentication: projectBearer
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| key | path | required | Card data key. May not start with feed:. |
| 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

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.

~~~sh
curl --request DELETE --header 'Authorization: Bearer [SCREENRIG_TOKEN]' 'https://api.screenrig.ai/api/card-data/KEY'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | 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) |

## Durable project events

### GET /api/events

**List Events**

Lists durable project events ascending by sequence.

Paging is contiguous: pass the returned next_cursor as after to continue exactly where the page stopped. next_cursor is the cursor of the last returned event while newer events already exist, and null at the end of the history. limit defaults to 50 and accepts 1-200; a value outside that range is refused with invalid_request and an errors[] member naming limit.

Offset paging is not supported. source=log lists the Player log instead: the condition reports Players send (runtime.reported) ascending by receipt time, filtered by screen_id and severity over a window from to (default the last 24 hours, at most 31 days).

Its cursors are opaque: lg1_ or ev1_ by where the server keeps the Player log, so pass back the one a page returned; a cursor of the other kind is refused with invalid_request naming the field. Player log records have no sequence, are not on GET /stream/project and are not delivered to webhooks.

A record is acknowledged when the Player log queue accepts it and is kept for the project's event retention. A poll with the last cursor and the default window follows the log. Player log records are not in the default (source=project) list. screen_id, from and to are refused with invalid_request unless source is log.

Both sources take resource_id, type and severity (a minimum), combined with each other and with paging; a Player log record is a runtime.reported record whose resource is its screen, so a resource_id that is not that screen or a type that excludes runtime.reported answers an empty page. before (or order=desc) pages newest first: next_cursor is then the cursor of the oldest returned item and passed back as before continues to older items.

Without from, a newest-first source=log page reaches past the default 24 hours to the oldest record the project's event retention keeps, at most 31 days below the page's upper edge per request. A newest-first source=log page may hold fewer than limit records, even none, with a next_cursor; null still ends it, at the end of the retained log.


Operation: listEvents.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| after | query | optional | A cursor from a previous response: ev1_ for project events (and the event streams). The Player log (source=log) pages with lg1_ or ev1_ cursors by where the server keeps it; treat them as opaque and pass back the one a page returned. A cursor of another kind is refused with invalid_request. |
| before | query | optional | Pages newest first, strictly older than this cursor: a next_cursor of a newest-first page, or any item cursor (ev1_ for project events; for source=log the opaque cursor its items carry). Empty or absent with order=desc starts at the newest item. |
| order | query | optional | asc pages oldest first with after; desc pages newest first with before. before implies desc; order=asc with before is refused. |
| limit | query | optional | Page size. A value outside 1-200 is refused with invalid_request and an errors[] member whose field is limit. |
| source | query | optional | project lists the sequenced project events. log lists the Player log: Player condition reports (runtime.reported), ascending by receipt time. |
| resource_id | query | optional | Only items whose resource.id is this id (a screen, playlist, media, webhook, agent or application id). With source=log it is the screen of the record. |
| type | query | optional | An exact event type such as playlist.updated, or a family prefix ending in a dot such as playlist. for every playlist.* type. |
| screen_id | query | optional | With source=log, only records of this screen. |
| severity | query | optional | Minimum severity: info is every item, warning keeps warnings and errors, error keeps errors. Both sources. |
| from | query | optional | With source=log, the start of the window (inclusive). Defaults to 24 hours before to; a newest-first page without from reaches back to the project's event retention instead. |
| to | query | optional | With source=log, the end of the window (exclusive). Defaults to now. The window is at most 31 days. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Durable events (source=project) or Player log records (source=log) | EventList (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 /stream/project

**Stream Events**

Replayable project listen stream. Player log records (runtime.reported) are read with GET /api/events?source=log and are not on this stream. Opening this request costs 1 credit as the listen subscribe. Credit headers are omitted. Later billed events cost 1 credit each; screen.*, runtime.*, and application.event are exempt screen-conversation messages.

Heartbeats and the retry preamble never charge. stream.cursor and stream.resync_required are billed. Debit idempotency is sse-event:<project_id>:<sequence>, so reconnect replay is free. An unpaid billed event is not written and the stream closes.

A stream opened with a dashboard session bills nothing (neither the open nor its events), like the dashboard's API requests; it still takes one of the project's stream slots.


Operation: streamEvents.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| after | query | optional | A cursor from a previous response: ev1_ for project events (and the event streams). The Player log (source=log) pages with lg1_ or ev1_ cursors by where the server keeps it; treat them as opaque and pass back the one a page returned. A cursor of another kind is refused with invalid_request. |
| Last-Event-ID | header | optional | Durable cursor used when after is absent. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Replayable SSE stream. The query cursor takes precedence over Last-Event-ID. Invalid, future, or pre-retention cursors receive stream.resync_required at the current head and close. No ScreenRig-Credits-* headers. | string (text/event-stream) |
| 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) |
| 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) |

## Webhooks: signed event delivery

### GET /api/webhooks

**List Webhooks**

Lists the project's webhooks, oldest first (at most 10). The signing secret is never returned here. Requires the project capability; a reports-only credential is refused 403 forbidden, because the response includes receiver URLs.


Operation: listWebhooks.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The project's webhooks. | WebhookList (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/webhooks

**Create Webhook**

Creates a webhook that receives the project's own durable events as signed HTTPS POSTs. url must be https on a public Internet host: an IP literal or every current DNS answer of the name must be public (private, loopback, link-local, CGNAT, IPv6 outside 2000::/3, documentation, and other reserved ranges are refused with webhook_url_rejected).

The port must be 443 (the default) or 8443, else webhook_url_rejected. The host is resolved and checked again before every delivery and the connection is dialed to exactly the checked address, so a DNS answer that changes later cannot redirect deliveries. event_types lists exact event types (screen.online) or prefixes ending in .* (screen.*).

A project has at most 10 webhooks (webhook_limit_reached). The server generates the signing secret and returns it only in this answer and in rotate-secret; store it. An exact retry with the same Idempotency-Key returns this same answer, secret included, for twenty-four hours. Delivery starts with the first event after webhook.created.


Operation: createWebhook.

- Authentication: projectBearer
- Request body: WebhookWrite

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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 |
| --- | --- | --- | --- |
| url | required | string | https URL on a public Internet host, port 443 (the default) or 8443. No userinfo or fragment. maxLength: 2048 format: "uri" |
| event_types | required | WebhookEventTypes | Exact event types such as screen.online, or prefixes ending in .* such as screen.* (every type starting with screen.). webhook.test is never fanned out by a subscription; it is sent only by POST /api/webhooks/{id}/test. |
| enabled | optional | boolean | default: true |
| description | optional | string | maxLength: 200 |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Created. secret is shown only here and on rotate-secret. | WebhookWithSecret (application/json); headers: Cache-Control |
| 400 | invalid_request or webhook_url_rejected. | Problem (application/problem+json) |
| 409 | webhook_limit_reached 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/webhooks/{id}

**Get Webhook**

Requires the project capability; a reports-only credential is refused 403 forbidden, because the response includes receiver URLs.


Operation: getWebhook.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | One webhook. | Webhook (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/webhooks/{id}

**Update Webhook**

Changes any of url, event_types, enabled, and description (an empty description clears it). A new url is checked like create.

Setting enabled true on a disabled webhook clears its failure state and starts delivery at the event that records the change; events from while it was disabled are not replayed, and deliveries still pending from before the disable are failed with webhook_disabled. Setting enabled false fails its pending deliveries with webhook_disabled.

Appends webhook.updated. If-Match guards the revision.


Operation: updateWebhook.

- Authentication: projectBearer
- Request body: WebhookPatch

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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 |
| --- | --- | --- | --- |
| url | optional | string | maxLength: 2048 format: "uri" |
| event_types | optional | WebhookEventTypes | Exact event types such as screen.online, or prefixes ending in .* such as screen.* (every type starting with screen.). webhook.test is never fanned out by a subscription; it is sent only by POST /api/webhooks/{id}/test. |
| enabled | optional | boolean |  |
| description | optional | string | An empty string clears the description. maxLength: 200 |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Updated webhook. | Webhook (application/json); headers: ETag |
| 400 | invalid_request or webhook_url_rejected. | 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/webhooks/{id}

**Delete Webhook**

Deletes the webhook and appends webhook.deleted. Its pending deliveries are failed with webhook_deleted; its delivery log is no longer addressable and expires with event retention.


Operation: deleteWebhook.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | Deleted. | no body; headers: Cache-Control |
| 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/webhooks/{id}/rotate-secret

**Rotate Webhook Secret**

Replaces the signing secret and returns the new one; the old secret stops signing at once: every attempt that signs after this commits uses the new secret, and at most one attempt per webhook already in flight may still arrive signed with the old one, so accept both briefly. Appends webhook.updated with details.changed [secret].

An exact retry with the same Idempotency-Key returns the same new secret for twenty-four hours.


Operation: rotateWebhookSecret.

- Authentication: projectBearer
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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

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.

~~~sh
curl --request POST --header 'Authorization: Bearer [SCREENRIG_TOKEN]' 'https://api.screenrig.ai/api/webhooks/RESOURCE_ID/rotate-secret'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The webhook and its new secret. | WebhookWithSecret (application/json); headers: Cache-Control |
| 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/webhooks/{id}/test

**Test Webhook**

Appends a webhook.test project event and queues one delivery of it to this webhook only (no other webhook receives webhook.test). It works on a disabled webhook, is attempted once without retries, is metered like any delivery, and never changes the webhook's failure state. Poll the deliveries log for its outcome.


Operation: testWebhook.

- Authentication: projectBearer
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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

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.

~~~sh
curl --request POST --header 'Authorization: Bearer [SCREENRIG_TOKEN]' 'https://api.screenrig.ai/api/webhooks/RESOURCE_ID/test'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 202 | The queued test delivery. | WebhookDelivery (application/json); headers: RateLimit-Policy, RateLimit |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, 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) |

### GET /api/webhooks/{id}/deliveries

**List Webhook Deliveries**

The webhook's delivery log, newest event first. One row per (webhook, event); retries update the row and GET .../deliveries/{delivery_id}/attempts lists its most recent attempts. Rows are retained like events (the plan's event retention, at least the 24-hour retry window). Pass next_cursor back as before for the next page.


Operation: listWebhookDeliveries.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |
| before | query | optional | next_cursor of the previous deliveries page. |
| limit | query | optional |  |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | One page of deliveries. | WebhookDeliveryList (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/webhooks/{id}/deliveries/{delivery_id}/attempts

**List Webhook Delivery Attempts**

One delivery's attempts, newest first, the most recent 20, kept with the delivery row and for as long. Each has its attempt number, when it finished, the receiver's HTTP status (null when none answered), the error class (null on success) and its duration. Response bodies and headers are never kept. A delivery recorded before attempts were kept lists none.

A delivery that is not this webhook's, or a webhook of another project, is not_found.


Operation: listWebhookDeliveryAttempts.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |
| delivery_id | path | required | The id of one of the webhook's deliveries. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The delivery's kept attempts. | WebhookDeliveryAttemptList (application/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) |
| 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) |

## CAP alert triggers

### GET /api/triggers

**List Triggers**

Lists the project's CAP alert triggers, oldest first (at most 20), each with its ingest url, username and latest alert. The password is never returned here.


Operation: listTriggers.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The project's triggers. | TriggerList (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/triggers

**Create Trigger**

Creates a CAP alert trigger: one inbound URL an emergency notification system (Everbridge, Rave, Alertus, IPAWS relays and other CAP 1.2 senders) posts alerts to, with HTTP Basic credentials.

The answer is the only place the password appears (with the rotate answer): store it in the sending system at once. target is a tag (every active screen with it when the alert arrives) or up to 500 screen_ids.

An alert takes every target screen with a default playlist over until the alert's <expires>, else for default_for_seconds (60 to 604800, default 3600); see POST /api/triggers/{id}/cap. A project has at most 20 triggers (trigger_limit_reached). Appends trigger.created.

An exact retry with the same Idempotency-Key returns this same answer, password included, for twenty-four hours.


Operation: createTrigger.

- Authentication: projectBearer OR dashboardSession
- Request body: TriggerWrite

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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 |
| --- | --- | --- | --- |
| label | required | string | minLength: 1 maxLength: 80 |
| target | required | TriggerTarget | Exactly one of tag and screen_ids. |
| default_for_seconds | optional | integer | How long an alert without <expires> holds the screens. Defaults to 3600. minimum: 60 maximum: 604800 |

#### Request template

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.

~~~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/triggers'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Created, with its url, username and password. | TriggerWithPassword (application/json); headers: ETag, Cache-Control |
| 400 | invalid_request, including a malformed tag or both tag and screen_ids. | Problem (application/problem+json) |
| 409 | trigger_limit_reached 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/triggers/{id}

**Get Trigger**

One trigger with its latest alert. An alert past its until reads as ended with ended_reason expired.


Operation: getTrigger.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | One trigger. | Trigger (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) |

### DELETE /api/triggers/{id}

**Delete Trigger**

Deletes the trigger. Its URL stops working at once, and its alert playlist and stills are deleted. A trigger showing an alert, or still clearing one, is refused resource_conflict; clear it first. Appends trigger.deleted. If-Match guards the revision.


Operation: deleteTrigger.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | Deleted. | no body |
| 409 | resource_conflict — the trigger is showing an alert. | Problem (application/problem+json) |
| 412 | revision_conflict. | 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/triggers/{id}/rotate-secret

**Rotate Trigger Secret**

Replaces the trigger's password and returns the new one, once. The old password stops working at once; update the sending system. Appends trigger.updated with details.rotated true. If-Match guards the revision.


Operation: rotateTriggerSecret.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The trigger with its new password. | TriggerWithPassword (application/json); headers: ETag, Cache-Control |
| 412 | revision_conflict. | 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/triggers/{id}/test

**Test Trigger**

Shows a one-minute Exercise alert (a blue card with a yellow EXERCISE banner) on the trigger's screens, exactly as a received alert would, so the targeting and the screens can be checked without the sending system. Refused resource_conflict while the trigger shows an alert, so a test never replaces a real one.

An exact retry with the same Idempotency-Key answers outcome duplicate.


Operation: testTrigger.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| 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

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | What the test alert did. | TriggerReceipt (application/json) |
| 409 | resource_conflict — the trigger is showing an alert. | 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/triggers/{id}/clear

**Clear Trigger**

Ends the trigger's alert (ended_reason cleared) and clears the takeovers it set. A screen another takeover has replaced since is left alone. A trigger without an active alert answers unchanged. Appends trigger.alert_received with outcome ended.


Operation: clearTrigger.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The trigger. alert.screens.pending is true while the takeovers are being cleared. | Trigger (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) |

### POST /api/triggers/{id}/cap

**Receive Trigger CAP**

The URL an emergency notification system posts one CAP 1.2 alert to (any XML content type, at most 256 KiB). It authenticates with the trigger's own HTTP Basic credentials: the username is the trigger id, the password the one create or rotate returned.

A message with status Actual or Exercise and msgType Alert shows a full-screen card (headline, event, description, instruction and sender, red for Extreme, Severe or no severity, amber for Moderate, blue for Minor; Exercise adds a yellow EXERCISE banner) on every target screen with a default playlist, until <expires>, else for default_for_seconds, at most 7 days.

An Update that references the shown alert redraws it in place and moves the takeovers when <expires> changed. A Cancel that references it, or any message with responseType AllClear, ends it and clears the takeovers it set. A trigger shows one alert: a new Alert replaces the shown one.

Test, System and Draft messages, Ack and Error, an alert already expired, and a Cancel of an alert not shown are ignored; the same sender and identifier twice is a duplicate. The answer is a receipt naming the outcome; the screens are written right after it (alert.screens.pending).

The takeovers bypass the screen-control rate limits and are never metered or refused for balance. Appends trigger.alert_received.


Operation: receiveTriggerCAP.

- Authentication: triggerBasic
- Request body: string

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |

#### Request template

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.

~~~sh
curl --request POST --header 'Content-Type: application/xml' --data-binary '@request.bin' 'https://api.screenrig.ai/api/triggers/RESOURCE_ID/cap'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | What the message did. | TriggerReceipt (application/json); headers: RateLimit-Policy, RateLimit, Cache-Control |
| 400 | invalid_request — the body is not a usable CAP 1.2 alert, or is larger than 256 KiB. | Problem (application/problem+json) |
| 401 | unauthorized — missing or wrong credentials, or an unknown trigger. WWW-Authenticate names the Basic realm. | Problem (application/problem+json) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, 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) |

## Playback aggregates

### GET /api/playback

**List Playback Aggregates**

Daily playback aggregates.

JSON returns at most 200 rows and is not rate limited. format=csv (or Accept text/csv without application/json) streams every matching row inside day_from to day_to (inclusive UTC days; defaults day_to today and day_from 30 days earlier; at most 366 days, else invalid_request) as CSV with a fixed header row (PlaybackAggregateCsv); only the CSV form spends the playback-export-project (30 per minute) and playback-export-ip (60 per minute) budgets.

Either form is one metered API request. Responses carry Vary Accept.


Operation: listPlaybackAggregates.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| screen_id | query | optional |  |
| media_id | query | optional |  |
| day | query | optional | UTC calendar day of the aggregate. |
| day_from | query | optional | First UTC day, inclusive. Excludes day. With day_to spans at most 366 days. CSV defaults it to 30 days before day_to. |
| day_to | query | optional | Last UTC day, inclusive. Excludes day. CSV defaults it to today (UTC). |
| format | query | optional | json (default) or csv. Omitted, an Accept header naming text/csv but not application/json selects csv. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Daily playback aggregates for the calling project, images and videos alike. One row per screen, media, and UTC day. Newest days first. Identifiers filter the caller's own rows and are never a cross-project lookup. | PlaybackAggregateList (application/json), PlaybackAggregateCsv (text/csv); headers: Vary, RateLimit-Policy, RateLimit |
| 400 | 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) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, 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) |

### GET /api/playback/plays

**List Playback Plays**

Per-play records: one row per accepted playback.media_started (or legacy playback.video_started) and one row per accepted playback.application_started (release_id set, media fields empty) of the calling project, oldest first by received_at, ties by insertion. from (inclusive) and to (exclusive) bound received_at; to defaults to now, from to 24 hours before to, and the range is at most 31 days (invalid_request otherwise).

The newest 5 seconds are not returned yet: the effective to is at most now minus 5 seconds (settle lag), so a live tail never skips a row that commits behind its cursor. screen_id, media_id, playlist_id (the playlist the start was shown from), and tag (a screen tag the screen carried when the play was received) filter.

A playlist_id page may hold fewer than limit rows, even none, with a next_cursor when a long stretch of plays of other playlists was scanned; null still ends the range.

JSON is a page of at most limit rows (default 200, at most 1000) with next_cursor, null at the end of the range; pass it back as cursor with the same filters. order=desc (or before) pages newest first within the same range: next_cursor is then the cursor of the oldest returned row, null at the start of the range, and is passed back as before (exclusive) with the same filters; order=asc with before is refused with invalid_request naming order, and cursor with a newest-first page naming cursor.

A newest-first page may hold fewer than limit rows, even none, with a next_cursor; null still ends it.

A later page never skips a row that existed when the earlier page was read and does not repeat one, except that a start the Player re-sent after an unacknowledged report (the same play_id, received a moment later) can appear again on a later page; the CSV stream lists it once. format=csv (or Accept text/csv without application/json) streams every play from cursor to the end of the range as CSV (PlaybackPlayCsv), newest first below before with order=desc; limit is refused with CSV.

Metering is per request: every JSON page costs one API request credit, while one CSV stream of the whole range costs one. Every plays request (page or stream) spends the playback-export-project (30 per minute) and playback-export-ip (60 per minute) budgets.

A stream bounds each write (15 seconds) and ends when the client disconnects; if it fails after the 200 header the connection is aborted, so an export that reads to a clean end is complete. Responses carry Vary Accept. Plays are kept for the plan's event retention (30 days is the shipped floor; plan-based retention uses the plan's own 7- or 90-day window).


Operation: listPlaybackPlays.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| from | query | optional | Inclusive lower bound on received_at. Defaults to 24 hours before to. |
| to | query | optional | Exclusive upper bound on received_at. Defaults to now. to - from is at most 31 days. |
| screen_id | query | optional |  |
| media_id | query | optional |  |
| playlist_id | query | optional | Only plays shown from this playlist. |
| tag | query | optional | A screen tag the screen carried when the play was received. |
| cursor | query | optional | next_cursor of the previous page, with the same filters. Opaque. |
| order | query | optional | asc pages oldest first with cursor; desc pages newest first with before. before implies desc; order=asc with before is refused. |
| before | query | optional | Pages newest first, strictly older than this position: the next_cursor of a newest-first page, with the same filters. Opaque. |
| limit | query | optional | JSON page size. Refused with CSV. |
| format | query | optional | json (default) or csv. Omitted, an Accept header naming text/csv but not application/json selects csv. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | A page of plays, or the CSV stream. | PlaybackPlayList (application/json), PlaybackPlayCsv (text/csv); headers: Cache-Control, Vary, RateLimit-Policy, RateLimit |
| 400 | 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) |
| 401 | 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) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, 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) |

### GET /api/playback/totals

**Get Playback Totals**

Play totals of the calling project over inclusive UTC days, read from the daily rollups: by=day returns one row per day with plays (screen_id or media_id narrows it to one screen or media), by=screen and by=media rank the busiest screens or media (top, at most 100), and by=screen_day returns the day series of the top screens (top at most 10). day_to defaults to today (UTC) and day_from to 29 days before it.

A day series spans at most 366 days (92 when narrowed to a screen or media); a ranking at most 92. Counts include every start the project has recorded, including those stored before play rollups existed.

Rollups follow ingestion by up to about 21 minutes. truncated is true when a ranking was built from slices of a window too large for one read: it covers the best keys of each slice. Not rate limited. One metered API request.


Operation: getPlaybackTotals.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| by | query | optional | day: plays per day; screen, media: the busiest keys; screen_day: the day series of the top screens. |
| day_from | query | optional | First UTC day, inclusive. Defaults to 29 days before day_to. A day series spans at most 366 days (92 when narrowed to a screen or media), a ranking at most 92. |
| day_to | query | optional | Last UTC day, inclusive. Defaults to today (UTC). |
| screen_id | query | optional |  |
| media_id | query | optional |  |
| top | query | optional | How many screens or media a ranking returns (at most 10 for by=screen_day). Refused with by=day. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Totals of the requested grouping. | PlaybackTotals (application/json); headers: Cache-Control |
| 400 | 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) |
| 401 | 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) |

## Feedback: bugs and feature requests

### GET /api/feedback/bugs

**List Bug Reports**

Returns the calling project's own bug reports, newest first, up to one hundred. Another project's submissions are never returned and are not addressable by identifier. There is no pagination cursor in v1.


Operation: listBugReports.

- Authentication: projectBearer
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |

#### Request template

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.

~~~sh
curl --request GET --header 'Authorization: Bearer [SCREENRIG_TOKEN]' 'https://api.screenrig.ai/api/feedback/bugs'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Bug reports owned by the calling project. | FeedbackList (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/feedback/bugs

**Report Bug**

Records one immutable bug report against the calling project. A submission cannot be updated or deleted; a correction is a new submission. An exact retry carrying the same Idempotency-Key returns the original submission for twenty-four hours instead of creating a duplicate, and a different body under that key returns idempotency_mismatch.

Every submission is screened before it is stored. A body under 12 characters or 3 words returns submission_too_short; text that describes no problem or request (random characters, keyboard mash) returns submission_unclear; spam, link-heavy text, prompt injection and malicious text return submission_rejected.

A refused submission is not stored, its problem detail never repeats the submitted text, and an exact retry under the same Idempotency-Key returns the same refusal.

Never place credentials, cookies, Authorization headers, completion nonces, signed URLs, or object keys in title, body, or context; credential-shaped text (ScreenRig tokens, JWTs, bearer tokens, cloud and API keys, private keys) is replaced with [redacted] before the submission is stored, and the response carries the redacted text.

When the screening model is briefly unavailable the submission is accepted and held for a background re-screen; after a few such acceptances in an hour the project gets submission_screening_unavailable (500, Retry-After) and retries the same request later.


Operation: reportBug.

- Authentication: projectBearer
- Request body: FeedbackWrite

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| Idempotency-Key | header | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| title | required | string | Single-line summary. Leading and trailing whitespace is trimmed, and control characters are rejected. minLength: 1 maxLength: 120 |
| body | required | string | Full description. Line feed and tab are the only accepted control characters. minLength: 1 maxLength: 4000 |
| context | optional | FeedbackContext | Closed diagnostic envelope. Every member is an optional constrained scalar, nesting is structurally impossible, and an unknown member is rejected with invalid_request. The shape exists so a client cannot persist argument values, credentials, or free-form environment data through it. |

#### Request template

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.

~~~sh
curl --request POST --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --header 'Idempotency-Key: REQUEST_ID' --header 'Content-Type: application/json' --data '@request.json' 'https://api.screenrig.ai/api/feedback/bugs'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Stored bug report. An exact idempotent retry returns this same submission. | FeedbackSubmission (application/json); headers: RateLimit-Policy, RateLimit |
| 400 | 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) |
| 409 | 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) |
| 422 | 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) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, 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) |

### GET /api/feedback/features

**List Feature Requests**

Returns the calling project's own feature requests, newest first, up to one hundred. Another project's submissions are never returned and are not addressable by identifier. There is no pagination cursor in v1.


Operation: listFeatureRequests.

- Authentication: projectBearer
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |

#### Request template

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.

~~~sh
curl --request GET --header 'Authorization: Bearer [SCREENRIG_TOKEN]' 'https://api.screenrig.ai/api/feedback/features'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Feature requests owned by the calling project. | FeedbackList (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/feedback/features

**Request Feature**

Records one immutable feature request against the calling project. A submission cannot be updated or deleted; a correction is a new submission. An exact retry carrying the same Idempotency-Key returns the original submission for twenty-four hours instead of creating a duplicate, and a different body under that key returns idempotency_mismatch.

Every submission is screened before it is stored. A body under 12 characters or 3 words returns submission_too_short; text that describes no problem or request (random characters, keyboard mash) returns submission_unclear; spam, link-heavy text, prompt injection and malicious text return submission_rejected.

A refused submission is not stored, its problem detail never repeats the submitted text, and an exact retry under the same Idempotency-Key returns the same refusal.

Never place credentials, cookies, Authorization headers, completion nonces, signed URLs, or object keys in title, body, or context; credential-shaped text (ScreenRig tokens, JWTs, bearer tokens, cloud and API keys, private keys) is replaced with [redacted] before the submission is stored, and the response carries the redacted text.

When the screening model is briefly unavailable the submission is accepted and held for a background re-screen; after a few such acceptances in an hour the project gets submission_screening_unavailable (500, Retry-After) and retries the same request later.


Operation: requestFeature.

- Authentication: projectBearer
- Request body: FeedbackWrite

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| Idempotency-Key | header | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| title | required | string | Single-line summary. Leading and trailing whitespace is trimmed, and control characters are rejected. minLength: 1 maxLength: 120 |
| body | required | string | Full description. Line feed and tab are the only accepted control characters. minLength: 1 maxLength: 4000 |
| context | optional | FeedbackContext | Closed diagnostic envelope. Every member is an optional constrained scalar, nesting is structurally impossible, and an unknown member is rejected with invalid_request. The shape exists so a client cannot persist argument values, credentials, or free-form environment data through it. |

#### Request template

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.

~~~sh
curl --request POST --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --header 'Idempotency-Key: REQUEST_ID' --header 'Content-Type: application/json' --data '@request.json' 'https://api.screenrig.ai/api/feedback/features'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Stored feature request. An exact idempotent retry returns this same submission. | FeedbackSubmission (application/json); headers: RateLimit-Policy, RateLimit |
| 400 | 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) |
| 409 | 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) |
| 422 | 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) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, 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) |

## Support conversations

### GET /api/support/status

**Support Status**

Availability and hours for any active project. chat_allowed is true for every active project, including Standard.


Operation: supportStatus.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Support result. | SupportStatus (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/support/conversations

**List Support Conversations**

Project conversations, newest first, at most 100. Staff unread counts are per authenticated person or agent.


Operation: listSupportConversations.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| before | query | optional | Conversation ID from the preceding page's next cursor. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Support result. | SupportConversationList (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/support/conversations

**Create Support Conversation**

Available on every plan. Screened before storage or Slack. An opening message under 8 characters or 3 words returns submission_too_short; one that is not a question or problem returns submission_unclear; spam, link-heavy text, prompt injection and unsafe text return submission_rejected.

Rejected text is not retained or sent to Luna, and the problem detail never repeats it. Credential-shaped text other than ScreenRig credentials (bearer tokens, cloud and API keys, private keys) is replaced with [redacted] before storage; ScreenRig credential material returns invalid_request. A new conversation starts one Slack thread; follow-ups stay in it.

Requests with the same key replay for 24 hours, a refusal included.


Operation: createSupportConversation.

- Authentication: projectBearer OR dashboardSession
- Request body: SupportWrite

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| Idempotency-Key | header | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| body | required | string | minLength: 1 maxLength: 4000 |
| human_requested | optional | boolean | Pause Luna and wait for a human for this conversation. default: false |

#### Request template

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.

~~~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/support/conversations'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Support result. | SupportSubmission (application/json) |
| 422 | The message was refused by screening: submission_too_short or submission_unclear (the opening message only) or submission_rejected. The detail never repeats the text. | 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/support/conversations/{id}/messages

**List Support Messages**

Messages in project conversation, ascending sequence, at most 100. Continue after the last message sequence for older conversation pagination. Reading does not acknowledge staff unread state.


Operation: listSupportMessages.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |
| after | query | optional | Project support message sequence; resumes strictly after this position. |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Support result. | SupportMessageList (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/support/conversations/{id}/messages

**Reply Support Conversation**

Adds a customer message to an open conversation. A follow-up has no minimum length and is never refused as unclear on its own ("thanks", "yes"); it is screened for safety, prompt injection and link spam, refused with submission_rejected, and credential-shaped text is handled as for a new conversation.

Rejected text is not retained, and a refusal replays under the same key for 24 hours.


Operation: replySupportConversation.

- Authentication: projectBearer OR dashboardSession
- Request body: SupportWrite

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |
| Idempotency-Key | header | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| body | required | string | minLength: 1 maxLength: 4000 |
| human_requested | optional | boolean | Pause Luna and wait for a human for this conversation. default: false |

#### Request template

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.

~~~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/support/conversations/RESOURCE_ID/messages'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Support result. | SupportSubmission (application/json) |
| 422 | The message was refused by screening: submission_too_short or submission_unclear (the opening message only) or submission_rejected. The detail never repeats the text. | 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) |

### PUT /api/support/conversations/{id}/read

**Read Support Conversation**

Monotonically acknowledge messages through sequence for this reader. Cannot acknowledge beyond the conversation last_sequence.


Operation: readSupportConversation.

- Authentication: projectBearer OR dashboardSession
- Request body: SupportRead

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| sequence | required | integer | minimum: 0 |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Support result. | SupportRead (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/support/conversations/{id}/close

**Close Support Conversation**

End this project conversation and cancel pending AI replies. Repeatable; preserves history. Staff can reopen it in Slack.


Operation: closeSupportConversation.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| id | path | required |  |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Closed conversation. | SupportConversation (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 /stream/support

**Stream Support Messages**

Project support messages on every plan as resumable SSE. event is support.message; id is the project support sequence, data is SupportMessage. after takes precedence over Last-Event-ID. Cursor 0 replays history. Staff, customer and AI messages are distinguished by role. Streams revalidate credentials and active project access.

No screenshot or credential data is sent.


Operation: streamSupportMessages.

- Authentication: projectBearer OR dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| after | query | optional | Project support message sequence; resumes strictly after this position. |
| Last-Event-ID | header | optional |  |

#### Request template

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.

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

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Support message SSE. | string (text/event-stream) |
| 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) |

## Website demo requests and bookings

### POST /website/demo-requests

**Submit Website Demo Request**

Same-origin anonymous website intake. Saves the request and independent delivery jobs before classification. Idempotency-Key is an independently random private retry UUID, retained only in browser memory. It is not the public flow_id. Matching retries set the same HttpOnly screenrig_demo cookie and return the saved request without another submission.

Requests and responses are excluded from payload logging. Country does not select a timezone. No project or dashboard login is created.


Operation: submitWebsiteDemoRequest.

- Authentication: none
- Request body: DemoRequestInput

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| Origin | header | required | Same-origin browser request; configured stage origin is accepted on stage. |
| Idempotency-Key | header | required |  |
| f | query | optional | Nonsecret funnel attempt ID, identical to body flow_id. |
| src | query | optional | Allowlisted source, identical to body source. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| email | required | string | maxLength: 254 format: "email" |
| country | required | string | ISO 3166-1 country code; retained separately from timezone. pattern: "^[A-Z]{2}$" |
| screen_range | required | string | Allowed: "1_10", "11_100", "101_500", "501_plus", "unknown" |
| organization | required | string | Allowed: "small_mid", "large_corporation", "agency_network" |
| budget | required | string | Self-reported annual signage budget in USD. Allowed: "under_2995", "2995_19994", "19995_plus", "unknown" |
| timezone | required | string | IANA timezone, or empty when browser detection is unavailable; scheduling requires explicit timezone. maxLength: 100 |
| source | required | DemoSource | Allowed: "home_hero", "home_paid_plans", "pricing_premium", "pricing_enterprise", "direct" |
| flow_id | required | string | Random nonsecret analytics attempt ID, never authorization. pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" |

#### Request template

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.

~~~sh
curl --request POST --cookie-jar cookies.txt --header 'Origin: https://screenrig.ai' --header 'Idempotency-Key: REQUEST_ID' --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/website/demo-requests'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Request durably saved. Sets private demo session cookie. | DemoResult (application/json) |
| 200 | Matching idempotent replay of the saved request. | DemoResult (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 /website/demo-request

**Get Website Demo Request**

Cookie-bound saved request. A request ID or public flow ID does not grant access. No-store.


Operation: getWebsiteDemoRequest.

- Authentication: demoSession
- Request body: none

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/website/demo-request'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Saved demo request and booking state. | DemoResult (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 /website/demo-calendar

**Get Website Demo Calendar**

Private calendar download for this browser's confirmed booking. Rechecks the saved event; no request ID or public flow ID grants access. No-store.


Operation: getWebsiteDemoCalendar.

- Authentication: demoSession
- Request body: none

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/website/demo-calendar'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Importable calendar event with stable identity and confirmed times. | string (text/calendar) |
| 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 /website/demo-slots

**Get Website Demo Slots**

Up to four real available 30-minute slots, varied across dates and times when possible, in the visitor's editable IANA timezone. Private slot IDs are returned in the JSON body and never sent in URL queries. Unconfigured calendar or no reasonable timezone overlap returns an empty list.


Operation: getWebsiteDemoSlots.

- Authentication: demoSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| timezone | query | required |  |
| refresh | query | optional | Offer alternatives to the previous cookie-bound batch. |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/website/demo-slots?timezone=TIMEZONE'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Real available slots or an empty list. | DemoSlots (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 /website/demo-bookings

**Book Website Demo**

Rechecks a cookie-bound slot, atomically reserves host buffer cells and creates a stable event in the configured local or Google calendar. Confirmation requires the real event and saved booking. Ambiguous provider failures remain pending for durable reconciliation, without a second event or false booked page.


Operation: bookWebsiteDemo.

- Authentication: demoSession
- Request body: object

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| Origin | header | required | Same-origin browser request; configured stage origin is accepted on stage. |
| f | query | optional | Nonsecret funnel attempt ID, identical to the saved flow_id. |
| src | query | optional | Saved allowlisted source. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| slot_id | required | string | Empty only for replay of an already confirmed cookie-bound booking. pattern: "^([A-Za-z0-9_-]{20,128})?$" |
| timezone | required | string | maxLength: 100 |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Origin: https://screenrig.ai' --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/website/demo-bookings'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Calendar event and booking confirmed and saved. | DemoResult (application/json) |
| 200 | Already confirmed booking replay. | DemoResult (application/json) |
| 202 | Booking pending provider reconciliation; excluded from confirmed booking analytics. | DemoResult (application/json) |
| 409 | 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) |

### POST /website/demo-review

**Request Website Demo Review**

Flags the existing on-demand request for team review without another questionnaire. Late classifier replies cannot reverse this choice.


Operation: requestWebsiteDemoReview.

- Authentication: demoSession
- Request body: object

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| Origin | header | required | Same-origin browser request; configured stage origin is accepted on stage. |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Origin: https://screenrig.ai' --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/website/demo-review'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Current saved request with review requested. | DemoResult (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 /website/demo-session

**Exchange Website Demo Invitation**

Exchanges an emailed seven-day booking invitation for a browser-only HttpOnly demo session. The website removes its invite fragment before any request and sends the token only in this private POST body, never in a URL, redirect, analytics marker or log. Multiple browser sessions may access the same invitation-bound request.


Operation: exchangeWebsiteDemoInvitation.

- Authentication: none
- Request body: object

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| Origin | header | required | Same-origin browser request; configured stage origin is accepted on stage. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| token | required | string | pattern: "^[A-Za-z0-9_-]{20,128}$" |

#### Request template

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.

~~~sh
curl --request POST --cookie-jar cookies.txt --header 'Origin: https://screenrig.ai' --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/website/demo-session'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Cookie set and saved demo request returned. | DemoResult (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) |

## Homepage browser handoff

### POST /api/project/browser-links/claim

**Claim Browser Link**

Claims one browser-link code for the authenticated project and atomically creates exactly one first pairing_pending screen plus its existing browser provisioning grant. Enrollment remains project-only. The response is safe metadata only and never contains a provisioning token, fragment URL, cookie, or proof.


Operation: claimBrowserLink.

- Authentication: projectBearer
- Request body: BrowserLinkClaimRequest

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| Idempotency-Key | header | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| code | required | string | pattern: "^[23456789ABCDEFGHJKMNPQRSTUVWXYZ]{3}-?[23456789ABCDEFGHJKMNPQRSTUVWXYZ]{3}$" |

#### Request template

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.

~~~sh
curl --request POST --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --header 'Idempotency-Key: REQUEST_ID' --header 'Content-Type: application/json' --data '@request.json' 'https://api.screenrig.ai/api/project/browser-links/claim'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Exact replay-safe fragment-free browser-link claim. | BrowserLinkClaim (application/json); headers: Cache-Control, RateLimit-Policy, RateLimit |
| 400 | 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) |
| 403 | 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) |
| 409 | 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) |
| 410 | 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) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, RateLimit-Policy, RateLimit |
| 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 /screen/browser-links

**Get Browser Link Status**

Read-only polling fallback authenticated only by the owning apex browser-link cookie. It returns safe status metadata and the fixed continuation path, never a provisioning URL or token.


Operation: getBrowserLinkStatus.

- Authentication: browserLinkCookie
- Request body: none

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/screen/browser-links'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Claimed safe status. | BrowserLinkStatus (application/json); headers: Cache-Control, Referrer-Policy |
| 202 | Browser link exists but is not claimed yet. | BrowserLinkStatus (application/json); headers: Cache-Control, Referrer-Policy |
| 401 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 410 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### POST /screen/browser-links

**Start Browser Link**

Strict same-origin no-JavaScript-compatible POST that creates or reuses a 24-hour unclaimed browser-link session. Claiming within that window creates a fresh, independent ten-minute protected provisioning-delivery window; cookie Player pairing lasts 24 hours.

Native pairing is a separate seventy-two-hour unclaimed locator; claimed native delivery remains durable until signed completion or explicit on-player reset. It sets exactly the apex host-only Secure HttpOnly SameSite=Strict Path=/ __Host-screenrig-browser-link cookie with no Domain.

JSON clients receive 201; Accept text/html receives a bodyless 303 to /setup. No insecure localhost fallback exists; development requires the configured HTTPS loopback origin.


Operation: startBrowserLink.

- Authentication: none
- Request body: none

#### Request template

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.

~~~sh
curl --request POST 'https://screenrig.ai/screen/browser-links'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Safe browser-link code/status metadata. | BrowserLinkSession (application/json); headers: Cache-Control, Referrer-Policy, Set-Cookie, RateLimit-Policy, RateLimit |
| 303 | No-JavaScript form continuation to the read-only setup page. | no body; headers: Location, Cache-Control, Referrer-Policy, Set-Cookie |
| 403 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 409 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 429 | Stable rate_limited problem on a Player-facing runtime or content route, in the runtime document shape (RuntimeProblem). Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | RuntimeProblem (application/problem+json); headers: Retry-After, RateLimit-Policy, RateLimit |
| 500 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### GET /stream/browser-links/events

**Stream Browser Link Events**

Owning-cookie-only replayable SSE with Last-Event-ID. Event data contains safe status fields only. It never contains a provisioning URL/token and does not use the project event stream.


Operation: streamBrowserLinkEvents.

- Authentication: browserLinkCookie
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| Last-Event-ID | header | optional | Durable cursor used when after is absent. |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/stream/browser-links/events'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Browser-link issued/claimed status SSE. | string (text/event-stream); headers: Cache-Control, Referrer-Policy, X-Accel-Buffering |
| 400 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 401 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 410 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### GET /screen/browser-links/continue

**Continue Browser Link**

Navigation-only owning-cookie-authenticated GET. After claim it returns a bodyless 303 whose sensitive Location is the exact Player /s/<public_id>#provision=<token> URL. The Location is never exposed through CORS, JSON, SSE, polling, logs, traces, metrics, analytics, events, or errors and is idempotent only during the short delivery repair window.


Operation: continueBrowserLink.

- Authentication: browserLinkCookie
- Request body: none

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/screen/browser-links/continue'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 303 | Bodyless redirect to the Player provisioning fragment. | no body; headers: Location, Cache-Control, Referrer-Policy |
| 401 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 409 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 410 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### GET /setup

**Get Browser Link Setup Page**

Read-only no-JavaScript setup page authenticated by the owning browser-link cookie. It contains code/status instructions only and never a provisioning URL or token.


Operation: getBrowserLinkSetupPage.

- Authentication: browserLinkCookie
- Request body: none

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/setup'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Safe noindex setup HTML. | string (text/html); headers: Cache-Control, Referrer-Policy, X-Robots-Tag |
| 401 | 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) |
| 410 | 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 /ABC-234

**Get Dashed Browser Link Locator**

Representative dashed public handoff-code locator. Production accepts every canonical ambiguity-safe XXX-XXX code. The route is side-effect-free and reveals only generic locator instructions. Accept text/markdown negotiates Markdown; Link metadata identifies canonical HTML, the explicit .md alternate, and /llms.txt.


Operation: getDashedBrowserLinkLocator.

- Authentication: none
- Request body: none

#### Request template

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.

~~~sh
curl --request GET 'https://screenrig.ai/ABC-234'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Public locator instructions. | string (text/html), string (text/markdown); headers: Cache-Control, Referrer-Policy, X-Robots-Tag, Vary, Link |
| 404 | Generic unknown-or-expired locator response with no existence oracle. | string (text/html), string (text/markdown); headers: Cache-Control, Referrer-Policy, X-Robots-Tag |

### GET /ABC234

**Get Undashed Browser Link Locator**

Representative undashed public handoff-code locator equivalent to /ABC-234. The route is side-effect-free and reveals only generic locator instructions.


Operation: getUndashedBrowserLinkLocator.

- Authentication: none
- Request body: none

#### Request template

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.

~~~sh
curl --request GET 'https://screenrig.ai/ABC234'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Public locator instructions. | string (text/html), string (text/markdown); headers: Cache-Control, Referrer-Policy, X-Robots-Tag, Vary, Link |
| 404 | Generic unknown-or-expired locator response with no existence oracle. | string (text/html), string (text/markdown); headers: Cache-Control, Referrer-Policy, X-Robots-Tag |

### GET /ABC-234.md

**Get Dashed Browser Link Locator Markdown**

Side-effect-free explicit Markdown alias for the representative dashed public handoff-code locator. Production also accepts the undashed .md form. It returns the same public-only instructions and generic no-oracle 404 policy as the canonical locator.


Operation: getDashedBrowserLinkLocatorMarkdown.

- Authentication: none
- Request body: none

#### Request template

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.

~~~sh
curl --request GET 'https://screenrig.ai/ABC-234.md'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Public locator Markdown instructions. | string (text/markdown); headers: Cache-Control, Referrer-Policy, X-Robots-Tag, Vary, Link |
| 404 | Generic unknown-or-expired locator response with no existence oracle. | string (text/markdown); headers: Cache-Control, Referrer-Policy, X-Robots-Tag |

### GET /ABC-234/index.md

**Get Dashed Browser Link Locator Index Markdown**

Side-effect-free directory-style Markdown alias for the representative dashed public handoff-code locator. Production also accepts the undashed /index.md form. Its canonical and alternate Link metadata point to /ABC-234 and /ABC-234.md.


Operation: getDashedBrowserLinkLocatorIndexMarkdown.

- Authentication: none
- Request body: none

#### Request template

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.

~~~sh
curl --request GET 'https://screenrig.ai/ABC-234/index.md'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Public locator Markdown instructions. | string (text/markdown); headers: Cache-Control, Referrer-Policy, X-Robots-Tag, Vary, Link |
| 404 | Generic unknown-or-expired locator response with no existence oracle. | string (text/markdown); headers: Cache-Control, Referrer-Policy, X-Robots-Tag |

## Player pairing and sessions

### POST /screen/pairing-sessions

**Start Pairing Session**

Creates one fleet-global expiring pairing session and sets the temporary HttpOnly Secure SameSite=Strict pairing cookie. An untrusted Origin or cross-site browser request is rejected with forbidden before rate-limit or pairing state is created.


Operation: startPairingSession.

- Authentication: none
- Request body: none

#### Request template

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.

~~~sh
curl --request POST 'https://play.screenrig.ai/screen/pairing-sessions'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Exact six-character pairing code and expiry. | PairingSession (application/json); headers: Cache-Control, RateLimit-Policy, RateLimit |
| 403 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 429 | Stable rate_limited problem on a Player-facing runtime or content route, in the runtime document shape (RuntimeProblem). Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | RuntimeProblem (application/problem+json); headers: Retry-After, RateLimit-Policy, RateLimit |
| 500 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### GET /stream/pairing-events

**Stream Pairing Events**


Operation: streamPairingEvents.

- Authentication: pairingCookie
- Request body: none

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://play.screenrig.ai/stream/pairing-events'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Authenticated temporary pairing SSE. The terminal pairing.claimed data is PairingClaimedEvent and contains only a non-secret completion nonce. | string (text/event-stream); headers: Cache-Control, X-Accel-Buffering |
| 401 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### POST /screen/pairing-sessions/complete

**Complete Pairing Session**


Operation: completePairingSession.

- Authentication: pairingCookie
- Request body: PairingComplete

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| completion_nonce | required | string | pattern: "^[A-Za-z0-9_-]{43}$" |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://play.screenrig.ai/screen/pairing-sessions/complete'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Paired-device cookie issued; the Player next creates the ordinary paired runtime session. | PairingCompletion (application/json); headers: Cache-Control, RateLimit-Policy, RateLimit |
| 400 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 401 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 403 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 404 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 429 | Stable rate_limited problem on a Player-facing runtime or content route, in the runtime document shape (RuntimeProblem). Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | RuntimeProblem (application/problem+json); headers: Retry-After, RateLimit-Policy, RateLimit |
| 500 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### POST /screen/sessions

**Create Anonymous Runtime Session**


Operation: createAnonymousRuntimeSession.

- Authentication: none
- Request body: RuntimeSessionRequest

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| public_id | required | string |  |
| playback | optional | PlaybackHandshake | Optional playback handshake a Player declares at mint. It is a hint that selects behavior, never authorization: unknown members and entries of the wrong type are ignored, and a value that is not an object negotiates nothing. The mint response echoes the supported intersection as PlaybackGrant. adslot-v1 is granted only to paired sessions. |
| player | optional | PlayerIdentity | Player self-identification, sent only on the three mint routes (docs/player-compatibility.md §3.1). It is a hint and never authorization, and it is not covered by the native proof. It is read tolerantly: unknown members are ignored, an invalid optional member is dropped on its own, and a player that is not an object or has no valid kind declares nothing. It is never a 400. A paired mint stores it on the screen for the operator fleet histogram. |

#### Request template

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.

~~~sh
curl --request POST --header 'Content-Type: application/json' --data '@request.json' 'https://play.screenrig.ai/screen/sessions'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Read-only runtime session cookie | RuntimeSession (application/json); headers: RateLimit-Policy, RateLimit |
| 404 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### POST /screen/device-sessions

**Create Device Runtime Session**


Operation: createDeviceRuntimeSession.

- Authentication: pairedDeviceCookie
- Request body: DeviceSessionRequest

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| public_id | optional | string | minLength: 1 |
| playback | optional | PlaybackHandshake | Optional playback handshake a Player declares at mint. It is a hint that selects behavior, never authorization: unknown members and entries of the wrong type are ignored, and a value that is not an object negotiates nothing. The mint response echoes the supported intersection as PlaybackGrant. adslot-v1 is granted only to paired sessions. |
| player | optional | PlayerIdentity | Player self-identification, sent only on the three mint routes (docs/player-compatibility.md §3.1). It is a hint and never authorization, and it is not covered by the native proof. It is read tolerantly: unknown members are ignored, an invalid optional member is dropped on its own, and a player that is not an object or has no valid kind declares nothing. It is never a 400. A paired mint stores it on the screen for the operator fleet histogram. |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://play.screenrig.ai/screen/device-sessions'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Paired runtime session | RuntimeSession (application/json) |
| 409 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### POST /screen/native/pairing-sessions

**Start Native Pairing Session**

Creates a native-only pairing session from a player Ed25519 public key. Unclaimed native pairing lasts seventy-two hours. A successful claim reserves a screen and stores durable encrypted delivery independently of the locator expiry. It does not bind the public key to a project until signed completion for that exact pairing credential and nonce.

Cookies, query credentials, cross-site origins, and Authorization are rejected. Exact Idempotency-Key replay precedes new abuse admission. The response never sets a cookie. An already-bound or explicitly retired public key returns enrollment_bound.


Operation: startNativePairingSession.

- Authentication: none
- Request body: NativePairingStart

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| Idempotency-Key | header | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| kid | required | string | pattern: "^[A-Za-z0-9_-]{43}$" |
| public_key | required | PlayerPublicKey |  |
| signature | required | string | minLength: 80 maxLength: 4096 |
| host | optional | RuntimeHostHint | The optional native host hint on pairing start, pairing completion, and identity session mint. It names a device and is never authorization. It is read tolerantly (docs/player-compatibility.md §1.1.1): unknown members are ignored, a malformed field or capabilities entry is dropped, at most 32 capabilities are kept, and a value that is not an object or a platform outside the pattern drops the whole hint. None of these refuses the request. A Player still sends only the registered platform values and the members below, because a pre-contract backend refuses anything else. |
| fleet_link | optional | string | Optional fleet link secret from the Player URL fragment (#fleet=). A valid, enabled link claims this pairing session for its project at once, or recovers the device's earlier screen; the answer's fleet_link reports which. Like host, it is read tolerantly: a malformed or unknown value never refuses the start. pattern: "^srfl_[A-Za-z0-9_-]{43}$" |

#### Request template

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.

~~~sh
curl --request POST --header 'Idempotency-Key: REQUEST_ID' --header 'Content-Type: application/json' --data '@request.json' 'https://play.screenrig.ai/screen/native/pairing-sessions'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Native pairing code plus temporary ScreenRig-Pairing credential; no Set-Cookie. | NativePairingSession (application/json); headers: Cache-Control, RateLimit-Policy, RateLimit |
| 400 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 403 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 409 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 429 | Stable rate_limited problem on a Player-facing runtime or content route, in the runtime document shape (RuntimeProblem). Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | RuntimeProblem (application/problem+json); headers: Retry-After, RateLimit-Policy, RateLimit |
| 500 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### GET /stream/native/pairing-events

**Stream Native Pairing Events**

Native-only pairing SSE authenticated by exactly one ScreenRig-Pairing Authorization header. Cookies and query credentials are rejected. The terminal event contains only the non-secret completion nonce.


Operation: streamNativePairingEvents.

- Authentication: nativePairingAuthorization
- Request body: none

#### Request template

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.

~~~sh
curl --request GET --header 'Authorization: ScreenRig-Pairing PAIRING' 'https://play.screenrig.ai/stream/native/pairing-events'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Authenticated native pairing SSE whose terminal data is PairingClaimedEvent. | string (text/event-stream); headers: Cache-Control, X-Accel-Buffering |
| 401 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 403 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 404 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 429 | Stable rate_limited problem on a Player-facing runtime or content route, in the runtime document shape (RuntimeProblem). Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | RuntimeProblem (application/problem+json); headers: Retry-After, RateLimit-Policy, RateLimit |
| 500 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### POST /screen/native/pairing-sessions/complete

**Complete Native Pairing Session**

Completes native pairing with a completion nonce and an EdDSA compact JWS (op=pairing.complete) whose request_hash binds the exact pairing credential and completion nonce. No cookie is accepted or set. No ScreenRig-Device credential is issued. The player next mints ScreenRig-Session from a possession proof.


Operation: completeNativePairingSession.

- Authentication: nativePairingAuthorization
- Request body: NativePairingComplete

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| kid | optional | string | pattern: "^[A-Za-z0-9_-]{43}$" |
| completion_nonce | required | string | pattern: "^[A-Za-z0-9_-]{43}$" |
| public_key | required | PlayerPublicKey |  |
| signature | required | string | Compact EdDSA JWS with op=pairing.complete and request_hash binding the exact pairing credential and completion nonce. minLength: 80 maxLength: 4096 |
| host | optional | RuntimeHostHint | The optional native host hint on pairing start, pairing completion, and identity session mint. It names a device and is never authorization. It is read tolerantly (docs/player-compatibility.md §1.1.1): unknown members are ignored, a malformed field or capabilities entry is dropped, at most 32 capabilities are kept, and a value that is not an object or a platform outside the pattern drops the whole hint. None of these refuses the request. A Player still sends only the registered platform values and the members below, because a pre-contract backend refuses anything else. |

#### Request template

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.

~~~sh
curl --request POST --header 'Authorization: ScreenRig-Pairing PAIRING' --header 'Content-Type: application/json' --data '@request.json' 'https://play.screenrig.ai/screen/native/pairing-sessions/complete'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Native pairing completion; no Set-Cookie and no device credential. | NativePairingCompletion (application/json); headers: Cache-Control, RateLimit-Policy, RateLimit |
| 400 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 401 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 403 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 404 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 409 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 429 | Stable rate_limited problem on a Player-facing runtime or content route, in the runtime document shape (RuntimeProblem). Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | RuntimeProblem (application/problem+json); headers: Retry-After, RateLimit-Policy, RateLimit |
| 500 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### POST /screen/native/identity/challenges

**Create Native Identity Challenge**

Issues a short-lived nonce bound to one player public-key thumbprint. Cookies, query credentials, and Authorization are rejected.


Operation: createNativeIdentityChallenge.

- Authentication: none
- Request body: NativeIdentityChallengeRequest

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| kid | required | string | RFC 7638 SHA-256 JWK thumbprint. pattern: "^[A-Za-z0-9_-]{43}$" |
| public_key | optional | PlayerPublicKey |  |
| op | required | string | Allowed: "pairing.start", "pairing.complete", "session.mint", "identity.reset" |
| request_hash | required | string | pattern: "^[0-9a-f]{64}$" |

#### Request template

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.

~~~sh
curl --request POST --header 'Content-Type: application/json' --data '@request.json' 'https://play.screenrig.ai/screen/native/identity/challenges'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Challenge nonce for an EdDSA compact JWS proof; no Set-Cookie. | NativeIdentityChallenge (application/json); headers: Cache-Control, RateLimit-Policy, RateLimit |
| 400 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 404 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 429 | Stable rate_limited problem on a Player-facing runtime or content route, in the runtime document shape (RuntimeProblem). Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | RuntimeProblem (application/problem+json); headers: Retry-After, RateLimit-Policy, RateLimit |
| 500 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### POST /screen/native/identity/sessions

**Create Native Identity Runtime Session**

Verifies an EdDSA compact JWS (op=session.mint) against the enrolled public key and returns a signed seven-day ScreenRig-Session. Paired Players renew after one day with jitter through this same signed mint; renewal replaces the credential without closing an existing runtime stream. Renewal metadata is optional to readers and does not require a Player update.

Cookies are neither accepted nor set. Exact Idempotency-Key replay precedes new abuse admission. An archived screen still mints a session so the player can show the archived glass. Session mint requires the exact durable project and screen binding established by signed completion; pending claim offers have no session authority.

Exact replay rechecks that binding and rejects retired identities.


Operation: createNativeIdentityRuntimeSession.

- Authentication: none
- Request body: NativeIdentitySessionRequest

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| Idempotency-Key | header | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| kid | required | string | pattern: "^[A-Za-z0-9_-]{43}$" |
| public_key | required | PlayerPublicKey |  |
| signature | required | string | Compact EdDSA JWS with op=session.mint. minLength: 80 maxLength: 4096 |
| host | optional | RuntimeHostHint | The optional native host hint on pairing start, pairing completion, and identity session mint. It names a device and is never authorization. It is read tolerantly (docs/player-compatibility.md §1.1.1): unknown members are ignored, a malformed field or capabilities entry is dropped, at most 32 capabilities are kept, and a value that is not an object or a platform outside the pattern drops the whole hint. None of these refuses the request. A Player still sends only the registered platform values and the members below, because a pre-contract backend refuses anything else. |
| playback | optional | PlaybackHandshake | Optional playback handshake a Player declares at mint. It is a hint that selects behavior, never authorization: unknown members and entries of the wrong type are ignored, and a value that is not an object negotiates nothing. The mint response echoes the supported intersection as PlaybackGrant. adslot-v1 is granted only to paired sessions. |
| player | optional | PlayerIdentity | Player self-identification, sent only on the three mint routes (docs/player-compatibility.md §3.1). It is a hint and never authorization, and it is not covered by the native proof. It is read tolerantly: unknown members are ignored, an invalid optional member is dropped on its own, and a player that is not an object or has no valid kind declares nothing. It is never a 400. A paired mint stores it on the screen for the operator fleet histogram. |

#### Request template

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.

~~~sh
curl --request POST --header 'Idempotency-Key: REQUEST_ID' --header 'Content-Type: application/json' --data '@request.json' 'https://play.screenrig.ai/screen/native/identity/sessions'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Paired native runtime session; no Set-Cookie. | NativeRuntimeSession (application/json); headers: Cache-Control, RateLimit-Policy, RateLimit |
| 400 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 401 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 403 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 404 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 409 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 429 | Stable rate_limited problem on a Player-facing runtime or content route, in the runtime document shape (RuntimeProblem). Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | RuntimeProblem (application/problem+json); headers: Retry-After, RateLimit-Policy, RateLimit |
| 500 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### POST /screen/native/identity/reset

**Reset Native Identity**

The on-player reset, invoked only by an explicit confirmed operator action on the device. Failed pairing, session minting, or network access never automatically invokes this operation or clears the Player's local key. The endpoint verifies an EdDSA compact JWS (op=identity.reset).

The screen bound to this key is archived with archive_reason device_reset (already archived: unchanged), and the key stays bound to it: a device that still holds the key mints an archived session (dark glass) and resumes on unarchive with no re-pairing, and the key cannot start a new pairing (enrollment_bound).

Pending native claim offers for the key are withdrawn. A key that never bound a screen, or a key already retired by screen recovery, becomes terminal instead: it cannot pair or mint sessions again, and resetting a retired key never touches the screen its successor owns.

If the enrolled project has been deleted, signed reset makes the orphaned key terminal without archiving another screen, so the device can pair anew. After a lost response, the Player keeps its local key and can retry with a new challenge and fresh signed proof.

A network error alone does not confirm a reset or authorize clearing that key (docs/player-compatibility.md §5.1). No project bearer. Cookies are neither accepted nor set.


Operation: resetNativeIdentity.

- Authentication: none
- Request body: NativeIdentityProof

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| kid | required | string | pattern: "^[A-Za-z0-9_-]{43}$" |
| public_key | required | PlayerPublicKey |  |
| signature | required | string | Compact EdDSA JWS. minLength: 80 maxLength: 4096 |

#### Request template

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.

~~~sh
curl --request POST --header 'Content-Type: application/json' --data '@request.json' 'https://play.screenrig.ai/screen/native/identity/reset'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | Reset recorded: a live-project bound screen archived with the key kept; a deleted-project, unbound, or retired key made terminal; or an unknown key answered as an idempotent no-op that archives nothing. An authenticated retry of any of these also succeeds. No Set-Cookie. | no body; headers: Cache-Control, RateLimit-Policy, RateLimit |
| 400 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 401 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 404 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 429 | Stable rate_limited problem on a Player-facing runtime or content route, in the runtime document shape (RuntimeProblem). Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | RuntimeProblem (application/problem+json); headers: Retry-After, RateLimit-Policy, RateLimit |
| 500 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

## Runtime manifest, reports, and events

### GET /screen/keys

**Get Runtime Session Keys**

The public keys that verify runtime and dashboard session tokens, as a JWKS document (OKP/Ed25519, one entry per key id). It carries public material only and needs no credential. An empty keys array means no session keys are installed. Cache-Control is public, max-age=300, with an ETag for If-None-Match.


Operation: getRuntimeSessionKeys.

- Authentication: none
- Request body: none

#### Request template

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.

~~~sh
curl --request GET 'https://play.screenrig.ai/screen/keys'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Public session verification keys | SessionKeySet (application/json); headers: Cache-Control, ETag |
| 304 | Key set unchanged | no body |
| 429 | Stable rate_limited problem on a Player-facing runtime or content route, in the runtime document shape (RuntimeProblem). Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | RuntimeProblem (application/problem+json); headers: Retry-After, RateLimit-Policy, RateLimit |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### POST /screen/browser-provisioning/exchanges

**Exchange Browser Provisioning**

Browser-only same-origin exchange. Atomically binds the one-time token to exchange_id and sets only the normal host-only HttpOnly Secure SameSite=Strict paired-device cookie. Android and Qt never consume this grant.


Operation: exchangeBrowserProvisioning.

- Authentication: none
- Request body: BrowserProvisioningExchange

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| provisioning_token | required | string | pattern: "^[A-Za-z0-9_-]{43}$" |
| exchange_id | required | string | pattern: "^xchg_[A-Za-z0-9_-]{43}$" |

#### Request template

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.

~~~sh
curl --request POST --header 'Content-Type: application/json' --data '@request.json' 'https://play.screenrig.ai/screen/browser-provisioning/exchanges'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Paired-device cookie issued without a runtime cookie or credential in the body. | BrowserProvisioningCompletion (application/json); headers: Cache-Control, Referrer-Policy, RateLimit-Policy, RateLimit |
| 400 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 403 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 404 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 409 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 410 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 429 | Stable rate_limited problem on a Player-facing runtime or content route, in the runtime document shape (RuntimeProblem). Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | RuntimeProblem (application/problem+json); headers: Retry-After, RateLimit-Policy, RateLimit |
| 500 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### GET /screen/chrome

**Get Runtime Chrome**

Stage-chrome for the current runtime session. Not part of the runtime manifest. Auth is the same runtime cookie or ScreenRig-Session as GET /screen/manifest. Cache-Control is private, no-store. banner is always null.


Operation: getRuntimeChrome.

- Authentication: runtimeCookie OR nativeSessionAuthorization
- Request body: none

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://play.screenrig.ai/screen/chrome'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Runtime chrome | RuntimeChrome (application/json); headers: Cache-Control |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### GET /screen/manifest

**Get Runtime Manifest**

The one runtime manifest route, for every session. schemaVersion is 3. Resolved player manifest; projection is fail-safe, and invalid pages and primitives are omitted and listed in diagnostics.

Adslot pages are included only for a paired session granted adslot-v1; every other session, including an anonymous public-screen viewer, receives house content with no adslot page. The root keys stay camelCase; adslot page keys stay id, type, adslot_id and optional snake_case visibility.

Compatibility rule 4 — 200 with pages [] and non-empty diagnostics is a degraded projection (player keeps last-known-good); 200 with pages [] and no diagnostics is an authored empty assignment (docs/player-compatibility.md §1.4). An archived screen returns problem screen_archived and must not refresh last-known-good.


Operation: getRuntimeManifest.

- Authentication: runtimeCookie OR nativeSessionAuthorization
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| stream_transports | query | optional | Comma-separated supported stream transports (hls,udp-mpegts). Omission projects streams to fallback images. Rendering negotiation only, never authorization. Advertise udp-mpegts only when locally enabled. |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://play.screenrig.ai/screen/manifest'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Resolved player manifest. | RuntimeManifest (application/json) |
| 304 | Manifest unchanged | no body |
| 409 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### GET /stream/events

**Stream Runtime Events**


Operation: streamRuntimeEvents.

- Authentication: runtimeCookie OR nativeSessionAuthorization
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| after | query | optional | A cursor from a previous response: ev1_ for project events (and the event streams). The Player log (source=log) pages with lg1_ or ev1_ cursors by where the server keeps it; treat them as opaque and pass back the one a page returned. A cursor of another kind is refused with invalid_request. |
| Last-Event-ID | header | optional | Durable cursor used when after is absent. |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://play.screenrig.ai/stream/events'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Screen-scoped durable SSE. The query cursor takes precedence over Last-Event-ID; each event carries an id cursor suitable for reconnect. Served by the stream sidecar from Valkey (stream-valkey/v1); the stream carries no stream.cursor frames, and a cursor advances only with delivered events. Invalid, future, or pre-retention cursors receive stream.resync_required at the current head and close. screen.toast and screen.screenshot_requested are runtime audience events; scan never delivers either past details.expires_at. Screenshot image bytes are never on this stream. screen.archived and screen.unarchived are runtime audience events; they do not wipe credentials. screen.deleted is a runtime audience event delivered when signed identity reset tombstones the screen. No runtime event authorizes clearing local identity. Only successful signed reset initiated by a confirmed local player action permits local identity rotation. project.suspended and project.resumed are runtime audience hints when prepaid remaining hits zero or becomes positive. The runtime audience is an allowlist (docs/player-compatibility.md §7.1): screen.content_changed, screen.manifest_changed, screen.toast, screen.screenshot_requested, screen.archived, screen.unarchived, screen.deleted, screen.device_credential_revoked, project.suspended, project.resumed, player.reload, which reaches every runtime session, the per-screen commands player.reboot and player.display (docs/player-compatibility.md §6.5), never delivered past details.expires_at, and screen.card_data_changed (resource {type: card_data, key}, details {revision} or {deleted: true}), addressed only to screens whose current content reads that card data or feed key. Every other type is never published to the stream. Players ignore unknown event types, including the default message type, and unknown field names, and advance the cursor from every id. The stream opens with its own retry, drawn per connection between 3000 and 15000 ms so that streams dropped together do not reconnect together; a draining host sends each stream its own retry between 1000 and 30000 ms. The in-band problem frame (event: problem) has no id; its data is {code, detail, action?}, where action is present when the registry defines one, and the stream then closes (payment_required sets retry: 60000). The fleet player.reload control frame has no id and never moves the cursor. No frame carries more than 64 KiB of data. | string (text/event-stream); headers: X-Accel-Buffering |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### POST /screen/reports

**Create Runtime Report**


Operation: createRuntimeReport.

- Authentication: pairedRuntimeCookie OR nativeSessionAuthorization
- Request body: inline

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://play.screenrig.ai/screen/reports'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 202 | Accepted after durable persistence. A telemetry batch is accepted or refused as a unit. It returns 202 only after its raw observations are durable and then its stream watermark has moved; it returns stream_id, sequence, accepted (report count), and duplicate. A batch that changes the latest health or observation state or the page-failure slot, or appends project events, commits its watermark with that state in one screen write, and appends its events in the same transaction. A batch that changes none of them, such as a steady batch of plays and condition reports, moves only its watermark, held in Valkey for 25 hours; while Valkey is unavailable or out of memory the screen write holds it instead. A refused batch (conflicting replay, full stream capacity, retired authority) stores nothing, except a new stream that loses the screen's last stream place to a concurrent new stream, which is refused after its raw observations are stored. A batch that was not acknowledged because of a server error or a lost connection can have stored some of its raw observations; replaying the same bytes stores each missing observation once, never stores one twice, and then acknowledges. A replay of an acknowledged batch returns 202 with duplicate true and changes nothing. A watermark held in Valkey can be lost or fall behind in a failover, never move ahead, so a sequence past the stream's watermark is accepted as the stream's next without a gap refusal, and a replay whose watermark was lost is accepted again with duplicate false; its plays are still stored once and its Player log records collapse on their identities. Individual reports retain their existing response. A runtime condition report (any other report code, such as player.page_skipped) is recorded in the Player log (GET /api/events?source=log), not in the project's events, and is acknowledged when the Player log queue accepts it; it is not on the event stream and not delivered to webhooks. manifest.activated is committed before this response and is idempotent for the current active revision. playback.media_started, legacy playback.video_started, and playback.application_started durably append one authenticated screen observation (GET /api/playback/plays) before this response; reported item, page, and manifest identities do not require current content membership and do not authorize content access. Daily counts, optional metadata, and project-owned resource last_used_at are updated by recoverable batches and can lag the raw observation. Ordinary playback-start responses omit optional credit-balance headers. playback.page_failed replaces the screen's latest page-failure slot and appends a project-only durable event in the same transaction without changing screen or manifest revision. manifest.upgrade stores the screen's one latest-wins manifest-upgrade slot behind its revision fences and appends a project-only durable event in the same transaction only when the derived state changes; a stale, superseded, or post-activation pre-activation report is accepted with no change, and no report ever promotes a grant. application.event appends a durable project event before this response. | no body; headers: RateLimit-Policy, RateLimit |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### PUT /screen/screenshot

**Put Runtime Screenshot**

Uploads the current pending still WebP for the session screen. Path does not name screen_id; the paired runtime session binds it. Header ScreenRig-Capture-Id must be the current pending capture_id. Content-Type is exactly image/webp. Body is raw WebP, not empty, at most 2097152 bytes.

The server validates RIFF/WEBP magic, decodes one still frame, and stores the uploaded bytes unchanged. Animated WebP is invalid_request. Missing, unknown, expired, or non-current capture_id is resource_conflict. Unpaired sessions are forbidden. Prepaid remaining credit is not a gate and these bytes are not counted in used_bytes.

One slot per screen is replaced. screen.screenshot_ready details name capture_id, bytes, width, height, and sha256; they never include pixels or an object key.


Operation: putRuntimeScreenshot.

- Authentication: pairedRuntimeCookie OR nativeSessionAuthorization
- Request body: string

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Capture-Id | header | required |  |
| ScreenRig-Capture-Failure | header | optional | Report unavailable capture with an empty body. Players send unsupported_surface or capture_failed. Any other non-empty value is recorded as capture_failed, never refused (docs/player-compatibility.md §1.1). Only the current pending capture may fail. No pixels or partial image are stored; screen.screenshot_failed carries this reason and status becomes unavailable. |

#### Request template

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.

~~~sh
curl --request PUT --cookie cookies.txt --cookie-jar cookies.txt --header 'ScreenRig-Capture-Id: VALUE' --header 'Content-Type: image/webp' --data-binary '@request.bin' 'https://play.screenrig.ai/screen/screenshot'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | Still WebP stored as the current slot. | no body; headers: Cache-Control |
| 400 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 401 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 403 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 409 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### POST /screen/adslots/resolve

**Resolve Adslots**

Resolves up to 16 server-verified page occurrences. A fresh candidate with a possible fill is atomically priced, budget-checked and reserved against the campaign-wide pricing fence. A fresh candidate with no possible fill returns empty without creating a decision or reservation.

No eligible or affordable campaign is a successful empty result with no content and no debit; it is never a payment challenge. Empty results may include a server-clock no-fill hint lasting at most 60 seconds and ending at an earlier known eligibility boundary.

Hints apply to the same screen, manifest, page, slot and eligibility context; they never authorize playback. A lease renewal reuses the same reservation and does not advance rotation, and is denied while the campaign requires price reacceptance.


Operation: resolveAdslots.

- Authentication: pairedRuntimeCookie OR nativeSessionAuthorization
- Request body: RuntimeAdslotResolveRequest

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| manifest_revision | required | string |  |
| candidates | required | array |  |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://play.screenrig.ai/screen/adslots/resolve'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Per-candidate filled or empty results in request order. | RuntimeAdslotResolveResponse (application/json); headers: Cache-Control, RateLimit-Policy, RateLimit |
| 400 | invalid_request - batch size, duplicate keys, or forecast window is invalid. | RuntimeProblem (application/problem+json) |
| 409 | resource_conflict - a candidate was reused with a different request hash. | RuntimeProblem (application/problem+json) |
| 429 | Stable rate_limited problem on a Player-facing runtime or content route, in the runtime document shape (RuntimeProblem). Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | RuntimeProblem (application/problem+json); headers: Retry-After, RateLimit-Policy, RateLimit |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### POST /screen/adslots/events

**Report Adslot Events**

Authenticated playback evidence. Events dedupe on screen and event ID. A valid completion consumes its held reservation once at the reserved price and fee, credits the seller gross and debits the serving fee in one transaction. A confirmed non-play terminal event releases the hold once.

Completion received after release is unbillable and never debits fresh credits. Completion before start is held pending reconciliation.


Operation: reportAdslotEvents.

- Authentication: pairedRuntimeCookie OR nativeSessionAuthorization
- Request body: RuntimeAdslotEventBatch

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| events | required | array |  |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://play.screenrig.ai/screen/adslots/events'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Per-event outcomes. | RuntimeAdslotEventResponse (application/json); headers: Cache-Control, RateLimit-Policy, RateLimit |
| 429 | Stable rate_limited problem on a Player-facing runtime or content route, in the runtime document shape (RuntimeProblem). Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | RuntimeProblem (application/problem+json); headers: Retry-After, RateLimit-Policy, RateLimit |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### PUT /screen/observation

**Put Runtime Observation**

Reports the playback surface the paired player used for the last resolveLayout. Path does not name screen_id; the paired runtime session binds it. Body is JSON ScreenObservation: observed_at plus exactly one surface.

Numbers are device-independent pixels and the device pixel ratio next to that layout surface, not canvas author size and not encoded screenshot size. They are range-checked only and are never proven hardware or authorization. Last write wins and replaces the whole surfaces array.

An identical surfaces tuple (id, width, height, pixel_ratio, presentation, rotation) does not emit screen.surface_changed; a later observed_at on the same tuple is ignored. Unpaired and anonymous public sessions are forbidden. Native cookies are rejected; browser must not adopt header profiles. Prepaid remaining credit is not a gate.

Observation is absent from GET /api/screens until the first accepted report. ScreenPatch cannot write it. An archived screen answers 409 screen_archived. At most 12 reports per minute per paired session (runtime-observation): a 429 means retry no sooner than Retry-After.


Operation: putRuntimeObservation.

- Authentication: pairedRuntimeCookie OR nativeSessionAuthorization
- Request body: RuntimeObservationWrite

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| observed_at | required | string | format: "date-time" |
| surfaces | required | array |  |

#### Request template

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.

~~~sh
curl --request PUT --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://play.screenrig.ai/screen/observation'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | Observation stored as the current surfaces array. | no body; headers: Cache-Control, RateLimit-Policy, RateLimit |
| 400 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 401 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 403 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 409 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 429 | Stable rate_limited problem on a Player-facing runtime or content route, in the runtime document shape (RuntimeProblem). Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | RuntimeProblem (application/problem+json); headers: Retry-After, RateLimit-Policy, RateLimit |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### PUT /screen/health

**Put Runtime Health**

Reports device health for the paired session's screen. Send one on session start and every fifteen minutes, and one at once when the display connection or power changes.

Every member is optional and the body is read tolerantly (docs/player-compatibility.md §1.1): unknown members are ignored, a value out of range is dropped, and an unknown display.power or network.kind reads as unknown; only a member of the wrong JSON type is 400 (a number beyond float64 range is out of range: that member is dropped).

The stored value is the sanitized ScreenHealth plus server reported_at, replacing the previous report. At most one report per screen per minute (runtime-health): a 429 means drop this report and send the next one on schedule, never retry sooner than Retry-After (at least 1 second).

A 400 or a server error does not spend the minute; all sessions of one screen share it. Requires a paired session holding health.write (a paired session minted before health.write existed is admitted by runtime.report); unpaired and anonymous sessions are forbidden and an archived screen answers 409 screen_archived.

A report from a session older than the screen's current content access generation is refused 401. Archive and screen recovery clear Screen.health. A backend without this route answers 404: keep playing and stop reporting health for that session. Transitions append the project-only screen.health_changed (never on GET /screen/events).

Health is never authorization.


Operation: putRuntimeHealth.

- Authentication: pairedRuntimeCookie OR nativeSessionAuthorization
- Request body: RuntimeHealthWrite

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| uptime_s | optional | integer | Seconds since the device (OS) booted. minimum: 0 |
| app_uptime_s | optional | integer | Seconds since the Player process started. minimum: 0 |
| memory | optional | object |  |
| cpu | optional | object |  |
| temperature_c | optional | number | SoC or panel temperature in degrees Celsius. minimum: -50 maximum: 150 |
| display | optional | object |  |
| network | optional | object |  |
| crashes_24h | optional | integer | Player crashes in the rolling last 24 hours. minimum: 0 maximum: 1000000 |
| renderer_restarts_24h | optional | integer | Renderer or web-view restarts in the rolling last 24 hours. minimum: 0 maximum: 1000000 |

#### Request template

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.

~~~sh
curl --request PUT --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://play.screenrig.ai/screen/health'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | Health report stored. | no body; headers: Cache-Control, RateLimit-Policy, RateLimit |
| 400 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 401 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 403 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 409 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 429 | Stable rate_limited problem on a Player-facing runtime or content route, in the runtime document shape (RuntimeProblem). Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | RuntimeProblem (application/problem+json); headers: Retry-After, RateLimit-Policy, RateLimit |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### PUT /screen/storage

**Put Runtime Storage**

Reports native player storage, the player's storage plan, and transferred-byte accounting. Path does not name screen_id; the paired runtime session binds it. Unpaired and anonymous sessions are forbidden. Native cookies are rejected; browser must not adopt header profiles. Unknown members are ignored (docs/player-compatibility.md §1.1).

The stored value is the sanitized project ScreenStorage plus server received_at, not the raw body. manifest_revision must be the screen's desired or active revision, or the response is 409 resource_conflict and nothing is stored. Excluded page ids must exist in that revision.

An identical fit class does not emit screen.storage_shortfall or screen.storage_shortfall_cleared. Those events are project-only and are not delivered on GET /screen/events. Storage is never authorization or admission. ScreenPatch cannot write it. A report older than 24 hours by received_at is stale to consumers.


Operation: putRuntimeStorage.

- Authentication: pairedRuntimeCookie OR nativeSessionAuthorization
- Request body: RuntimeStorageWrite

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| observed_at | required | string | format: "date-time" |
| volume | required | ScreenStorageVolume |  |
| cache | required | ScreenStorageCache |  |
| durability | required | string | Allowed: "durable", "purgeable" |
| plan | required | ScreenStoragePlan |  |
| transfer_24h | required | ScreenStorageTransfer |  |

#### Request template

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.

~~~sh
curl --request PUT --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://play.screenrig.ai/screen/storage'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | Storage report stored. | no body; headers: Cache-Control, RateLimit-Policy, RateLimit |
| 400 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 401 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 403 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 409 | resource_conflict - manifest_revision is not the screen's desired or active revision. The report is not stored. | RuntimeProblem (application/problem+json) |
| 429 | Stable rate_limited problem on a Player-facing runtime or content route, in the runtime document shape (RuntimeProblem). Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | RuntimeProblem (application/problem+json); headers: Retry-After, RateLimit-Policy, RateLimit |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### PUT /screen/screen-label

**Put Runtime Screen Label**

Renames the screen bound to the paired runtime session. It is the same rename as PATCH /api/screens/{id} name: the server trims screen_label and requires 1 to 120 UTF-8 bytes without control characters, otherwise invalid_request.

The rename records the same screen events, and on an assigned screen it changes the manifest screen_label and emits screen.manifest_changed to the Player. Path and body do not name screen_id; the paired runtime session binds it. No If-Match or Idempotency-Key; last write wins. Auth is the runtime cookie or ScreenRig-Session, as GET /screen/chrome.

Anonymous and unpaired sessions are unauthorized. An archived screen is screen_archived. Prepaid remaining credit is not a gate. A Player that receives 404 treats the route as unsupported and keeps the name locally (docs/player-compatibility.md §2.5).


Operation: putRuntimeScreenLabel.

- Authentication: pairedRuntimeCookie OR nativeSessionAuthorization
- Request body: RuntimeScreenLabelWrite

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| screen_label | required | string | minLength: 1 |

#### Request template

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.

~~~sh
curl --request PUT --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://play.screenrig.ai/screen/screen-label'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Screen renamed. screen_label is the stored name. | RuntimeScreenLabel (application/json); headers: Cache-Control, RateLimit-Policy, RateLimit |
| 400 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 401 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 403 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 409 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 429 | Stable rate_limited problem on a Player-facing runtime or content route, in the runtime document shape (RuntimeProblem). Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | RuntimeProblem (application/problem+json); headers: Retry-After, RateLimit-Policy, RateLimit |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### POST /screen/unpair

**Unpair Runtime**

Cookie-only unpair. Archives a cookie-paired screen that has no player public-key enrollment, with archive_reason device_unpair. The device credential stays bound: a browser that still holds it mints an archived session (dark glass), and unarchive resumes it with no re-pairing (docs/player-compatibility.md §5.1).

A screen that is already archived, or already gone, is success. Identity-enrolled screens return screen_archive_required; those players reset only via POST /screen/native/identity/reset. Path and body do not name screen_id; the paired runtime session binds it. No If-Match or Idempotency-Key. Browser 204 expires the runtime, device, and pairing host cookies.

Native identity-bound calls never 204. Anonymous or unpaired sessions are forbidden. Prepaid remaining credit is not a gate.


Operation: unpairRuntime.

- Authentication: pairedRuntimeCookie OR nativeSessionAuthorization
- Request body: none

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt 'https://play.screenrig.ai/screen/unpair'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | Cookie-paired screen archived with its credential kept. Browser pairing cookies expired. | no body; headers: Cache-Control, RateLimit-Policy, RateLimit |
| 401 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 403 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 409 | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |
| 429 | Stable rate_limited problem on a Player-facing runtime or content route, in the runtime document shape (RuntimeProblem). Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | RuntimeProblem (application/problem+json); headers: Retry-After, RateLimit-Policy, RateLimit |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### POST /screen/manifests/{manifest_revision}/releases/{release_id}/launch

**Launch Application Release**


Operation: launchApplicationRelease.

- Authentication: runtimeCookie OR nativeSessionAuthorization
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| manifest_revision | path | required |  |
| release_id | path | required |  |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt 'https://play.screenrig.ai/screen/manifests/MANIFEST_REVISION/releases/RELEASE_ID/launch'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Single-use 30-second exact-release-host launch ticket | ReleaseLaunch (application/json); headers: Referrer-Policy, RateLimit-Policy, RateLimit |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### POST /screen/manifests/{manifest_revision}/selectors/resolve

**Resolve all dynamic media selectors for one playlist loop**

Uses the authenticated screen's assigned playlist and its manifest revision. Returns a fixed snapshot in filename order; Players retain cursor and shuffle state across loops. Uploads and tag changes never rewrite this manifest. Empty matches are valid.

Selected media are authorized by the screen's runtime grant independently of the immutable manifest definition.


Operation: resolveRuntimeSelectors.

- Authentication: runtimeCookie OR nativeSessionAuthorization
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| manifest_revision | path | required |  |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt 'https://api.screenrig.ai/screen/manifests/MANIFEST_REVISION/selectors/resolve'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Resolved loop snapshot, including item metadata and media_end safety deadlines. | RuntimeSelectorSnapshot (application/json) |
| 401 | Runtime session invalid or expired | RuntimeProblem (application/problem+json) |
| 403 | Runtime capability denied | RuntimeProblem (application/problem+json) |
| 503 | Manifest projection pending or selector snapshot degraded; retain the current loop and retry | RuntimeProblem (application/problem+json) |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### GET /screen/card-data/{key}

**Get Runtime Card Data**

Returns a card data or feed document, the card data or feed key of a card primitive in the screen's current manifest. A key is readable only while a desired, active, or transition-grace grant of the session's screen lists it; knowing a key is never enough.

Feed keys start with feed: and read the system feed namespace (screenrig.weather/v1 and the other feed schemas); every other key reads the card data of the screen's project. The body is the document itself.

ETag is the sha256 of its bytes and If-None-Match answers 304; keep the last good document for offline playback and refetch on screen.card_data_changed for the key. 404 means no document is stored yet; keep the card hidden.


Operation: getRuntimeCardData.

- Authentication: runtimeCookie OR nativeSessionAuthorization
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| key | path | required |  |
| If-None-Match | header | optional |  |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://play.screenrig.ai/screen/card-data/KEY'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The document | object (application/json); headers: ETag |
| 304 | Not modified | no body |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

## Runtime application K/V

### GET /screen/apps/{application_id}/kv

**List Runtime KV**

Returns at most 200 entries in ascending key order and does not page; a namespace with more keys is only partly listed. Lists metadata for an application the screen's current content holds.

Runtime KV is authorized by the Player's runtime session (kv.read, and kv.write for writes) and the screen's current grant, which must hold a ready release of the application; a ScreenRig-Primitive-Capability header is ignored. Uploaded frames never call these routes and must use the validated parent SDK bridge.


Operation: listRuntimeKV.

- Authentication: runtimeCookie OR nativeSessionAuthorization
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| application_id | path | required |  |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://play.screenrig.ai/screen/apps/APPLICATION_ID/kv'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Manifest-authorized application KV | RuntimeKVList (application/json) |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### GET /screen/apps/{application_id}/kv/{key}

**Get Runtime KV**


Operation: getRuntimeKV.

- Authentication: runtimeCookie OR nativeSessionAuthorization
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| application_id | path | required |  |
| key | path | required |  |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://play.screenrig.ai/screen/apps/APPLICATION_ID/kv/KEY'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Manifest-authorized application KV value | RuntimeKVEntry (application/json) |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### PUT /screen/apps/{application_id}/kv/{key}

**Put Runtime KV**


Operation: putRuntimeKV.

- Authentication: pairedRuntimeCookie OR nativeSessionAuthorization
- Request body: RuntimeKVWrite

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| application_id | path | required |  |
| key | path | required |  |
| If-Match | header | optional | Optional positive resource revision. Omit to create or overwrite a key; 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 |
| --- | --- | --- | --- |
| value_base64 | required | string | maxLength: 1398104 |
| content_type | optional | string | default: "application/octet-stream" minLength: 1 maxLength: 127 |

#### Request template

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.

~~~sh
curl --request PUT --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://play.screenrig.ai/screen/apps/APPLICATION_ID/kv/KEY'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Paired-device KV mutation | RuntimeKVEntry (application/json); headers: RateLimit-Policy, RateLimit |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### DELETE /screen/apps/{application_id}/kv/{key}

**Delete Runtime KV**


Operation: deleteRuntimeKV.

- Authentication: pairedRuntimeCookie OR nativeSessionAuthorization
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| application_id | path | required |  |
| key | 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

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.

~~~sh
curl --request DELETE --cookie cookies.txt --cookie-jar cookies.txt 'https://play.screenrig.ai/screen/apps/APPLICATION_ID/kv/KEY'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | Paired-device KV deletion | no body; headers: RateLimit-Policy, RateLimit |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

## Protected content and release assets

### GET /screen/ad-decisions/{decision_id}

**Get Ad Decision Content**

Serves the one private object bound to an issued decision. Authority is the live paired identity plus the decision binding; the buyer project and media ID are resolved server-side, never from the caller, and no object key or signed URL is published. A zero-length probe returns metadata only.


Operation: getAdDecisionContent.

- Authentication: runtimeCookie OR nativeSessionAuthorization
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| decision_id | path | required |  |
| Range | header | optional | V1 accepts one byte range only. |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://play.screenrig.ai/screen/ad-decisions/DECISION_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Complete decision-bound private media. | no body; headers: Accept-Ranges, Content-Length, ETag |
| 206 | One standards-correct byte range. | no body; headers: Accept-Ranges, Content-Range, Content-Length, ETag |
| 404 | not_found - unknown decision. | RuntimeProblem (application/problem+json) |
| 410 | decision expired or otherwise not servable. | RuntimeProblem (application/problem+json) |
| 416 | invalid_range. | RuntimeProblem (application/problem+json); headers: Content-Range, Accept-Ranges |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### HEAD /screen/ad-decisions/{decision_id}

**Head Ad Decision Content**

Identical authority and metadata to the GET with no body.


Operation: headAdDecisionContent.

- Authentication: runtimeCookie OR nativeSessionAuthorization
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| decision_id | path | required |  |
| Range | header | optional | V1 accepts one byte range only. |

#### Request template

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.

~~~sh
curl --request HEAD --cookie cookies.txt --cookie-jar cookies.txt 'https://play.screenrig.ai/screen/ad-decisions/DECISION_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Metadata only. | no body; headers: Accept-Ranges, Content-Length, ETag |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### GET /media/{media_id}

**Get Stable Protected Media**

Stable immutable media URL shared across playlist revisions. The runtime session identifies the screen and project for authorization and bandwidth accounting. Every request requires membership in that screen's desired, active, or unexpired transition-grace grant in its current content-access generation. IDs alone grant no access.

Browser cookie and native session authentication remain on the trusted Player origin; redirects and public object-storage access are not used.


Operation: getStableProtectedMedia.

- Authentication: runtimeCookie OR nativeSessionAuthorization
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| media_id | path | required |  |
| Range | header | optional | V1 accepts one byte range only. |
| Sec-Fetch-Site | header | optional |  |
| Sec-Fetch-Mode | header | optional |  |
| Sec-Fetch-Dest | header | optional |  |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://play.screenrig.ai/media/MEDIA_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Session-authorized complete private media. Browser Fetch Metadata must describe trusted-player same-origin media/image/video use; authenticated native clients may omit it. | no body; headers: Accept-Ranges, Content-Length, ETag, Content-Disposition |
| 206 | One standards-correct byte range. | no body; headers: Accept-Ranges, Content-Range, Content-Length, ETag |
| 416 | Unsatisfiable, multiple, or malformed range. | RuntimeProblem (application/problem+json); headers: Content-Range, Accept-Ranges |
| 429 | Per-client, screen, or project protected-media egress limit exceeded. | RuntimeProblem (application/problem+json); headers: Retry-After |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### HEAD /media/{media_id}

**Head Stable Protected Media**

Session-authorized immutable media metadata with the same grant checks as GET and no response body.


Operation: headStableProtectedMedia.

- Authentication: runtimeCookie OR nativeSessionAuthorization
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| media_id | path | required |  |
| Range | header | optional | V1 accepts one byte range only. |
| Sec-Fetch-Site | header | optional |  |
| Sec-Fetch-Mode | header | optional |  |
| Sec-Fetch-Dest | header | optional |  |

#### Request template

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.

~~~sh
curl --request HEAD --cookie cookies.txt --cookie-jar cookies.txt 'https://play.screenrig.ai/media/MEDIA_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Session-authorized complete private media. Browser Fetch Metadata must describe trusted-player same-origin media/image/video use; authenticated native clients may omit it. | no body; headers: Accept-Ranges, Content-Length, ETag, Content-Disposition |
| 206 | One standards-correct byte range. | no body; headers: Accept-Ranges, Content-Range, Content-Length, ETag |
| 416 | Unsatisfiable, multiple, or malformed range. | RuntimeProblem (application/problem+json); headers: Content-Range, Accept-Ranges |
| 429 | Per-client, screen, or project protected-media egress limit exceeded. | RuntimeProblem (application/problem+json); headers: Retry-After |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### GET /media/manifests/{manifest_revision}/{media_id}

**Get Protected Media**


Operation: getProtectedMedia.

- Authentication: runtimeCookie OR nativeSessionAuthorization
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| manifest_revision | path | required |  |
| media_id | path | required |  |
| Range | header | optional | V1 accepts one byte range only. |
| Sec-Fetch-Site | header | optional |  |
| Sec-Fetch-Mode | header | optional |  |
| Sec-Fetch-Dest | header | optional |  |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://play.screenrig.ai/media/manifests/MANIFEST_REVISION/MEDIA_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Session-authorized complete private media. Browser Fetch Metadata must describe trusted-player same-origin media/image/video use; authenticated native clients may omit it. | no body; headers: Accept-Ranges, Content-Length, ETag, Content-Disposition |
| 206 | One standards-correct byte range. | no body; headers: Accept-Ranges, Content-Range, Content-Length, ETag |
| 416 | Unsatisfiable, multiple, or malformed range. | RuntimeProblem (application/problem+json); headers: Content-Range, Accept-Ranges |
| 429 | Per-client, screen, or project protected-media egress limit exceeded. | RuntimeProblem (application/problem+json); headers: Retry-After |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### GET /screen/manifests/{manifest_revision}/releases/{release_id}/package

**Get Native Webapp Package**

Retrieves the normalized immutable package for one uploaded application release so a paired native player can retain its authorized last-known-good release across process restart and temporary network loss.

The paired-device cookie, exact screen grant, manifest revision, content-access generation, release membership, and ready package metadata are checked on every request. Project tokens, anonymous runtime cookies, release cookies, external iframe primitives, and direct object-storage access never authorize package bytes.


Operation: getNativeWebappPackage.

- Authentication: pairedDeviceCookie OR nativeSessionAuthorization
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| manifest_revision | path | required |  |
| release_id | path | required |  |
| Range | header | optional | V1 accepts one byte range only. |
| Sec-Fetch-Site | header | optional |  |
| Sec-Fetch-Mode | header | optional |  |
| Sec-Fetch-Dest | header | optional |  |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://play.screenrig.ai/screen/manifests/MANIFEST_REVISION/releases/RELEASE_ID/package'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Complete normalized private package. | no body; headers: Accept-Ranges, Content-Length, Content-Type, Content-Disposition, Cache-Control, ETag, Referrer-Policy, Cross-Origin-Resource-Policy, X-Content-Type-Options, Vary |
| 206 | One standards-correct byte range from the same immutable package. | no body; headers: Accept-Ranges, Content-Range, Content-Length, Content-Type, Content-Disposition, Cache-Control, ETag, Referrer-Policy, Cross-Origin-Resource-Policy, X-Content-Type-Options, Vary |
| 416 | Unsatisfiable, multiple, or malformed range. | RuntimeProblem (application/problem+json); headers: Content-Range, Accept-Ranges |
| 429 | Stable rate_limited problem on a Player-facing runtime or content route, in the runtime document shape (RuntimeProblem). Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | RuntimeProblem (application/problem+json); headers: Retry-After, RateLimit-Policy, RateLimit |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### HEAD /screen/manifests/{manifest_revision}/releases/{release_id}/package

**Head Native Webapp Package**

Returns the same authorization and immutable identity headers as GET without package bytes.


Operation: headNativeWebappPackage.

- Authentication: pairedDeviceCookie OR nativeSessionAuthorization
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| manifest_revision | path | required |  |
| release_id | path | required |  |
| Range | header | optional | V1 accepts one byte range only. |
| Sec-Fetch-Site | header | optional |  |
| Sec-Fetch-Mode | header | optional |  |
| Sec-Fetch-Dest | header | optional |  |

#### Request template

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.

~~~sh
curl --request HEAD --cookie cookies.txt --cookie-jar cookies.txt 'https://play.screenrig.ai/screen/manifests/MANIFEST_REVISION/releases/RELEASE_ID/package'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Authorized normalized package metadata. | no body; headers: Accept-Ranges, Content-Length, Content-Type, Content-Disposition, Cache-Control, ETag, Referrer-Policy, Cross-Origin-Resource-Policy, X-Content-Type-Options, Vary |
| 206 | Authorized metadata for one byte range. | no body; headers: Accept-Ranges, Content-Range, Content-Length, Content-Type, Content-Disposition, Cache-Control, ETag, Referrer-Policy, Cross-Origin-Resource-Policy, X-Content-Type-Options, Vary |
| 416 | Unsatisfiable, multiple, or malformed range. | no body; headers: Content-Range, Accept-Ranges |
| 429 | Stable rate_limited problem on a Player-facing runtime or content route, in the runtime document shape (RuntimeProblem). Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | RuntimeProblem (application/problem+json); headers: Retry-After, RateLimit-Policy, RateLimit |
| default | Stable RFC 9457 problem response on a Player-facing runtime, content, or probe route, in the runtime document shape (RuntimeProblem). 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. | RuntimeProblem (application/problem+json) |

### GET /{asset_path}

**Consume Launch Ticket Or Get Release Asset**

Exact-release-host operation. With a ticket query, atomically consumes the single-use 30-second ticket, binds Host/release/screen/content-generation/manifest/grant, sets only the exact-host release-grant cookie, and redirects to the same path without the ticket. Without a ticket, validates that cookie on every GET/HEAD.

The asset_path parameter is greedy (x-screenrig-greedy-path) and is resolved only beneath the ready release root. Runtime/project/device cookies and project bearer tokens never authorize this operation.


Operation: consumeLaunchTicketOrGetReleaseAsset.

- Authentication: releaseLaunchTicket OR releaseGrantCookie
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| asset_path | path | required |  |
| Sec-Fetch-Site | header | optional |  |
| Sec-Fetch-Mode | header | optional |  |
| Sec-Fetch-Dest | header | optional |  |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://r-{release_host_label}.apps.screenrig.ai/index.html'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Exact-host grant-authorized immutable asset. | no body; headers: Referrer-Policy, X-Content-Type-Options, ETag |
| 302 | Ticket consumed once; release-grant cookie issued; ticket removed from Location. | no body; headers: Location, Set-Cookie, Cache-Control, Referrer-Policy |
| 403 | Wrong Host/release/screen/generation/grant, stale/replayed ticket, invalid cookie, contradictory Fetch Metadata, or disallowed method. | Problem (application/problem+json) |
| 404 | Asset absent without exposing release filesystem shape. | Problem (application/problem+json) |

## People and project dashboard

### POST /api/invitations/inspect

**Inspect Dashboard Invitation**

Inspects an invitation without consuming it. Never reports signup or verification state.


Operation: inspectDashboardInvitation.

- Authentication: none
- Request body: InvitationInspectRequest

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| token | required | string | pattern: "^[A-Za-z0-9_-]{43}$" |

#### Request template

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.

~~~sh
curl --request POST --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/invitations/inspect'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Inspects an invitation without consuming it. | InvitationInspect (application/json); headers: 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) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, RateLimit-Policy, RateLimit |

### POST /api/invitations/passkey-options

**Start Dashboard Invitation Passkey**

Starts a five-minute registration ceremony bound to the invitation digest and mode. Link create_login requires a verified __Host-screenrig-signup cookie and uses its name. Email create_login binds the supplied display name; reset binds the target person.


Operation: startDashboardInvitationPasskey.

- Authentication: none OR invitationSignup
- Request body: InvitationPasskeyOptionsRequest

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| token | required | string | pattern: "^[A-Za-z0-9_-]{43}$" |
| mode | required | string | Allowed: "create_login", "reset" |
| display_name | optional | DashboardDisplayName | Trimmed printable display name, at most 80 UTF-8 bytes. Rejects controls, URL-looking text, @, www., ://, and credential material. Never authorization. minLength: 1 maxLength: 80 |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/invitations/passkey-options'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Starts a five-minute registration ceremony bound to the invitation digest and mode. | DashboardPasskeyRegistration (application/json); headers: 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) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, RateLimit-Policy, RateLimit |

### POST /api/invitations/email-verifications

**Verify Dashboard Invitation Email**

Starts a browser-bound link signup and revokes earlier signups for this invitation. The verification challenge is bound to this signup. Development auth verifies the signup immediately without mail.


Operation: verifyDashboardInvitationEmail.

- Authentication: none
- Request body: InvitationEmailVerificationRequest

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| token | required | string | pattern: "^[A-Za-z0-9_-]{43}$" |
| display_name | required | DashboardDisplayName | Trimmed printable display name, at most 80 UTF-8 bytes. Rejects controls, URL-looking text, @, www., ://, and credential material. Never authorization. minLength: 1 maxLength: 80 |
| email | required | string | minLength: 3 maxLength: 254 format: "email" |

#### Request template

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.

~~~sh
curl --request POST --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/invitations/email-verifications'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 202 | Starts a browser-bound link signup and revokes earlier signups for this invitation. | InvitationEmailVerificationAccepted (application/json); headers: Set-Cookie, 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) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, RateLimit-Policy, RateLimit |

### GET /api/invitations/signup

**Get Dashboard Invitation Signup**

Returns the live signup belonging to this browser. A missing, revoked, consumed, or expired signup cookie returns invitation_invalid.


Operation: getDashboardInvitationSignup.

- Authentication: invitationSignup
- Request body: none

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/invitations/signup'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Returns the live signup belonging to this browser. | InvitationSignup (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/invitations/accept

**Accept Dashboard Invitation**

Accepts an invitation transactionally and mints a fresh person session, deleting the old session when present. Link create_login requires the verified __Host-screenrig-signup cookie for this invitation. signed_in requires a dashboard session and recent sign-in or fresh proof; email delivery requires a matching verified email.

An already-registered create_login address returns resource_conflict. An organization_admin invitation adds the person to the organization's admins and opens no project; it returns organization_admin_limit when the organization already has 20 admins and stays outstanding, and resource_conflict when the organization was deleted.


Operation: acceptDashboardInvitation.

- Authentication: none OR invitationSignup OR dashboardSession
- Request body: InvitationAcceptRequest

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| token | required | string | pattern: "^[A-Za-z0-9_-]{43}$" |
| mode | required | string | Allowed: "create_login", "signed_in", "reset" |
| display_name | optional | DashboardDisplayName | Trimmed printable display name, at most 80 UTF-8 bytes. Rejects controls, URL-looking text, @, www., ://, and credential material. Never authorization. minLength: 1 maxLength: 80 |
| credential | optional | InvitationCredential |  |
| proof | optional | DashboardProof |  |
| buyer | optional | InvitationBuyer |  |
| remove_other_passkeys | optional | boolean | Sign-in reset only (mode reset). Removes the person's passkeys other than the one this reset registers, or every passkey when the reset sets a password, so a lost or stolen device stops signing in. Absent means true; any other invitation refuses it. default: true |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/invitations/accept'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Accepts an invitation transactionally and mints a fresh person session, deleting the old session when present. | InvitationAccepted (application/json); headers: Set-Cookie, 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) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, RateLimit-Policy, RateLimit |

### PUT /api/session/project

**Switch Dashboard Project**

Switches to one of the person’s projects and updates its last-used time. A staff member may also switch to a project they shadow (GET /api/staff/shadows); that session reports shadow true, is read-only, and updates no last-used time. Any other project returns not_found.


Operation: switchDashboardProject.

- Authentication: dashboardSession
- Request body: DashboardProjectSwitch

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| project_id | required | string | minLength: 1 |

#### Request template

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.

~~~sh
curl --request PUT --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/session/project'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Switches to one of the person’s projects and updates its last-used time. | DashboardSession (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/members

**List Dashboard Members**

Lists members of the current project without their private email addresses. Every member has equal authority except removal; owner marks the project owner, and viewer_user_id and viewer_is_owner describe the signed-in person.


Operation: listDashboardMembers.

- Authentication: dashboardSession
- Request body: none

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/members'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Lists members of the current project without their private email addresses. | DashboardMemberList (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/members/{user_id}

**Remove Dashboard Member**

Removes a member from the current project. The project owner removes any other member; any member removes themselves to leave. The owner is never removed and never leaves (owner_cannot_leave; transfer ownership first). A member removing someone else is refused with owner_required. A person who is not a member, including one already removed, is not_found.

In one transaction the membership ends, that person's outstanding email invitations to the project are revoked, and the project event member.removed is appended (details user_id, removed_by_user_id, left). The person's sessions stop resolving the project on their next request and fall back as they do after a project deletion.

Agent memberships are separate and stay. People only; no bearer route.


Operation: removeDashboardMember.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| user_id | path | required |  |
| project_id | query | required | The project the caller means; a session whose selection has changed is refused with resource_conflict. |

#### Request template

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.

~~~sh
curl --request DELETE --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/members/USER_ID?project_id=PROJECT_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | The membership ended. | 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) |

### GET /api/staff/projects/{id}/deletion-preview

**Preview Dashboard Staff Project Deletion**

Shows the current project name and revision plus the existing operator cascade counts, including bound active and archived devices. Deletion requires a separate approved project.delete request; paired devices require include_paired_screens in that exact request.


Operation: previewDashboardStaffProjectDeletion.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/staff/projects/RESOURCE_ID/deletion-preview'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Project metadata and cascade counts, without credentials or device keys. | DashboardStaffDeletionPreview (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/staff/organizations/{id}/lifecycle-preview

**Preview Dashboard Staff Organization Lifecycle**

Shows the organization's current name and revision and up to 101 live project IDs. An approved merge binds the complete source project set, with at most 100 projects, and the destination revision. Reseller relationships require a dated end first. Deletion requires no live projects.

Closure removes domain claims and retains the original wallet, recorded obligations and historical readers. A closed organization receives no new grants; a merge grants no membership and transfers no old money or charges.


Operation: previewDashboardStaffOrganizationLifecycle.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/staff/organizations/RESOURCE_ID/lifecycle-preview'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Metadata to confirm deletion or a complete atomic merge. | DashboardStaffOrganizationLifecyclePreview (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/staff/actions

**Prepare Dashboard Staff Action**

Stores one normalized change for ten minutes, bound to this staff session. Preparation grants no access. Approve its request_id with a fresh staff_action passkey proof; the approval accepts no replacement change.


Operation: prepareDashboardStaffAction.

- Authentication: dashboardSession
- Request body: DashboardStaffChange

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| action | required | string | Allowed: "organization.create", "organization.rename", "organization.admins", "organization.domain", "project.rename", "project.move", "project.owner", "person.deactivate", "person.reinstate", "person.free-cap", "person.reset-sign-in", "organization.plan", "project.plan-to-organization", "project.plan", "project.delete", "organization.delete", "organization.merge", "staff.grant", "staff.revoke", "project.write-mode", "project.write-mode-end", "project.write", "project.shadow", "project.unshadow", "project.member", "project.member-remove", "demo.claim", "demo.release", "demo.offer-live", "demo.send-booking-link", "demo.record-outcome" |
| organization_id | optional | string |  |
| project_id | optional | string |  |
| user_id | optional | string |  |
| name | optional | string |  |
| domain | optional | string |  |
| admin_user_ids | optional | array |  |
| reason | optional | string | maxLength: 240 |
| free_limit | optional | integer | minimum: 0 maximum: 10000 |
| plan_id | optional | string | Allowed: "standard", "premium", "enterprise", "staff", "promo" |
| revision | optional | integer | minimum: 1 |
| include_paired_screens | optional | boolean | default: false |
| target_organization_id | optional | string |  |
| target_revision | optional | integer | minimum: 1 |
| project_ids | optional | array |  |
| demo_request_id | optional | string |  |
| demo_version | optional | string | Exact saved updated_at from the demo read. format: "date-time" |
| demo_revision | optional | integer | Exact staff tracking revision from the demo read. minimum: 0 |
| demo_attendance | optional | string | Allowed: "unrecorded", "attended", "no_show" |
| demo_outcome | optional | string | Allowed: "unrecorded", "interested", "converted", "not_fit", "closed" |
| demo_followup | optional | string | Allowed: "unrecorded", "pending", "scheduled", "done", "not_needed" |
| demo_followup_at | optional | string | format: "date-time" |
| revoke_passkeys | optional | boolean | person.reset-sign-in only: also remove every passkey and end every session at once. default: false |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/staff/actions'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | The exact proposed change and its approval subject. | DashboardStaffActionRequest (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/staff/actions/{id}/approve

**Approve Dashboard Staff Action**

A fresh passkey proof binds to this specific request_id. Current staff and session authority, the change, exact-retry result and permanent audit share one transaction. Development auth accepts a password proof. Completed retries recheck current staff authority before returning their original result.


Operation: approveDashboardStaffAction.

- Authentication: dashboardSession
- Request body: object

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| proof | required | DashboardProof |  |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/staff/actions/RESOURCE_ID/approve'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The result of the approved change. person.reset-sign-in answers a DashboardStaffSignInReset. | object (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/staff/organizations

**List Dashboard Staff Organizations**


Operation: listDashboardStaffOrganizations.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| after | query | optional |  |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/staff/organizations'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | At most 100 organizations, including empty organizations. | DashboardStaffOrganizationPage (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/staff/organizations/{id}

**Get Dashboard Staff Organization**

Staff-cookie only; the staff flag is reread for each request. One live organization in the shape of an item of GET /api/staff/organizations. An unknown or deleted organization answers 404 not_found.


Operation: getDashboardStaffOrganization.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/staff/organizations/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The organization. | DashboardStaffOrganization (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) |

### GET /api/staff/organizations/{id}/projects

**List Dashboard Staff Organization Projects**


Operation: listDashboardStaffOrganizationProjects.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |
| after | query | optional |  |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/staff/organizations/RESOURCE_ID/projects'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | At most 100 live projects with their owners. | OrganizationProjectPage (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/session/staff-write-mode

**End this session's approved staff write access**

Removes the current write lease and records its end. A fresh approval is required to enter write mode again. Uses the dashboard cookie and same-origin mutation guard.


Operation: endDashboardStaffWriteMode.

- Authentication: dashboardSession
- Request body: none

#### Request template

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.

~~~sh
curl --request DELETE --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/session/staff-write-mode'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | Write mode ended. | 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) |

### GET /api/staff/audit

**List Dashboard Staff Audit**


Operation: listDashboardStaffAudit.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| after | query | optional |  |
| project | query | optional |  |
| organization | query | optional | Organization ID. Selects changes to the organization (change.organization_id, or change.target_organization_id of a merge into it) and changes to any of its current live projects (change.project_id). Combines with the other scopes. |
| actor | query | optional |  |
| outcome | query | optional | Recorded audit outcome. Admitted writes are unknown; blocked approvals never became changes. |
| since | query | optional |  |
| until | query | optional |  |
| exclude_system | query | optional | true leaves out the changes the backend made on its own, recorded with user_id system (an orphaned organization's deletion, change.reason orphaned). |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/staff/audit'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | At most 100 immutable staff changes, newest first. Pass next_cursor as after for older entries. | DashboardStaffAuditPage (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/staff/operations/live

**Get Dashboard Staff Operations Live**

Staff-cookie only; the staff flag is reread for each request. Live edge rates from the proxy nodes' 10-second counters, over the last 60 seconds of complete buckets, and the last 15 minutes as a series. At most 5 seconds old. Answers 500 dependency_unavailable when the counters cannot be read.


Operation: getDashboardStaffOperationsLive.

- Authentication: dashboardSession
- Request body: none

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/staff/operations/live'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Live edge rates, per lane, status, traffic class, cache outcome, limit and proxy node. | DashboardStaffOperationsLive (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) |

### GET /api/staff/operations/insights

**Get Dashboard Staff Operations Insights**

Staff-cookie only; the staff flag is reread for each request. Fixed read-only reports over the edge, service log and usage records, at most 60 seconds old. Each section is null when its read failed or its source is missing. Answers not_ready when the report data source is unavailable.


Operation: getDashboardStaffOperationsInsights.

- Authentication: dashboardSession
- Request body: none

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/staff/operations/insights'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Top downloaders, recent errors, latency, top routes and clients, the 24-hour trend, open streams and ingest lag. | DashboardStaffOperationsInsights (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) |

### GET /api/staff/reports/catalog

**Get Dashboard Staff Report Catalog**

Staff-only catalog of approved operational datasets and query fields. Uses dashboard cookie authentication.


Operation: getDashboardStaffReportCatalog.

- Authentication: dashboardSession
- Request body: none

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/staff/reports/catalog'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Available read-only datasets and enforced limits. | DashboardStaffReportCatalog (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/staff/reports

**Run Dashboard Staff Report**

Staff-only bounded report or Rig-style query. Accepts a closed read-only grammar, not SQL. Requires dashboard cookie authentication and CSRF proof. Personal fields and free text are excluded.


Operation: runDashboardStaffReport.

- Authentication: dashboardSession
- Request body: DashboardStaffReportRequest

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| words | required | array |  |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/staff/reports'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Reports with time window, source, freshness and sampling metadata. | DashboardStaffReportResponse (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/staff/feedback

**List Dashboard Staff Feedback**

Staff-cookie only; the staff flag is reread for each request. Lists bugs and feature requests across projects, newest first by created_at then id. Text is plain text, never trusted HTML. Pass next_cursor as before for older entries.


Operation: listDashboardStaffFeedback.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| before | query | optional | Opaque cursor from the previous page. |
| kind | query | optional | Only bug reports or only feature requests. Pass the same kind with before for the next page. |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/staff/feedback'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | At most 100 feedback submissions, newest first. | DashboardStaffFeedbackList (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) |

### GET /api/staff/users

**List Dashboard Staff Users**

Staff only: the session's person must carry the screenrig_staff flag, reread on every request; any other session answers 403 forbidden. Lists every person, newest first, with login email, credential counts, last activity, and their live projects.


Operation: listDashboardStaffUsers.

- Authentication: dashboardSession
- Request body: none

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/staff/users'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Every person with their live projects. | DashboardStaffUserList (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) |

### GET /api/staff/users/{id}

**Get Dashboard Staff User**

Staff only: the session's person must carry the screenrig_staff flag, reread on every request; any other session answers 403 forbidden. Returns one person with their live projects, credential counts, and live dashboard session count.


Operation: getDashboardStaffUser.

- Authentication: dashboardSession
- Request body: none

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/staff/users/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | One person. | DashboardStaffUserDetail (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) |

### GET /api/staff/demos

**List Dashboard Staff Demos**

Staff-only inclusion-only demo queue. Server scopes precede newest-first 50-row cursor pages. Contains contact metadata, never session material or provider links. Does not switch projects.


Operation: listDashboardStaffDemos.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| q | query | optional |  |
| route | query | optional |  |
| booking | query | optional |  |
| attendance | query | optional |  |
| outcome | query | optional |  |
| followup | query | optional |  |
| owner | query | optional | Dashboard staff person ID or mine/unassigned. Slack ownership is separate. |
| due | query | optional | Pending follow-up or scheduled follow-up whose time has arrived. |
| after | query | optional |  |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/staff/demos'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Up to 50 matching demo requests and next-page cursor. | DashboardStaffDemoPage (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/staff/demos/{id}

**Get Dashboard Staff Demo**


Operation: getDashboardStaffDemo.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/staff/demos/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Safe contact, routing, booking and staff-tracking metadata; no visitor cookie or provider URLs. | DashboardStaffDemo (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/staff/journeys

**Get Dashboard Staff Journeys**

Read-only creation-cohort evidence for live signage-enabled projects. At most 1000 newest projects are evaluated; total and truncation make bounded coverage explicit. Missing older retained history is unknown. No project switch or write lease.


Operation: getDashboardStaffJourneys.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| since | query | optional | Creation cohort start inclusive. Defaults to seven days ago. |
| until | query | optional | Creation cohort end exclusive. Defaults to now. Range at most 90 days. |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/staff/journeys'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Recorded milestones and coverage; transition medians use exact first-recorded adjacent timestamps only. Return timing is latest observed use, not first-return time. | DashboardStaffJourneys (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/staff/activity

**List Dashboard Staff Activity**

Staff-only global customer events in the common logical retention window. No payloads or delivery material. Timestamp and ObjectID keyset pagination is independent of project sequence numbers. Missing attribution or outcomes are explicit.


Operation: listDashboardStaffActivity.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| project | query | optional |  |
| organization | query | optional | Organization ID. Selects events of the organization's current live projects; an organization without projects returns an empty page with the usual since, until and retention_seconds. Combines with project. |
| actor | query | optional | Exact person or agent ID. |
| outcome | query | optional |  |
| type | query | optional | Exact type or dotted family prefix. |
| severity | query | optional | Minimum severity. |
| since | query | optional | Inclusive start. Defaults to 24 hours ago; clipped to the common retention floor. |
| until | query | optional | Exclusive end. Defaults to now. |
| after | query | optional | next_cursor from the previous page. |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/staff/activity'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Up to 100 events with coverage and an opaque next cursor. | DashboardStaffActivityPage (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/staff/screens

**List Dashboard Staff Screens**

Staff-only complete fleet directory, newest first in cursor pages. Filters search all matching screens before paging. Reads do not switch the session project or grant a write lease.


Operation: listDashboardStaffScreens.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| q | query | optional | Literal case-insensitive search of label, ID, project, organization and reported Player kind/version. |
| project | query | optional |  |
| organization | query | optional |  |
| state | query | optional | Omitted lists active and pairing-pending screens. |
| presence | query | optional |  |
| after | query | optional | The next_cursor from the previous page. |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/staff/screens'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Up to fifty matching screens and an opaque next-page position. | DashboardStaffScreenPage (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) |

### GET /api/staff/screens/{id}

**Get Dashboard Staff Screen**

Staff-only read of one screen's fleet metadata, including archived screens. Screen IDs are never authorization; the current staff flag is reread for each request.


Operation: getDashboardStaffScreen.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/staff/screens/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Inclusion-only metadata without pairing credentials, recovery offers, addresses or content paths. | DashboardStaffScreen (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) |

### GET /api/staff/projects

**List Dashboard Staff Projects**

Staff only: the session's person must carry the screenrig_staff flag, reread on every request; any other session answers 403 forbidden. Lists every live project, newest first, with plan, usage, public credit remaining, and members.


Operation: listDashboardStaffProjects.

- Authentication: dashboardSession
- Request body: none

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/staff/projects'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Every live project with its members. | DashboardStaffProjectList (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) |

### GET /api/staff/projects/{id}

**Get Dashboard Staff Project**

Staff only: the session's person must carry the screenrig_staff flag, reread on every request; any other session answers 403 forbidden. Returns one live project with plan, usage, screen, media, application, and agent counts, and members.


Operation: getDashboardStaffProject.

- Authentication: dashboardSession
- Request body: none

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/staff/projects/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | One live project. | DashboardStaffProjectDetail (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) |

### GET /api/staff/members

**List Dashboard Staff Members**

Staff only: the session's person must carry the screenrig_staff flag, reread on every request; any other session answers 403 forbidden. Lists every staff member.


Operation: listDashboardStaffMembers.

- Authentication: dashboardSession
- Request body: none

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/staff/members'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Every staff member. | DashboardStaffMemberList (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/staff/members

**Grant Dashboard Staff Member**

Staff only: the session's person must carry the screenrig_staff flag, reread on every request; any other session answers 403 forbidden. Adds the person whose verified login is email as staff. Proof-protected: the body carries a fresh passkey proof from a staff_grant reauthentication whose subject_id is that email.

A password proof answers 401 reauthentication_required, except on a development server, which registers no passkeys. Granting is idempotent, and it makes a passkey mandatory for the added person (passkey_enrollment_required on their session until they add one). An address with no verified person answers 404 not_found.

Staff removal is operator-only (screenrig-server local staff revoke).


Operation: grantDashboardStaffMember.

- Authentication: dashboardSession
- Request body: DashboardStaffGrantRequest

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| email | required | string | minLength: 3 maxLength: 254 format: "email" |
| proof | required | DashboardProof |  |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/staff/members'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The person is staff. | DashboardStaffMember (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) |

### GET /api/staff/shadows

**List Dashboard Staff Shadows**

Staff only: the session's person must carry the screenrig_staff flag, reread on every request; any other session answers 403 forbidden. Lists the live projects the reader shadows, by name.

PUT /api/session/project switches to one read-only, without a membership: the session reports shadow true, every current-project mutation answers 403 forbidden, and the reader records no project activity, bills nothing, and does not appear among the project's members.


Operation: listDashboardStaffShadows.

- Authentication: dashboardSession
- Request body: none

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/staff/shadows'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The reader's shadowed projects. | DashboardStaffShadowList (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) |

### GET /api/staff/projects/{id}/support/conversations

**List Staff Project Support Conversations**

Staff-only project communication history, newest first, at most 100. Available across project plans; does not mark customer messages read.


Operation: listStaffProjectSupportConversations.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |
| before | query | optional |  |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/staff/projects/RESOURCE_ID/support/conversations'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Project support conversations. | SupportConversationList (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/staff/projects/{id}/support/conversations/{conversation}/messages

**List Staff Project Support Messages**

Staff-only messages in this project's conversation, ascending sequence, at most 100. Does not alter customer read receipts.


Operation: listStaffProjectSupportMessages.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |
| conversation | path | required |  |
| after | query | optional |  |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/staff/projects/RESOURCE_ID/support/conversations/CONVERSATION/messages'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Conversation messages. | SupportMessageList (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/staff/support/conversations

**List Dashboard Staff Support Inbox**

Staff-cookie only; the staff flag is reread for each request. Every project's support conversations, most recent activity first (updated_at, then id), 50 per page, with project and organization names, message facts and the reader's own unread count. counts covers the project, organization and q scopes, not the folder. Reading moves no read receipt.


Operation: listDashboardStaffSupportInbox.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| folder | query | optional | open is every conversation not closed; waiting is open and waiting for staff (human_requested). |
| project | query | optional | Project ID. |
| organization | query | optional | Organization ID: conversations of its current live projects. Combines with project. |
| q | query | optional | Literal, case-insensitive search of the subject. |
| before | query | optional | next from the previous page, a conversation ID. |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/staff/support/conversations'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | One page of conversations and the scope's folder counts. | DashboardStaffSupportInbox (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) |

### GET /api/staff/support/conversations/{id}

**Get Dashboard Staff Support Conversation**

Staff-cookie only. One conversation of any project, as an inbox row. An unknown conversation answers 404 not_found.


Operation: getDashboardStaffSupportConversation.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/staff/support/conversations/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The conversation. | DashboardStaffSupportConversation (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) |

### GET /api/staff/support/conversations/{id}/messages

**List Dashboard Staff Support Messages**

Staff-cookie only. The conversation's messages after a sequence, ascending, at most 100, with each message's author_id and origin where they were recorded. Reading moves no read receipt.


Operation: listDashboardStaffSupportMessages.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |
| after | query | optional | Project support message sequence; resumes strictly after this position. |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/staff/support/conversations/RESOURCE_ID/messages'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Conversation messages. | DashboardStaffSupportMessageList (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/staff/support/conversations/{id}/messages

**Reply Dashboard Staff Support Conversation**

Staff-cookie only, with the dashboard's same-origin mutation guard. Adds a staff reply under the staff member's first name. The customer sees it as any staff reply (role staff, counted in unread_staff, sent on /stream/support), any pending Luna answer is cancelled, and the reply is posted into the conversation's Slack thread as "Name (dashboard)".

A closed conversation answers 409 support_conversation_closed until it is reopened; a conversation whose project is not active answers 409 resource_conflict. ScreenRig credential material answers invalid_request; other credential-shaped text is replaced with [redacted].

A retry with the same key returns the original message for 24 hours; a different body under that key is idempotency_mismatch. At most 300 replies per staff member per hour.


Operation: replyDashboardStaffSupportConversation.

- Authentication: dashboardSession
- Request body: DashboardStaffSupportReply

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |
| Idempotency-Key | header | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| body | required | string | 1 to 4000 characters after surrounding whitespace is trimmed. minLength: 1 maxLength: 4000 |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Idempotency-Key: REQUEST_ID' --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/staff/support/conversations/RESOURCE_ID/messages'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | The reply and the conversation after it. | DashboardStaffSupportReplyResult (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) |

### PUT /api/staff/support/conversations/{id}/read

**Read Dashboard Staff Support Conversation**

Staff-cookie only, with the dashboard's same-origin mutation guard. Monotonically acknowledges the conversation's messages through sequence for this staff member alone; customer receipts never move. Cannot acknowledge beyond last_sequence.


Operation: readDashboardStaffSupportConversation.

- Authentication: dashboardSession
- Request body: SupportRead

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| sequence | required | integer | minimum: 0 |

#### Request template

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.

~~~sh
curl --request PUT --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/staff/support/conversations/RESOURCE_ID/read'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The acknowledged sequence. | SupportRead (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/staff/support/conversations/{id}/state

**Change Dashboard Staff Support State**

Staff-cookie only, with the dashboard's same-origin mutation guard. Takes over (pauses Luna), resolves (closes) or reopens (leaves Luna paused for staff follow-up) a conversation, exactly as the Slack buttons do. Each change adds one lifecycle notice to the customer's history and the Slack thread; a repeat that changes nothing adds none.

A conversation whose project is not active answers 409 resource_conflict.


Operation: changeDashboardStaffSupportState.

- Authentication: dashboardSession
- Request body: DashboardStaffSupportStateChange

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| action | required | string | Allowed: "takeover", "resolve", "reopen" |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/staff/support/conversations/RESOURCE_ID/state'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The conversation after the change. | DashboardStaffSupportConversation (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) |

### PUT /api/staff/projects/{id}/shadow

**Shadow Dashboard Staff Project**

Staff only: the session's person must carry the screenrig_staff flag, reread on every request; any other session answers 403 forbidden. Adds a live project to the reader's shadow list. Idempotent. An unknown or deleted project answers 404 not_found; a list already holding 200 projects answers 409 resource_conflict.


Operation: shadowDashboardStaffProject.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |

#### Request template

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.

~~~sh
curl --request PUT --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/staff/projects/RESOURCE_ID/shadow'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | The reader shadows the project. | 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) |

### DELETE /api/staff/projects/{id}/shadow

**Unshadow Dashboard Staff Project**

Staff only: the session's person must carry the screenrig_staff flag, reread on every request; any other session answers 403 forbidden. Removes a project from the reader's shadow list. Idempotent. A session shadowing it loses the project on its next request.


Operation: unshadowDashboardStaffProject.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |

#### Request template

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.

~~~sh
curl --request DELETE --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/staff/projects/RESOURCE_ID/shadow'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | The reader no longer shadows the project. | 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) |

### GET /api/me

**Get Dashboard Me**

Returns the signed-in person’s profile and credential counts.


Operation: getDashboardMe.

- Authentication: dashboardSession
- Request body: none

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/me'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Returns the signed-in person’s profile and credential counts.. | DashboardMe (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/me

**Delete Dashboard Me**

Tombstones the signed-in person. Proof-protected: the body carries a fresh sign-in proof verified against a reauthentication bound to this session with kind account_delete and subject_id equal to the signed-in person's user id (start it with POST /api/me/reauthentications). Without a current reauthentication the answer is reauthentication_required.

On success the response is 204, the dashboard session cookie is cleared, and the browser's HTTP cache for the dashboard origin is cleared as on sign-out.


Operation: deleteDashboardMe.

- Authentication: dashboardSession
- Request body: DashboardProofRequest

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| proof | required | DashboardProof |  |

#### Request template

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.

~~~sh
curl --request DELETE --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/me'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | Person deleted, dashboard session cookie cleared and the browser's HTTP cache for this origin cleared. | no body; headers: Clear-Site-Data |
| 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/me/reauthentications

**Start Dashboard Reauthentication**

Starts passkey step-up bound to the session, kind, and subject. A person without a passkey receives resource_conflict and uses a password proof.


Operation: startDashboardReauthentication.

- Authentication: dashboardSession
- Request body: DashboardReauthenticationRequest

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| kind | required | string | Allowed: "agent_connection_approval", "agent_connection_denial", "agent_disconnect", "email_change", "password_set", "passkey_add", "passkey_delete", "invitation_accept", "account_delete", "staff_grant", "staff_action", "oauth_authorization_approval", "oauth_authorization_denial", "service_client_change", "review_policy_change" |
| subject_id | optional | string | The subject the reauthentication binds to. kind account_delete requires the signed-in person's user id. kind staff_grant is staff only and requires the added person's email address. kind staff_action requires the specific prepared request_id. kinds oauth_authorization_approval and oauth_authorization_denial require the authorization id (oaz_...). kind service_client_change requires the service client id, or the project id when creating a client. kind review_policy_change requires the project id (turning Require review for agent changes off). minLength: 1 |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/me/reauthentications'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Starts passkey step-up bound to the session, kind, and subject. | DashboardReauthentication (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) |

### PUT /api/me/password

**Set Dashboard Password**

Sets or replaces the person’s password after fresh proof. The first password requires passkey proof; replacement accepts the current password or a passkey. Revokes other sessions and queues a sign-in changed notice.


Operation: setDashboardPassword.

- Authentication: dashboardSession
- Request body: DashboardPasswordSet

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| password | required | string | 12–128 UTF-8 bytes, without whitespace. Cannot equal the email or display name or a bundled common password. Stored only as an Argon2id hash. minLength: 12 maxLength: 128 pattern: "^\\S+$" |
| proof | required | DashboardProof |  |

#### Request template

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.

~~~sh
curl --request PUT --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/me/password'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | Sets or replaces the person’s password after fresh proof. | 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) |

### GET /api/me/passkeys

**List Dashboard Passkeys**

Lists the person’s passkeys.


Operation: listDashboardPasskeys.

- Authentication: dashboardSession
- Request body: none

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/me/passkeys'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Lists the person’s passkeys.. | DashboardPasskeyList (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/me/passkeys/options

**Start Dashboard Passkey Registration**

Verifies fresh proof and starts a registration bound to the session and person. Passkeys are disabled under development auth.


Operation: startDashboardPasskeyRegistration.

- Authentication: dashboardSession
- Request body: DashboardProofRequest

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| proof | required | DashboardProof |  |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/me/passkeys/options'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Verifies fresh proof and starts a registration bound to the session and person. | DashboardPasskeyRegistration (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/me/passkeys/complete

**Complete Dashboard Passkey Registration**

Stores the new passkey, revokes the person’s other sessions, and queues a sign-in changed notice.


Operation: completeDashboardPasskeyRegistration.

- Authentication: dashboardSession
- Request body: DashboardPasskeyComplete

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| ceremony_id | required | string | minLength: 1 |
| credential | required | object | WebAuthn credential response. |
| name | optional | string | default: "Passkey" minLength: 1 maxLength: 60 |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/me/passkeys/complete'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Stores the new passkey, revokes the person’s other sessions, and queues a sign-in changed notice.. | DashboardPasskey (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) |

### PATCH /api/me/passkeys/{id}

**Rename Dashboard Passkey**

Renames a passkey owned by the signed-in person.


Operation: renameDashboardPasskey.

- Authentication: dashboardSession
- Request body: DashboardPasskeyPatch

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| name | required | string | minLength: 1 maxLength: 60 |

#### Request template

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.

~~~sh
curl --request PATCH --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/me/passkeys/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Renames a passkey owned by the signed-in person.. | DashboardPasskey (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/me/passkeys/{id}

**Delete Dashboard Passkey**

Deletes a passkey after fresh proof and sends a sign-in changed notice. Removing the last sign-in method returns last_credential. A person whose account requires a passkey (every staff member) cannot remove their last passkey; that returns 409 resource_conflict.


Operation: deleteDashboardPasskey.

- Authentication: dashboardSession
- Request body: DashboardProofRequest

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| proof | required | DashboardProof |  |

#### Request template

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.

~~~sh
curl --request DELETE --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/me/passkeys/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | Deletes a passkey after fresh proof and sends a sign-in changed notice. | 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) |

### GET /api/me/email

**Get Dashboard Email**

Returns the person’s verified and pending email state.


Operation: getDashboardEmail.

- Authentication: dashboardSession
- Request body: none

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/me/email'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Returns the person’s verified and pending email state.. | DashboardEmail (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/me/email/change

**Change Dashboard Email**

Requests an email change after fresh proof. The verified address stays effective while verification is pending. An address held by another person returns user_email_conflict. The old address receives a warning, not an undo link.


Operation: changeDashboardEmail.

- Authentication: dashboardSession
- Request body: UserEmailChangeRequest

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| email | required | string | minLength: 3 maxLength: 254 format: "email" |
| proof | required | DashboardProof |  |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/me/email/change'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 202 | Requests an email change after fresh proof. | UserEmailChangeAccepted (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/advertising/inventory/{screen_id}

**Put Dashboard Advertising Inventory**


Operation: putDashboardAdvertisingInventory.

- Authentication: dashboardSession
- Request body: AdvertisingInventoryWrite

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| ScreenRig-Project | header | optional | Required with an identity bearer on every project route. Exactly one project identifier; no implicit current target. With a project bearer, must match its bound project. |
| screen_id | path | required |  |
| If-Match | header | optional | Optional positive resource revision. Omit to write the current resource; a supplied stale revision is revision_conflict. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| ads_enabled | required | boolean |  |
| site_name | optional | string | maxLength: 120 |
| city | optional | string | maxLength: 120 |
| region | optional | string | maxLength: 120 |
| venue_type | optional | string | maxLength: 64 |
| audience_tags | optional | array |  |
| placement | optional | string | maxLength: 120 |
| public_description | optional | string | maxLength: 500 |
| rate_override_mcr_per_15s | optional | inline | Null restores inheritance from the project default; it never means zero. |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/advertising/inventory/SCREEN_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Stored inventory. | AdvertisingInventory (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}/thumbnail

**Get Dashboard Media Thumbnail**

Returns a small lossy WebP of a ready image owned by the session's project, so the dashboard's grids never decode originals. There is one size: it fits inside 512 x 512 with the aspect kept and is never upscaled, and an animated image shows its first frame.

The first request generates it and stores it beside the original; later requests stream the stored copy, and deleting the media removes it with the original. Another project's identifier, video, audio, and media that is not ready return not_found. Range is not accepted.

The ETag is strong and names the original and the thumbnail generator, so it is the same on every request and host. The response is private, no-cache, and an If-None-Match that matches, checked after the media resolves in the session's project, returns 304 without reading the thumbnail. Completed bytes are recorded as dashboard_media egress.


Operation: getDashboardMediaThumbnail.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |
| If-None-Match | header | optional | Thumbnail ETag returned by this route. |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/media/RESOURCE_ID/thumbnail'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The WebP thumbnail. | string (image/webp); headers: Content-Length, Content-Disposition, ETag, Cache-Control, X-Content-Type-Options |
| 304 | The thumbnail ETag matches If-None-Match. No body or egress increment is produced. | no body; headers: ETag, Cache-Control |
| 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) |
| 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}/thumbnail

**Head Dashboard Media Thumbnail**

Applies the same ownership, conditional and generation rules as getDashboardMediaThumbnail and returns its headers without the body. No egress is recorded.


Operation: headDashboardMediaThumbnail.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |
| If-None-Match | header | optional | Thumbnail ETag returned by this route. |

#### Request template

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.

~~~sh
curl --request HEAD --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/media/RESOURCE_ID/thumbnail'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Thumbnail metadata with no body. | no body; headers: Content-Length, Content-Disposition, ETag, Cache-Control, X-Content-Type-Options |
| 304 | The thumbnail ETag matches If-None-Match. No egress increment is produced. | no body; headers: ETag, Cache-Control |
| 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) |
| 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/email/verify

**Peek Dashboard Email Verification**

Scanner-safe verification peek. It never verifies an address, mutates a user, or signs the visitor in.


Operation: peekDashboardEmailVerification.

- Authentication: none
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| token | query | required |  |

#### Request template

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.

~~~sh
curl --request GET 'https://screenrig.ai/api/email/verify?token=TOKEN'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Verification status. | EmailVerifyResult (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/email/verify

**Verify Dashboard Email**

Explicit human confirmation consumes one purpose-bound verification challenge. Link-signup challenges require the matching __Host-screenrig-signup cookie; email-change challenges are token-bound. Verification never signs the visitor in.


Operation: verifyDashboardEmail.

- Authentication: none OR invitationSignup
- Request body: EmailVerifyRequest

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| token | required | string | minLength: 1 |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/email/verify'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Verification status. | EmailVerifyResult (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/webauthn/assertions

**Start Dashboard Passkey Assertion**

Starts one usernameless WebAuthn assertion ceremony and returns the request options verbatim for navigator.credentials.get. No cookie is required, and no allowCredentials list is published, so the route reveals nothing about which users or credentials exist. The ceremony expires with DashboardCeremonyTTL of five minutes.

When the server runs dashboard development authentication, this route returns passkeys_disabled.


Operation: startDashboardPasskeyAssertion.

- Authentication: none
- Request body: none

#### Request template

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.

~~~sh
curl --request POST 'https://screenrig.ai/api/webauthn/assertions'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | WebAuthn request options bound to this ceremony. | DashboardPasskeyAssertion (application/json); headers: Cache-Control, RateLimit-Policy, RateLimit |
| 400 | 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) |
| 403 | 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) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, 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/webauthn/assertions/complete

**Complete Dashboard Passkey Assertion**

Verifies one usernameless assertion and mints the dashboard session cookie for the user the discoverable credential identifies. No cookie authenticates the request. A ceremony that is unknown, elapsed, or already used returns passkey_invalid, and so does an assertion the relying party cannot verify or a sign counter that moves backwards.

The credential identifier is not authorization on its own: the signed assertion is. When the server runs dashboard development authentication, this route returns passkeys_disabled.


Operation: completeDashboardPasskeyAssertion.

- Authentication: none
- Request body: DashboardPasskeyAssertionComplete

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| ceremony_id | required | string |  |
| credential | required | object | WebAuthn assertion response serialized from the PublicKeyCredential the authenticator returned. |

#### Request template

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.

~~~sh
curl --request POST --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/webauthn/assertions/complete'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Session cookie minted from a verified assertion. | DashboardSession (application/json); headers: Cache-Control, Set-Cookie |
| 400 | 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) |
| 401 | 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) |
| 403 | 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) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, 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/sessions

**Create Dashboard Session**

Signs in the person identified by verified email and password. Unknown email, no password, and wrong password return the same unauthorized response. Opens the last-used project if still a membership, otherwise the most recent membership, or no project. Development auth uses this same password flow.


Operation: createDashboardSession.

- Authentication: none
- Request body: DashboardPasswordLogin

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| email | required | string | The person’s verified email address. Unknown email, no password, and wrong password return the same unauthorized response. minLength: 3 maxLength: 254 format: "email" |
| password | required | string | minLength: 12 maxLength: 128 |

#### Request template

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.

~~~sh
curl --request POST --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/sessions'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Person session minted from verified email and password. | DashboardSession (application/json); headers: Cache-Control, Set-Cookie, RateLimit-Policy, RateLimit |
| 400 | 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) |
| 401 | 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) |
| 403 | 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) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, RateLimit-Policy, RateLimit |
| 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/session

**Get Dashboard Session**

Returns the current dashboard session. The session cookie and server-side session establish authority; user_id is descriptive. A 401 response means sign-in is required. The dev_auth field is reserved for local development and is false on the hosted service.


Operation: getDashboardSession.

- Authentication: dashboardSession
- Request body: none

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/session'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Current dashboard session. | DashboardSession (application/json); headers: Cache-Control |
| 401 | 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) |

### POST /api/logout

**Logout Dashboard Session**

Deletes the server-side session row and expires the session cookie. It does not delete the user and does not remove any credential. It is intrinsically idempotent and takes no Idempotency-Key.

A successful sign-out also clears the browser's HTTP cache for the dashboard origin, dropping the private, no-cache media, screenshot and ad review bytes the session viewed; a session that only expires leaves them until the browser evicts them.


Operation: logoutDashboardSession.

- Authentication: dashboardSession
- Request body: none

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/logout'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | Session row deleted, session cookie expired and the browser's HTTP cache for this origin cleared. | no body; headers: Cache-Control, Set-Cookie, Clear-Site-Data |
| 401 | 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/agents

**List Dashboard Agents**

Lists every pending, active, revoked, cancelled, and expired agent of the session project with safe installation metadata, last use, authenticated request count, and directly attributable metered credits. No bearer, recipient key bytes, envelope, or project identifier is returned.


Operation: listDashboardAgents.

- Authentication: dashboardSession
- Request body: none

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/agents'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Project agents. | AgentList (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) |

### GET /api/agents/{id}/events

**List Dashboard Agent Events**

This agent's history, newest first, as ONE record per trigger. It carries both what the agent asked for and what changed as a result. A request that changed something is its domain event, stamped with the command that caused it. A request that changed nothing — refused, failed, or an event-less success — is one agent.command event.

There is no separate commands endpoint; showing the same trigger on two panels was the defect this replaced. Lifecycle events for this agent are included. Worker, player, and dashboard-only activity is never inferred as agent activity. Two requests are two triggers. A retry after a refusal is a second record, never a rewrite of the first.

Reads are not recorded, because they change nothing and a polling agent would bury every real action. Anonymous enrollment and connection creation are excluded. Requests using temporary ScreenRig-Agent-Connect authority are also excluded, because that authority has not authenticated a project agent installation.

Every part of the command stamp is derived from the route this server matched or from the problem registry. There is no argument vector, no request body, no header, and no free text anywhere in a record, so nothing needs redacting and no caller can write chosen text into an operator-visible surface.

Resource identifiers appear because an identifier is never authorization and is already published on events. Paging keys on sequence, descending.

Pass the returned next_cursor as after to continue. next_cursor is empty when the page is short, which is the end of the history: a concurrent append only takes a higher sequence, and retention removes the lowest sequences first, so a cursor never duplicates or skips a surviving record. Offset paging is not supported.

Every search parameter is an exact-match or range predicate on a server-written token. A malformed cursor or filter value is refused with invalid_request. A limit outside 1-200 is clamped to the default rather than refused. Records expire on the project's own event retention window.


Operation: listDashboardAgentEvents.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |
| after | query | optional | Cursor from a previous page's next_cursor. |
| limit | query | optional |  |
| command | query | optional | Exact route-derived command name, such as screens.archive. |
| type | query | optional | Exact event type, such as agent.command or screen.archived. |
| resource_type | query | optional | Exact kind of object the request acted on. |
| resource_id | query | optional | Exact identifier of the object the request acted on. |
| outcome | query | optional | Present only on agent.command records; a domain event implies ok. |
| problem_code | query | optional | Exact stable problem code the server refused with. |
| since | query | optional |  |
| until | query | optional |  |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/agents/RESOURCE_ID/events'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Agent history. | EventList (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/agents/{id}/disconnect

**Disconnect Dashboard Agent**

Disconnects the agent from the selected project after fresh proof. The project must be one of the person’s memberships. Pending connections and credential issuances are cancelled; no replacement token is issued.


Operation: disconnectDashboardAgent.

- Authentication: dashboardSession
- Request body: DashboardAgentAction

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| project_id | required | string | minLength: 1 |
| proof | required | DashboardProof |  |
| capabilities | optional | array | Optional on agent connection and connected-app approval only. Omitted approves the full requested set; a non-empty subset of the connection's capabilities narrows the granted credential; anything else is invalid_request. |
| user_code | optional | string | Required when approving a device sign-in (flow device): the code the person confirms matches the terminal. A mismatch is agent_connection_code_mismatch; the fifth expires the sign-in (agent_connection_expired). Ignored otherwise. maxLength: 32 |
| access | optional | string | The access level a device sign-in or a connected app is approved with (Read only, Propose or Manage); omitted keeps the level asked for. Ignored for other connections. A person who cannot approve reviews may not approve Manage (403 approver_required). Allowed: "read", "propose", "manage" |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/agents/RESOURCE_ID/disconnect'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | Agent disconnected. | 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) |

### GET /api/agents/{id}/notes

**List Dashboard Agent Notes**

The notes this agent wrote in the session project, newest first, open and resolved. state is all (the default), open or resolved. Pass next_cursor back as after; it is empty on the last page. Note bodies are plain text written by an agent; render them as text, never as HTML or Markdown.


Operation: listDashboardAgentNotes.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |
| state | query | optional |  |
| after | query | optional | next_cursor of the previous page. |
| limit | query | optional |  |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/agents/RESOURCE_ID/notes'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The agent's notes. | NotePage (application/json); headers: Cache-Control |
| 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) |
| 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/agents/{id}/notes/{note_id}/resolve

**Resolve Dashboard Agent Note**

Resolves one of the agent's open notes as the signed-in person, with a required reason; resolution.by names the person. Appends note.resolved with the person as actor. A resolved note is note_not_open.


Operation: resolveDashboardAgentNote.

- Authentication: dashboardSession
- Request body: NoteResolve

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |
| note_id | path | required | The id of one of the agent's notes. |
| 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 |
| --- | --- | --- | --- |
| reason | required | string | minLength: 1 maxLength: 500 |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/agents/RESOURCE_ID/notes/NOTE_ID/resolve'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The resolved note. | Note (application/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) |
| 409 | 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) |
| 422 | 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) |

### DELETE /api/agents/{id}/notes/{note_id}

**Delete Dashboard Agent Note**

Deletes one of the agent's notes, open or resolved, as the signed-in person. Appends note.deleted with the person as actor.


Operation: deleteDashboardAgentNote.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |
| note_id | path | required | The id of one of the agent's notes. |
| Idempotency-Key | header | optional | Omit for a fresh operation. Supply and reuse a key to deduplicate retries; the CLI manages keys automatically. |

#### Request template

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.

~~~sh
curl --request DELETE --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/agents/RESOURCE_ID/notes/NOTE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | Deleted. | no body |
| 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) |
| 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/agent-connections/pending

**List Dashboard Agent Connections**

The pending connection requests addressed to the session project, which a member may review and approve: requests naming the project, and device sign-ins or connects by an agent identity already a member of it.

A new installation's sign-in is not listed (its project hint is client-supplied); a person reaches it by its user code (POST /api/agent-connections/lookup).


Operation: listDashboardAgentConnections.

- Authentication: dashboardSession
- Request body: none

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/agent-connections/pending'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The pending requests, newest first, at most 100. | object (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/agent-connections/lookup

**Lookup Dashboard Agent Connection**

Resolves the user code of a pending device sign-in (`screenrig login`), as typed or prefilled on the connect page, to its connection, whose approval view then shows it. Approves nothing. Separators, spaces and case are ignored. An unknown, used or invalid code is agent_connection_invalid; an expired one agent_connection_expired.

Limited to 5 lookups per dashboard session per 10 minutes, 20 per client address per hour and 1200 per hour fleet-wide (rate_limited).


Operation: lookupDashboardAgentConnection.

- Authentication: dashboardSession
- Request body: object

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| user_code | required | string | maxLength: 32 |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/agent-connections/lookup'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The connection the code names. | object (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/agent-connections/{id}

**Get Dashboard Agent Connection**

Returns safe connection review metadata. Review does not select a project; approval selects one of the person’s memberships.


Operation: getDashboardAgentConnection.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/agent-connections/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Safe connection metadata. | AgentConnection (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/agent-connections/{id}/approve

**Approve Dashboard Agent Connection**

Approves the connection for the selected project after fresh proof and creates a pending agent. The project must be one of the person’s memberships. Returns safe metadata, never the bearer.


Operation: approveDashboardAgentConnection.

- Authentication: dashboardSession
- Request body: DashboardAgentAction

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| project_id | required | string | minLength: 1 |
| proof | required | DashboardProof |  |
| capabilities | optional | array | Optional on agent connection and connected-app approval only. Omitted approves the full requested set; a non-empty subset of the connection's capabilities narrows the granted credential; anything else is invalid_request. |
| user_code | optional | string | Required when approving a device sign-in (flow device): the code the person confirms matches the terminal. A mismatch is agent_connection_code_mismatch; the fifth expires the sign-in (agent_connection_expired). Ignored otherwise. maxLength: 32 |
| access | optional | string | The access level a device sign-in or a connected app is approved with (Read only, Propose or Manage); omitted keeps the level asked for. Ignored for other connections. A person who cannot approve reviews may not approve Manage (403 approver_required). Allowed: "read", "propose", "manage" |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/agent-connections/RESOURCE_ID/approve'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Pending agent safe metadata. | Agent (application/json) |
| 409 | 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) |

### POST /api/agent-connections/{id}/deny

**Deny Dashboard Agent Connection**

Denies the connection for the selected project after fresh proof without creating an agent. The project must be one of the person’s memberships.


Operation: denyDashboardAgentConnection.

- Authentication: dashboardSession
- Request body: DashboardAgentAction

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| project_id | required | string | minLength: 1 |
| proof | required | DashboardProof |  |
| capabilities | optional | array | Optional on agent connection and connected-app approval only. Omitted approves the full requested set; a non-empty subset of the connection's capabilities narrows the granted credential; anything else is invalid_request. |
| user_code | optional | string | Required when approving a device sign-in (flow device): the code the person confirms matches the terminal. A mismatch is agent_connection_code_mismatch; the fifth expires the sign-in (agent_connection_expired). Ignored otherwise. maxLength: 32 |
| access | optional | string | The access level a device sign-in or a connected app is approved with (Read only, Propose or Manage); omitted keeps the level asked for. Ignored for other connections. A person who cannot approve reviews may not approve Manage (403 approver_required). Allowed: "read", "propose", "manage" |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/agent-connections/RESOURCE_ID/deny'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | Connection denied. | 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) |

### GET /api/summary

**Get Dashboard Summary**

Project network-status counters for the session's project. Screen online counts are derived from live paired runtime event streams, the same derivation Screen.online publishes; they are not a stored boolean and not a player heartbeat. Media bytes are the durable bytes of ready media only.


Operation: getDashboardSummary.

- Authentication: dashboardSession
- Request body: none

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/summary'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Project summary counters. | DashboardSummary (application/json); headers: Cache-Control |
| 401 | 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/billing/payout-status

**Get Dashboard Payout Status**

Truthful payout capability state. rails_available is false and unavailable_reason is financial_rails_unavailable while card, tax and cash-out rail are unavailable. No raw bank credentials are returned.


Operation: getDashboardPayoutStatus.

- Authentication: dashboardSession
- Request body: none

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/billing/payout-status'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Payout status. | BillingPayoutStatus (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/billing/receipts/{id}

**Get Dashboard Receipt**

One finalized receipt/tax document owned by the caller. No receipt exists while money collection is unshipped, so this is not_found rather than a fabricated document.


Operation: getDashboardReceipt.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/billing/receipts/RESOURCE_ID'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 404 | not_found - no receipt exists. | 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/billing/topups

**Create Dashboard Topup**

Answers billing_unavailable when payment and payout rails are not configured, rather than quoting a payment.


Operation: createDashboardTopup.

- Authentication: dashboardSession
- Request body: none

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/billing/topups'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 404 | billing_unavailable. | 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/billing/topups/{id}/checkout

**Checkout Dashboard Topup**

Answers billing_unavailable when payment rails are not configured; no payment attempt is created.


Operation: checkoutDashboardTopup.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/billing/topups/RESOURCE_ID/checkout'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 404 | billing_unavailable. | 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/billing/withdrawal-quotes

**Create Dashboard Withdrawal Quote**

Answers billing_unavailable when payout rails are not configured.


Operation: createDashboardWithdrawalQuote.

- Authentication: dashboardSession
- Request body: none

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/billing/withdrawal-quotes'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 404 | billing_unavailable. | 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/billing/withdrawals

**Create Dashboard Withdrawal**

Answers billing_unavailable when payout rails are not configured; the project bearer never authorizes a cash-out.


Operation: createDashboardWithdrawal.

- Authentication: dashboardSession
- Request body: none

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/billing/withdrawals'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 404 | billing_unavailable. | 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/billing/payout-onboarding

**Start Dashboard Payout Onboarding**

Answers billing_unavailable when payout rails are not configured; no hosted payout onboarding is created.


Operation: startDashboardPayoutOnboarding.

- Authentication: dashboardSession
- Request body: none

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/billing/payout-onboarding'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 404 | billing_unavailable. | 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) |

### PUT /api/media/uploads/{id}/content

**Put Dashboard Media Upload Content**

Same-origin object write for one cookie media upload. No signed URL is involved and the session cookie is the authority.


Operation: putDashboardMediaUploadContent.

- Authentication: dashboardSession
- Request body: string

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |

#### Request template

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.

~~~sh
curl --request PUT --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/octet-stream' --data-binary '@request.bin' 'https://screenrig.ai/api/media/uploads/RESOURCE_ID/content'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | Bytes stored. | no body |
| 404 | not_found - the upload is unknown. | 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/balance

**Get Dashboard Balance**

Returns the session project's public whole-credit balance. This cookie-authenticated read never debits, so it remains available at zero after an unpaid billable dashboard-stream event closes the SSE response. It is the dashboard recovery surface; the cookie-only dashboard never presents a project bearer to GET /api/project.


Operation: getDashboardBalance.

- Authentication: dashboardSession
- Request body: none

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/balance'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Public whole-credit balance. | DashboardBalance (application/json); headers: Cache-Control |
| 401 | 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) |

### POST /api/applications/{id}/preview-launches

**Create Dashboard Application Preview Launch**

Mints one single-use thirty-second launch ticket for the latest ready release of an application the session's project owns, on that release's own exact preview host, never the release host Players frame, so a Player and the dashboard in one browser never share that host's cached documents or grant cookie.

The ticket converts to the release-grant cookie on that host only, so the preview is cross-origin by construction and web application code can never read a dashboard cookie. Assign launch_url to iframe.src and to nothing else. Authority here is owner authority: the session's project owns the application.

That is a deliberate second authority source alongside exact-current-manifest authority for screenrig.webapp-package/v1, and it is scoped to this operation. Do not generalize it to media or to protected runtime content. There is no parent player, so the browser SDK and the key-value bridge fail closed and the preview is presentation-only.

An application with no ready release is resource_conflict.


Operation: createDashboardApplicationPreviewLaunch.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| id | path | required |  |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/applications/RESOURCE_ID/preview-launches'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Single-use 30-second exact-release-host preview launch. | ReleaseLaunch (application/json); headers: Cache-Control, Referrer-Policy, RateLimit-Policy, RateLimit |
| 401 | 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) |
| 409 | 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) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, 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) |

### GET /api/organizations/{org_id}/projects

**Dashboard Organizations Projects**

Organization admins read project names, owners, status and screen counts only; this grants no project access.


Operation: dashboardOrganizationsProjects.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| org_id | path | required |  |
| cursor | query | optional |  |
| limit | query | optional |  |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/organizations/ORG_ID/projects'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Organization result. | OrganizationProjectPage (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) |

### GET /api/organizations/{org_id}/discovery

**Dashboard Organizations Discovery**

Projects elsewhere whose owners have a verified email on the exact verified domain. Contact addresses and subdomains do not match, and admins cannot pull projects in.


Operation: dashboardOrganizationsDiscovery.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| org_id | path | required |  |
| cursor | query | optional |  |
| limit | query | optional |  |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/organizations/ORG_ID/discovery'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Organization result. | OrganizationProjectPage (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/organizations/{org_id}/domain-challenges

**Dashboard Organizations Start Domain**

Open a 7-day DNS TXT challenge for one domain, replacing that domain's pending challenge; the organization's other domains are unchanged. Put its exact value at its returned DNS name and keep it there. An organization holds at most 20 domains, verified and pending together (resource_conflict). A domain another organization holds is resource_conflict.

A domain this organization already holds may be proved again, which records the new value for the daily DNS check.


Operation: dashboardOrganizationsStartDomain.

- Authentication: dashboardSession
- Request body: OrganizationDomainInput

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| org_id | path | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| domain | required | string | Exact DNS domain; no wildcard, URL or IP address. minLength: 3 maxLength: 253 |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/organizations/ORG_ID/domain-challenges'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | Organization result. | OrganizationDomainChallenge (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/organizations/{org_id}/domain-verification

**Dashboard Organizations Verify Domain**

Verify one unexpired challenge against DNS and add its domain to the organization, beside the domains it already holds. Each exact domain belongs to one organization. A proof rotated during lookup is refused. Keep the TXT record in place; a daily check removes a domain whose record stays missing for seven days.

Once the claim commits, and in the background after this response, each project whose owner is a verified person at exactly the domain and which sits alone in that owner's personal organization is moved into this organization (renamed "<name> (<owner>)" on a name conflict).

Projects in any other organization, projects holding a customer-paid plan of their own, and projects their owner or staff moved out of a domain organization stay where they are; a project the organization's limits refuse is retried a day later. Placement grants no access.


Operation: dashboardOrganizationsVerifyDomain.

- Authentication: dashboardSession
- Request body: OrganizationDomainProof

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| org_id | path | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| challenge_id | required | string |  |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/organizations/ORG_ID/domain-verification'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Organization result. | VerifiedOrganizationDomain (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) |

### GET /api/organizations/{org_id}/admins

**Dashboard Organizations Admins**

A designated admin reads the organization's admins, the candidates who may become one (verified, active owners of its live projects who are not admins yet) and the organization revision. Anyone else is refused as on the other organization admin routes. People only; agents and bearer credentials have no admin route.


Operation: dashboardOrganizationsAdmins.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| org_id | path | required |  |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/organizations/ORG_ID/admins'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Organization admins. | OrganizationAdmins (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) |

### PUT /api/organizations/{org_id}/admins

**Dashboard Organizations Set Admins**

A designated admin replaces the organization's admins. If-Match carries the organization revision; a stale one is revision_conflict with current_revision. The set names 1 to 20 people, each a current admin or a candidate, otherwise invalid_request; an admin removes themselves only while another admin remains.

The change increments the revision, is recorded in the organization audit, grants no project membership, and returns the same shape as GET. People only; agents and bearer credentials have no admin route.


Operation: dashboardOrganizationsSetAdmins.

- Authentication: dashboardSession
- Request body: OrganizationAdminsUpdate

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| org_id | path | required |  |
| If-Match | header | required | The organization revision from GET, optionally quoted. |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| admin_user_ids | required | array |  |

#### Request template

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.

~~~sh
curl --request PUT --cookie cookies.txt --cookie-jar cookies.txt --header 'If-Match: "REVISION"' --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/organizations/ORG_ID/admins'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Organization admins after the change. | OrganizationAdmins (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) |

### GET /api/organizations/{org_id}/people

**Dashboard Organizations People**

A designated admin reads the organization's people across its live projects.

Read-only. projects lists the organization's live projects, ordered by name, at most 500. people is the union of those projects' active memberships (suspended memberships are left out) and the designated admins, ordered by display name then email; each person carries their role in each project (owner or member) and whether they are an admin, and an admin in no project appears with an empty projects list. email is empty for a person whose address is not verified.

Deactivated people and agents never appear. At most 500 people are returned, admins always among them; truncated is true when people, projects or memberships were left out. Anyone else is refused as on the other organization admin routes. People only; agents and bearer credentials have no route.


Operation: dashboardOrganizationsPeople.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| org_id | path | required |  |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/organizations/ORG_ID/people'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Organization people. | OrganizationPeople (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) |

### GET /api/organizations/{org_id}/admin-invitations

**Dashboard Organizations List Admin Invitations**

A designated admin lists the organization's admin invitations by id, 50 at a time, after expiring those whose time has passed. Recipient addresses are visible to the organization's admins, who supplied them. Tokens and credential URLs are never listed. People only; agents and bearer credentials have no admin route.


Operation: dashboardOrganizationsListAdminInvitations.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| org_id | path | required |  |
| status | query | optional |  |
| cursor | query | optional |  |

#### Request template

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.

~~~sh
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/organizations/ORG_ID/admin-invitations'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The organization's admin invitations. | OrganizationAdminInvitationList (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/organizations/{org_id}/admin-invitations

**Dashboard Organizations Create Admin Invitations**

A designated admin invites people by email to become admins. Accepting adds the person to the organization's admins and creates their login if they have none; it grants no project membership. At most 20 admins counting pending admin invitations (organization_admin_limit); an address whose person already administers the organization is invalid_request.

Email invitations expire after seven days; an outstanding invitation to the same address is reused. The organization and inviter names must be fit for email. People only; agents and bearer credentials have no admin route.


Operation: dashboardOrganizationsCreateAdminInvitations.

- Authentication: dashboardSession
- Request body: OrganizationAdminInvitationCreate

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| org_id | path | required |  |
| Idempotency-Key | header | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| emails | required | array |  |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Idempotency-Key: REQUEST_ID' --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/organizations/ORG_ID/admin-invitations'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 201 | The organization admin invitations, one per distinct address. | OrganizationAdminInvitationCreated (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) |
| 429 | Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures. | Problem (application/problem+json); headers: Retry-After, RateLimit-Policy, RateLimit |

### POST /api/organizations/{org_id}/admin-invitations/{invitation_id}/revoke

**Dashboard Organizations Revoke Admin Invitation**

A designated admin revokes an outstanding admin invitation and frees its admin slot. An already revoked invitation returns 204; an accepted one returns invitation_consumed; an id the organization does not hold is not_found. People only; agents and bearer credentials have no admin route.


Operation: dashboardOrganizationsRevokeAdminInvitation.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| org_id | path | required |  |
| invitation_id | path | required |  |

#### Request template

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.

~~~sh
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/organizations/ORG_ID/admin-invitations/INVITATION_ID/revoke'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 204 | The invitation is revoked. | 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) |

### DELETE /api/organizations/{org_id}/domain

**Dashboard Organizations Remove Domain**

Remove every verified domain and pending challenge the organization holds; future placement and domain discovery stop. DELETE /api/organizations/{org_id}/domains/{domain} removes one.


Operation: dashboardOrganizationsRemoveDomain.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| org_id | path | required |  |

#### Request template

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.

~~~sh
curl --request DELETE --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/organizations/ORG_ID/domain'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Organization result. | OrganizationDomainRemoved (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) |

### DELETE /api/organizations/{org_id}/domains/{domain}

**Dashboard Organizations Remove One Domain**

Remove one domain, verified or pending, and leave the organization's other domains. Future placement and discovery for it stop, and another organization may verify it. Projects already in the organization stay. A domain the organization neither holds nor is proving is not_found.


Operation: dashboardOrganizationsRemoveOneDomain.

- Authentication: dashboardSession
- Request body: none

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| org_id | path | required |  |
| domain | path | required |  |

#### Request template

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.

~~~sh
curl --request DELETE --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/organizations/ORG_ID/domains/DOMAIN'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | Organization result. | OrganizationDomainRemoved (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) |

### PUT /api/members/{user_id}/approver

**Set Dashboard Member Approver**

Sets whether a member can approve: decide reviews and grant agents Manage access. A member who cannot approve has their agents held to Propose. The project owner only (forbidden otherwise); the owner's own row is fixed (resource_conflict); a non-member is not_found. Appends project.member_approver_changed (details user_id, can_approve).


Operation: setDashboardMemberApprover.

- Authentication: dashboardSession
- Request body: MemberApproverUpdate

#### Parameters

| Name | Send in | Required? | Description |
| --- | --- | --- | --- |
| user_id | path | required |  |

#### Request body fields

| Field | Required? | Type | Description |
| --- | --- | --- | --- |
| can_approve | required | boolean |  |
| project_id | optional | string | The project the caller means; a session whose selection has changed is refused with resource_conflict. minLength: 1 maxLength: 200 |

#### Request template

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.

~~~sh
curl --request PUT --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/members/USER_ID/approver'
~~~

#### Responses

| Status | Meaning | Body and headers |
| --- | --- | --- |
| 200 | The member. | DashboardMember (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) |

## API lifecycle, deprecations, and rate limits

URL families: `/api`, `/screen`, `/stream`, `/media`, `/website`, `/agentid`.

Compatible changes within a major:

- Additive endpoints and methods.
- Optional request members.
- New problem codes.
- New fields only where the published schema permits unknown fields.

Changes that require a new major:

- Route or field removal or rename.
- Type, meaning, or authorization changes.
- Making optional input required.
- Adding a member to a closed enum.
- Adding a field to a closed response object.

A new major overlaps for at least 180 days. Sunset notice is at least 90 days, and a deprecated operation remains served for at least 90 days from the RFC 9745 Deprecation effective date unless a later published commitment controls.

No operations are currently deprecated.

Lifecycle signals:

- OpenAPI deprecated true on the operation.
- RFC 9745 Deprecation with its effective date.
- RFC 8594 Sunset once the sunset date is fixed.
- Link with rel=deprecation to migration documentation.

Operations with disclosed fixed-window controls declare `x-rate-limit-policies`. Eligible admitted responses may include optional `RateLimit-Policy` and `RateLimit` structured field list members. Exact replays and deployments without shared admission may omit them. Fixed-window denials include them; concurrency and byte-budget denials may omit them. These fields follow `draft-ietf-httpapi-ratelimit-headers-11`, an Internet-Draft rather than an RFC. On every `429`, `Retry-After` is authoritative.

Application upload validates and extracts already-built content and publishes immutable release identity. Media upload follows server-issued signed PUT details verbatim. Protected release, media, package, and K/V access derives from current manifest authority.

Authorized native Player sessions receive normalized immutable webapp packages through the manifest-and-release-bound protected route. Package authority binds the current screen, manifest revision, release identity, package hash, size, type, and device session; storage remains private and delivery remains server-mediated.

## RFC 9457 problem codes

- free_organization_limit — HTTP 409: Free organization limit reached (https://screenrig.ai/problems/free-organization-limit)
- organization_plan_required — HTTP 409: The organization needs a plan before it admits screens (https://screenrig.ai/problems/organization-plan-required)
- project_name_conflict — HTTP 409: Project name already used in the organization (https://screenrig.ai/problems/project-name-conflict)
- project_delete_blocked — HTTP 409: Project cannot be deleted in its current state (https://screenrig.ai/problems/project-delete-blocked)
- owner_required — HTTP 409: A responsible person is required (https://screenrig.ai/problems/owner-required)
- owner_cannot_leave — HTTP 409: The project owner cannot leave (https://screenrig.ai/problems/owner-cannot-leave)
- internal_error — HTTP 500: Internal server error (https://screenrig.ai/problems/internal-error)
- invalid_request — HTTP 400: Request is invalid (https://screenrig.ai/problems/invalid-request)
- unauthorized — HTTP 401: Authentication is required (https://screenrig.ai/problems/unauthorized)
- credential_retired — HTTP 401: Credential was retired (https://screenrig.ai/problems/credential-retired)
- client_update_required — HTTP 410: Update the screenRIG plugin or CLI (https://screenrig.ai/problems/client-update-required)
- forbidden — HTTP 403: Request is not allowed (https://screenrig.ai/problems/forbidden)
- insufficient_access — HTTP 403: This access level cannot do this (https://screenrig.ai/problems/insufficient-access)
- approver_required — HTTP 403: Only an approver can do this (https://screenrig.ai/problems/approver-required)
- review_not_pending — HTTP 409: The review is no longer pending (https://screenrig.ai/problems/review-not-pending)
- review_stale — HTTP 409: The playlist changed after this proposal (https://screenrig.ai/problems/review-stale)
- review_blocked — HTTP 409: Another review must be decided first (https://screenrig.ai/problems/review-blocked)
- playlist_not_approved — HTTP 409: The playlist has no approved version (https://screenrig.ai/problems/playlist-not-approved)
- not_found — HTTP 404: Resource was not found (https://screenrig.ai/problems/not-found)
- method_not_allowed — HTTP 405: Method is not allowed (https://screenrig.ai/problems/method-not-allowed)
- idempotency_mismatch — HTTP 409: Idempotency key does not match the original request (https://screenrig.ai/problems/idempotency-mismatch)
- credential_issuance_expired — HTTP 410: Credential issuance expired (https://screenrig.ai/problems/credential-issuance-expired)
- user_email_conflict — HTTP 409: Email address belongs to another person (https://screenrig.ai/problems/user-email-conflict)
- provisioning_invalid — HTTP 404: Browser provisioning invalid (https://screenrig.ai/problems/provisioning-invalid)
- provisioning_expired — HTTP 410: Browser provisioning expired (https://screenrig.ai/problems/provisioning-expired)
- provisioning_consumed — HTTP 409: Browser provisioning consumed (https://screenrig.ai/problems/provisioning-consumed)
- provisioning_exchange_mismatch — HTTP 409: Browser provisioning exchange mismatch (https://screenrig.ai/problems/provisioning-exchange-mismatch)
- browser_already_paired — HTTP 409: Browser already paired (https://screenrig.ai/problems/browser-already-paired)
- handoff_code_invalid — HTTP 404: Browser handoff code invalid (https://screenrig.ai/problems/handoff-code-invalid)
- handoff_code_expired — HTTP 410: Browser handoff code expired (https://screenrig.ai/problems/handoff-code-expired)
- handoff_session_rate_limited — HTTP 429: Browser handoff session rate limited (https://screenrig.ai/problems/handoff-session-rate-limited)
- handoff_session_conflict — HTTP 409: Browser handoff session conflict (https://screenrig.ai/problems/handoff-session-conflict)
- browser_link_not_claimed — HTTP 409: Browser link not claimed (https://screenrig.ai/problems/browser-link-not-claimed)
- browser_link_project_mismatch — HTTP 403: Browser link project mismatch (https://screenrig.ai/problems/browser-link-project-mismatch)
- origin_not_allowed — HTTP 403: Origin is not allowed (https://screenrig.ai/problems/origin-not-allowed)
- resource_conflict — HTTP 409: Resource state conflicts with the request (https://screenrig.ai/problems/resource-conflict)
- card_invalid — HTTP 400: Card placement is invalid (https://screenrig.ai/problems/card-invalid)
- card_data_invalid — HTTP 409: Card data document does not fit a card that reads it (https://screenrig.ai/problems/card-data-invalid)
- revision_conflict — HTTP 412: Resource revision does not match (https://screenrig.ai/problems/revision-conflict)
- invalid_range — HTTP 416: Requested byte range is not satisfiable (https://screenrig.ai/problems/invalid-range)
- quota_exceeded — HTTP 413: Project content quota is exceeded (https://screenrig.ai/problems/quota-exceeded)
- payment_required — HTTP 402: Prepaid credit is required (https://screenrig.ai/problems/payment-required)
- rate_limited — HTTP 429: Request rate is too high (https://screenrig.ai/problems/rate-limited)
- revocation_unavailable — HTTP 500: Credential revocation state is unavailable (https://screenrig.ai/problems/revocation-unavailable)
- dependency_unavailable — HTTP 500: Required dependency is unavailable (https://screenrig.ai/problems/dependency-unavailable)
- dependency_timeout — HTTP 500: Required dependency did not answer in time (https://screenrig.ai/problems/dependency-timeout)
- schema_incompatible — HTTP 500: Database schema is incompatible (https://screenrig.ai/problems/schema-incompatible)
- not_ready — HTTP 500: Service is not ready (https://screenrig.ai/problems/not-ready)
- server_draining — HTTP 421: Service is draining (https://screenrig.ai/problems/server-draining)
- manifest_degraded — HTTP 500: Runtime manifest projection is degraded (https://screenrig.ai/problems/manifest-degraded)
- screenshot_unavailable — HTTP 409: Screenshot is not available (https://screenrig.ai/problems/screenshot-unavailable)
- identity_invalid — HTTP 400: Player public key is invalid (https://screenrig.ai/problems/identity-invalid)
- identity_conflict — HTTP 409: Player public key does not match the enrollment (https://screenrig.ai/problems/identity-conflict)
- enrollment_bound — HTTP 409: Player public key is already bound to a project (https://screenrig.ai/problems/enrollment-bound)
- proof_invalid — HTTP 401: Player identity proof is invalid (https://screenrig.ai/problems/proof-invalid)
- screen_archived — HTTP 409: Screen is archived (https://screenrig.ai/problems/screen-archived)
- screen_archive_required — HTTP 409: Archive the screen instead of deleting or unbinding it (https://screenrig.ai/problems/screen-archive-required)
- passkey_invalid — HTTP 401: Passkey ceremony is invalid (https://screenrig.ai/problems/passkey-invalid)
- passkeys_disabled — HTTP 403: Passkeys are disabled on this server (https://screenrig.ai/problems/passkeys-disabled)
- application_in_use — HTTP 409: A live manifest still references an application release (https://screenrig.ai/problems/application-in-use)
- agent_connection_invalid — HTTP 404: Agent connection is invalid (https://screenrig.ai/problems/agent-connection-invalid)
- agent_connection_code_mismatch — HTTP 400: The code does not match this sign-in (https://screenrig.ai/problems/agent-connection-code-mismatch)
- agent_connection_expired — HTTP 410: Agent connection is expired (https://screenrig.ai/problems/agent-connection-expired)
- agent_connection_conflict — HTTP 409: Agent connection is already resolved (https://screenrig.ai/problems/agent-connection-conflict)
- agent_connection_not_approved — HTTP 409: Agent connection is not approved (https://screenrig.ai/problems/agent-connection-not-approved)
- agent_connection_cancelled — HTTP 410: Agent connection was cancelled (https://screenrig.ai/problems/agent-connection-cancelled)
- agent_limit_exceeded — HTTP 409: Agent limit is exceeded (https://screenrig.ai/problems/agent-limit-exceeded)
- agent_lockout_risk — HTTP 409: Disconnecting the last active agent risks lockout (https://screenrig.ai/problems/agent-lockout-risk)
- recovery_ambiguous — HTTP 409: Device identifier is attached to more than one screen (https://screenrig.ai/problems/recovery-ambiguous)
- recovery_expired — HTTP 410: Screen recovery offer has expired (https://screenrig.ai/problems/recovery-expired)
- recovery_not_offered — HTTP 404: No screen recovery is pending (https://screenrig.ai/problems/recovery-not-offered)
- invitation_limit_reached — HTTP 409: Project has the maximum outstanding invitations (https://screenrig.ai/problems/invitation-limit-reached)
- organization_admin_limit — HTTP 409: Organization admin limit reached (https://screenrig.ai/problems/organization-admin-limit)
- version_required — HTTP 409: Playlist requires a newer API version (https://screenrig.ai/problems/version-required)
- price_change_pending — HTTP 409: Campaign is paused until new rates are accepted (https://screenrig.ai/problems/price-change-pending)
- quote_stale — HTTP 409: Advertising quote is stale or expired (https://screenrig.ai/problems/quote-stale)
- insufficient_credits — HTTP 402: Eligible credits are insufficient (https://screenrig.ai/problems/insufficient-credits)
- self_deal — HTTP 409: A project cannot buy advertising in its own network (https://screenrig.ai/problems/self-deal)
- invitation_invalid — HTTP 404: Invitation is invalid (https://screenrig.ai/problems/invitation-invalid)
- invitation_expired — HTTP 410: Invitation is expired (https://screenrig.ai/problems/invitation-expired)
- invitation_consumed — HTTP 409: Invitation is already accepted (https://screenrig.ai/problems/invitation-consumed)
- invitation_email_mismatch — HTTP 403: Invitation belongs to a different email address (https://screenrig.ai/problems/invitation-email-mismatch)
- reauthentication_required — HTTP 401: Fresh sign-in proof is required (https://screenrig.ai/problems/reauthentication-required)
- member_limit_reached — HTTP 409: Project has the maximum members (https://screenrig.ai/problems/member-limit-reached)
- last_credential — HTTP 409: Last sign-in method cannot be removed (https://screenrig.ai/problems/last-credential)
- no_project — HTTP 409: No current project is selected (https://screenrig.ai/problems/no-project)
- billing_unavailable — HTTP 404: This billing action is not available (https://screenrig.ai/problems/billing-unavailable)
- feature_unavailable — HTTP 404: This feature is not available on this server (https://screenrig.ai/problems/feature-unavailable)
- user_binding_stale — HTTP 409: Claiming user email binding is stale (https://screenrig.ai/problems/user-binding-stale)
- session_expired — HTTP 401: Runtime session is expired or no longer current (https://screenrig.ai/problems/session-expired)
- credential_revoked — HTTP 401: Device credential no longer binds this device to a screen (https://screenrig.ai/problems/credential-revoked)
- proof_clock_skew — HTTP 401: Player identity proof is outside the accepted time window (https://screenrig.ai/problems/proof-clock-skew)
- key_retired — HTTP 401: Player key was replaced and its grace window has closed (https://screenrig.ai/problems/key-retired)
- assignment_not_found — HTTP 404: Screen has no playlist assignment (https://screenrig.ai/problems/assignment-not-found)
- upgrade_required — HTTP 403: Player version is below the supported minimum (https://screenrig.ai/problems/upgrade-required)
- reboot_unsupported — HTTP 409: Screen cannot reboot remotely (https://screenrig.ai/problems/reboot-unsupported)
- webhook_limit_reached — HTTP 409: Project has the maximum webhooks (https://screenrig.ai/problems/webhook-limit-reached)
- fleet_link_limit_reached — HTTP 409: Project has the maximum fleet links (https://screenrig.ai/problems/fleet-link-limit-reached)
- webhook_url_rejected — HTTP 400: Webhook URL must be HTTPS on a public Internet host (https://screenrig.ai/problems/webhook-url-rejected)
- note_contains_credential — HTTP 422: Note text contains a credential (https://screenrig.ai/problems/note-contains-credential)
- note_rejected — HTTP 422: Note text was rejected (https://screenrig.ai/problems/note-rejected)
- note_not_open — HTTP 409: Note is not open (https://screenrig.ai/problems/note-not-open)
- note_limit_reached — HTTP 409: Project has the maximum open notes (https://screenrig.ai/problems/note-limit-reached)
- trigger_limit_reached — HTTP 409: Project has the maximum triggers (https://screenrig.ai/problems/trigger-limit-reached)
- kv_webhook_limit_reached — HTTP 409: Application has the maximum key/value webhooks (https://screenrig.ai/problems/kv-webhook-limit-reached)
- support_conversation_closed — HTTP 409: Support conversation is closed (https://screenrig.ai/problems/support-conversation-closed)
- submission_too_short — HTTP 422: Submission is too short to act on (https://screenrig.ai/problems/submission-too-short)
- submission_unclear — HTTP 422: Submission does not describe a problem or request (https://screenrig.ai/problems/submission-unclear)
- submission_rejected — HTTP 422: Submission was not accepted (https://screenrig.ai/problems/submission-rejected)
- submission_screening_unavailable — HTTP 500: Submission cannot be screened right now (https://screenrig.ai/problems/submission-screening-unavailable)
- demo_slot_unavailable — HTTP 409: Demo time is no longer available (https://screenrig.ai/problems/demo-slot-unavailable)
- agentid_claim_invalid — HTTP 400: AgentID claim is invalid or expired (https://screenrig.ai/problems/agentid-claim-invalid)
- agentid_already_enrolled — HTTP 409: AgentID is already enrolled (https://screenrig.ai/problems/agentid-already-enrolled)

## Schema index

Generated names, required fields, and schema descriptions; use raw OpenAPI for complete JSON Schema.

- `AccessLevel` — required: none

  read (Read only) < propose < manage. Propose reads, contributes (media, notes, comments, support, feedback, screenshots, ad drafts) and turns playlist and screen changes into reviews (docs/reviews.md).

- `AccessLimitedBy` — required: none

  Why effective_access is lower than the credential's access. project - the project requires review for agent changes. person - the agent's responsible person is a member who cannot approve reviews.

- `AdslotPageWrite` — required: id, type, adslot_id

  Whole-page advertising slot. At most 16 adslot pages, and at least one ordinary page with no visibility, are server checks and are not expressed here.

- `AdvertisingCampaign` — required: id, revision, name, state, pause_reasons, price_change_pending, daily_cap_mcr, lifetime_cap_mcr, reserved_mcr, spent_mcr, flight_start, flight_end, networks, created_at, updated_at

- `AdvertisingCampaignDraft` — required: name, daily_cap_mcr, lifetime_cap_mcr, flight_start, flight_end, networks

- `AdvertisingCampaignList` — required: campaigns

- `AdvertisingCampaignNetwork` — required: seller_project_id, membership_id, admission_generation, screen_ids, slot_ids, creative_ids, network_cap_mcr, network_reserved_mcr, network_spent_mcr, accepted_pricing_generation, price_change_pending, barrier_generation, enabled

- `AdvertisingCampaignNetworkDraft` — required: seller_project_id, screen_ids, slot_ids, creative_ids

- `AdvertisingCreative` — required: id, revision, media_id, media_revision, sha256, kind, content_type, duration_ms, width, height, bytes, state, created_at

- `AdvertisingCreativeCreate` — required: media_id

- `AdvertisingCreativeList` — required: creatives

- `AdvertisingCreativeReview` — required: id, seller_project_id, creative_revision, state, created_at

- `AdvertisingDaypart` — required: start, end

- `AdvertisingDeliveryReport` — required: gross_mcr, fee_mcr, net_mcr, completed, interrupted, unbillable, rows, coverage

- `AdvertisingDeliveryRow` — required: screen_id, slot_id, reservation_id, gross_mcr, fee_mcr, net_mcr, completed_at

- `AdvertisingInventory` — required: screen_id, revision, ads_enabled, audience_tags, rate_override_mcr_per_15s, rate_revision, created_at, updated_at

- `AdvertisingInventoryList` — required: inventory

- `AdvertisingInventoryWrite` — required: ads_enabled

- `AdvertisingJoinedInventory` — required: inventory, slots

- `AdvertisingJoinedNetwork` — required: seller_project_id, membership_id, policy, admission_generation, scope, price_change_pending, default_rate_mcr_per_15s

- `AdvertisingJoinedNetworkList` — required: networks

- `AdvertisingMembership` — required: id, seller_project_id, admission_generation, policy_revision, policy, scope, state, revision, created_at, updated_at

- `AdvertisingMembershipList` — required: memberships

- `AdvertisingMembershipScope` — required: policy, screen_ids, slot_ids

- `AdvertisingNetwork` — required: id, revision, enabled, rate_revision, pricing_generation, seller_fee_bps, seller_fee_revision, created_at, updated_at

- `AdvertisingNetworkCreate` — required: name

- `AdvertisingQuote` — required: id, campaign_id, campaign_revision, revision, state, expires_at, items, pricing_generations, created_at

- `AdvertisingQuoteAccept` — required: quote_id

- `AdvertisingQuoteItem` — required: seller_project_id, screen_id, slot_id, creative_id, duration_ms, rate_mcr_per_15s, play_price_mcr, pricing_generation, fee_bps, fee_revision

- `AdvertisingRate` — required: rate_mcr_per_15s

- `AdvertisingReportCoverage` — required: mode, outcomes_complete

  Coverage of playback outcome counts. Monetary totals always come from immutable settlements. When outcomes_complete is false, counts are observed lower bounds and missing history is unavailable, not zero.

- `AdvertisingReview` — required: id, creative_id, creative_revision, state, created_at

- `AdvertisingReviewDetail` — required: review, creative

- `AdvertisingReviewList` — required: reviews

- `AdvertisingReviewRejection` — required: reason

- `AdvertisingScope` — required: screen_ids, slot_ids

- `AdvertisingSlot` — required: id, revision, name, enabled, accepted_media, max_image_duration_ms, max_video_duration_ms, muted, rate_override_mcr_per_15s, rate_revision, created_at, updated_at

- `AdvertisingSlotList` — required: slots

- `AdvertisingSlotWrite` — required: name, accepted_media, max_image_duration_ms, max_video_duration_ms

- `AdvertisingSpendReport` — required: campaign_id, spent_mcr, reserved_mcr, completed, unbillable, pending, coverage

- `Agent` — required: id, name, agent_type, capabilities, state, authenticated_requests, metered_credits, created_at

- `AgentCapability` — required: none

  One agent-credential capability area. Capabilities are fixed when the credential is minted; to change them, connect a new agent and disconnect the old one. Canonical order is this enum order; capability lists never contain duplicates.

- `AgentConnection` — required: connection_id, name, agent_type, status, capabilities, expires_at, created_at

- `AgentConnectionRequest` — required: recipient_public_key

- `AgentConnectionStart` — required: connection_id, connection_token, approval_url, expires_at

- `AgentCredentialCollection` — required: agent, credential_envelope, issuance_expires_at

- `AgentCredentialEnvelope` — required: algorithm, ephemeral_public_key, nonce, ciphertext

- `AgentDisconnectRequest` — required: none

- `AgentIdentityCredential` — required: agent_id, identity_token, issuance_expires_at

- `AgentInput` — required: none

- `AgentList` — required: items

- `AgentSelfStatus` — required: agent, connection_ready

- `Application` — required: id, name, revision, created_at, updated_at

- `ApplicationEventContext` — required: primitive_id, code

- `ApplicationEventReport` — required: severity, code, context

- `ApplicationList` — required: items

- `ApplicationRename` — required: name

- `ArchivedProjectDevice` — required: screen_id, label, host

- `BillingBalance` — required: wallet_revision, remaining_mcr, reserved_mcr, available_mcr, sources, withdrawal

- `BillingPayer` — required: kind, id

- `BillingPayoutStatus` — required: rails_available, unavailable_reason

- `BillingSourceBucket` — required: remaining_mcr, reserved_mcr

- `BillingSources` — required: nonredeemable, purchased, ad_earned

- `BillingStatement` — required: entries, next_cursor

- `BillingStatementEntry` — required: sequence, journal_id, kind, amount_mcr, amount_credits, amount_usd, occurred_at

- `BillingUsageAllowance` — required: bandwidth_bytes, storage_mcr, api_events, storage_mcr_per_gb_month

  The monthly allowance one payer's pool waives, as canonical decimal strings. Unused allowance does not roll over.

- `BillingUsageAmounts` — required: bandwidth_bytes, waived_bandwidth_bytes, storage_mcr, waived_storage_mcr, api_events

- `BillingWithdrawal` — required: eligible_earned_mcr, amount_usd_cents, threshold_exclusive_usd_cents, allowed, blockers, rails_available

- `BrowserLinkClaim` — required: session_id, status, screen

- `BrowserLinkClaimRequest` — required: code

- `BrowserLinkClaimScreen` — required: id, public_id, state, public_url

- `BrowserLinkEvent` — required: session_id, status, event_id, expires_at

- `BrowserLinkSession` — required: session_id, code, display_code, status, expires_at

- `BrowserLinkStatus` — required: session_id, code, display_code, status, continuation_path, expires_at

- `BrowserProvisioningCompletion` — required: screen, public_url

- `BrowserProvisioningExchange` — required: provisioning_token, exchange_id

- `CLIEnrollment` — required: project, agent, connection_ready, token, issuance_id, issuance_expires_at, invitation

- `CLIEnrollmentOAuth` — required: project, agent, connection_ready, issuance_id, issuance_expires_at, invitation, access_token, token_type, expires_in, refresh_token, refresh_expires_at, scope

  Enrollment in the oauth credential format. The enrollment's grant (client screenrig-cli, Manage on the new project with every capability, identity access Manage) issued its first token pair; an exact retry before issuance_expires_at returns the same pair, later retries return credential_issuance_expired.

- `CLIEnrollmentRequest` — required: client_id

- `CanvasBackground` — required: none

  Solid canonical uppercase #RRGGBBAA, or a top-to-bottom linear gradient.

- `CanvasColor` — required: none

  Canonical uppercase sRGB RRGGBBAA.

- `Capabilities` — required: api_version, protocol_version, application_compressed_bytes, application_expanded_bytes, application_package_bytes, application_file_count, application_file_bytes, application_path_depth, application_path_bytes, media_image_bytes, media_audio_bytes, playlist_max_pages, playlist_max_items_per_page, playlist_max_media_per_selector, transition_max_duration_ms, screens_per_project, project_content_bytes, features

- `CardDataEntry` — required: key, document, bytes, sha256, revision, created_at, updated_at

- `CardDataList` — required: items, next_cursor

- `CardDataSummary` — required: key, bytes, sha256, revision, created_at, updated_at

- `CardDataWrite` — required: document

- `Comments` — required: comments

- `CommentsWrite` — required: comments

- `CreatedProject` — required: project, organization

- `DashboardAgentAction` — required: project_id, proof

- `DashboardApplicationSummary` — required: count

- `DashboardBalance` — required: credit_remaining, credit_included, credit_reset

- `DashboardDisplayName` — required: none

  Trimmed printable display name, at most 80 UTF-8 bytes. Rejects controls, URL-looking text, @, www., ://, and credential material. Never authorization.

- `DashboardEmail` — required: email, email_verified, email_revision

  The current user's own email view. The verified address stays effective while a change is pending, and the pending candidate is visible only to its owner.

- `DashboardHealthSummary` — required: display_disconnected, hot, crashing, stale

  Active screens with a health report, counted by condition; a screen can count in several. display_disconnected, hot, and crashing count only online screens whose latest report is current (not stale); stale counts online screens whose report is older than 35 minutes and nothing else.

- `DashboardMe` — required: user_id, display_name, email, password, passkeys, created_at

- `DashboardMediaSummary` — required: count, bytes

  Ready media counters for the session's project. bytes counts stored media bytes only; it is not the project used_bytes total, which also covers release trees and key/value entries.

- `DashboardMember` — required: user_id, display_name, joined_at, passkeys, password, owner

- `DashboardMemberList` — required: items, viewer_user_id, viewer_is_owner

- `DashboardPasskey` — required: id, name, created_at, last_used_at

- `DashboardPasskeyAssertion` — required: ceremony_id, request_options

- `DashboardPasskeyAssertionComplete` — required: ceremony_id, credential

- `DashboardPasskeyComplete` — required: ceremony_id, credential

- `DashboardPasskeyList` — required: items

- `DashboardPasskeyPatch` — required: name

- `DashboardPasskeyProof` — required: type, ceremony_id, credential

- `DashboardPasskeyRegistration` — required: ceremony_id, creation_options

- `DashboardPasswordLogin` — required: email, password

- `DashboardPasswordProof` — required: type, password

- `DashboardPasswordSet` — required: password, proof

- `DashboardPendingEmail` — required: email, expires_at

- `DashboardProject` — required: status, created_at, email, email_verified, screen_count, screen_limit, used_bytes, reserved_bytes, content_limit_bytes, event_retention_days, name

  Status, contact address, enforced limits, usage, retention, and public whole-credit state for one project. The internal plan identifier remains unpublished.

- `DashboardProjectList` — required: items

- `DashboardProjectListItem` — required: id, name, last_used_at

- `DashboardProjectReference` — required: id, name

- `DashboardProjectSwitch` — required: project_id

- `DashboardProof` — required: none

- `DashboardProofRequest` — required: proof

- `DashboardReauthentication` — required: ceremony_id, options

- `DashboardReauthenticationRequest` — required: kind

- `DashboardScreenSummary` — required: total, online, pairing_pending, active, archived

  Screen counters for the session's project. total is pairing_pending plus active and excludes archived, matching project screen_count. online is derived from live paired runtime event streams exactly as Screen.online is; it is not a stored boolean and not a player heartbeat.

- `DashboardSession` — required: user_id, display_name, email, project, auth_method, authenticated_at, expires_at, dev_auth, staff, shadow, passkey_enrollment_required

  Person session. Identifiers are descriptive; the session cookie establishes authority. A person with no projects has project: null.

- `DashboardStaffActionRequest` — required: request_id, change, state, created_at, expires_at

- `DashboardStaffActivity` — required: id, project_id, sequence, at, type, severity, outcome, actor_kind

- `DashboardStaffActivityPage` — required: items, next_cursor, since, until, retention_seconds

- `DashboardStaffAudit` — required: audit_id, request_id, user_id, change, created_at

- `DashboardStaffAuditPage` — required: items, next_cursor

- `DashboardStaffChange` — required: action

  Only fields required by the selected action are accepted. project.write-mode requires a reason and the current session project; it grants ten minutes of staff write access, ending on a project switch, logout, staff revoke or unshadow. Every admitted project write has a permanent audit. project.write, project.write-mode-end, project.shadow, project.unshadow, project.member and project.member-remove are audit-only actions and cannot be proposed; project.member records a membership granted, and project.member-remove one ended, by the screenrig-admin org apply command, whose rows carry its reason. staff.grant and staff.revoke appoint an existing verified person by ID. person.free-cap sets how many free Standard organizations the person may open (free_limit; none restores one) and changes no organization they hold; organization.domain with no domain removes the claim; a domain set pulls in, after the approved change commits, the projects its verified people hold alone in their personal organizations, as domain verification does. project.owner explicitly appoints the person and adds their project membership. Admin and owner targets must be existing verified people. organization.plan with no plan_id schedules removal; project.plan-to-organization with no plan_id moves the existing plan; the organization's other projects holding plans of their own roll up into it, each paying for itself until the next UTC month boundary or the organization plan's start, whichever is later. An organization holds premium, enterprise, promo or the staff-assigned staff plan; every organization without one pays on Standard, so organization.plan standard removes the plan as no plan_id does, and project.plan-to-organization refuses standard. Organization billing must be enabled for both; the result names the actual effective time and grant. project.plan takes standard, which ends a plan the project still holds of its own at the next UTC month boundary and refuses occupancy above Standard's limits. Project-held Enterprise is accepted only before organization billing is enabled, through the retained provisioning path. person.reset-sign-in emails a one-hour sign-in reset link to the person's verified email address only; the link is never returned to staff, and the result names only the delivery and expiry. Demo actions bind the exact demo_version and demo_revision. Attendance, staff-reported outcomes and follow-up decisions are separate; completed booking never implies attendance or conversion. Offer-live and booking-link actions queue existing durable notification jobs only after approval. With revoke_passkeys it also removes every passkey of the person, ends every dashboard session and cuts off every token already issued to them, in the same approved action.

- `DashboardStaffDeletionPreview` — required: none

- `DashboardStaffDemo` — required: id, email, organization, country, screen_range, budget, timezone, source, route, review_requested, confidence, created_at, updated_at, booking, tracking, notifications_enabled

- `DashboardStaffDemoBooking` — required: state

- `DashboardStaffDemoPage` — required: items, next_cursor

- `DashboardStaffDemoTracking` — required: revision, attendance, outcome, followup

- `DashboardStaffFeedback` — required: id, project_id, kind, title, body, created_at

  Staff-only feedback with plain text fields. Escape for the display context, including historical submissions.

- `DashboardStaffFeedbackList` — required: items, next_cursor

- `DashboardStaffGrantRequest` — required: email, proof

- `DashboardStaffJourney` — required: id, name, created_at, stages, progress, stalled

- `DashboardStaffJourneyCount` — required: key, label, observed, ordered, unknown, not_observed, waiting, covered_gap

- `DashboardStaffJourneyStage` — required: key, state, source, timing

- `DashboardStaffJourneyTransition` — required: from, to, samples

- `DashboardStaffJourneys` — required: as_of, since, until, event_history_since, playback_history_since, total, scanned, truncated, stalled, counts, transitions, items

- `DashboardStaffMember` — required: user_id, display_name, email, created_at

  A person carrying the screenrig_staff flag.

- `DashboardStaffMemberList` — required: items

- `DashboardStaffOperationsAds` — required: requests_per_second, bits_per_second

- `DashboardStaffOperationsClientRow` — required: client, requests, errors, bytes, client_kind, bot_name

- `DashboardStaffOperationsClients` — required: window_seconds, rows

- `DashboardStaffOperationsDownloader` — required: project_id, project_name, bytes, requests

- `DashboardStaffOperationsDownloaders` — required: window, projects

- `DashboardStaffOperationsEdgeErrorRow` — required: status, route, lane, term, requests, last_seen

- `DashboardStaffOperationsEdgeErrors` — required: window_seconds, rows

- `DashboardStaffOperationsIngestLag` — required: window_seconds, p50_ms, p95_ms

- `DashboardStaffOperationsInsights` — required: generated_at, top_downloaders, server_errors, client_errors, service_errors, latency, top_routes, top_clients, trend, streams_open, ingest_lag

  Each section is null when its read failed or its source is missing. Every list is sorted by its count, descending, except trend, which is by time.

- `DashboardStaffOperationsLane` — required: lane, requests_per_second, bits_per_second, ingress_bits_per_second, client_errors_per_second, server_errors_per_second, mean_duration_ms

- `DashboardStaffOperationsLatency` — required: window_seconds, rows

- `DashboardStaffOperationsLatencyRow` — required: lane, requests, p50_ms, p95_ms, p99_ms

- `DashboardStaffOperationsLive` — required: generated_at, through, bucket_seconds, window_seconds, total, lanes, ads, statuses, traffic, cache, limited, nodes, series, screens_online

  Rates are per second over window_seconds of complete buckets ending at through. Every list is sorted by its rate, descending.

- `DashboardStaffOperationsNamedRate` — required: name, per_second

- `DashboardStaffOperationsNode` — required: node, requests_per_second, bits_per_second, reporting, last_flush

- `DashboardStaffOperationsRates` — required: requests_per_second, bits_per_second, ingress_bits_per_second, client_errors_per_second, server_errors_per_second, mean_duration_ms

- `DashboardStaffOperationsRouteRow` — required: route, lane, requests, bytes, server_errors, p95_ms

- `DashboardStaffOperationsRoutes` — required: window_seconds, rows

- `DashboardStaffOperationsSeriesPoint` — required: at, requests, bytes, client_errors, server_errors, nodes

- `DashboardStaffOperationsServiceErrorRow` — required: service, severity, message, exception_type, count, hosts, last_seen

- `DashboardStaffOperationsServiceErrors` — required: window_seconds, rows

- `DashboardStaffOperationsStatusRate` — required: status, per_second

- `DashboardStaffOperationsTrend` — required: window_seconds, bucket_seconds, rows

- `DashboardStaffOperationsTrendRow` — required: at, lane, requests, bytes, server_errors, p95_ms

- `DashboardStaffOrganization` — required: none

- `DashboardStaffOrganizationLifecyclePreview` — required: organization, project_ids, can_close

- `DashboardStaffOrganizationPage` — required: items, next_cursor

- `DashboardStaffPlan` — required: none

  The project's plan identifier, visible to staff only.

- `DashboardStaffPlayer` — required: legacy, seen_at

- `DashboardStaffProject` — required: id, name, status, plan, email, screen_count, screen_limit, used_bytes, content_limit_bytes, credit_remaining, created_at, members, shadowed

- `DashboardStaffProjectDetail` — required: id, name, status, plan, email, email_verified, screen_count, screen_limit, used_bytes, reserved_bytes, content_limit_bytes, credit_remaining, created_at, members, screens, media, applications, agents, shadowed

- `DashboardStaffProjectList` — required: items

- `DashboardStaffProjectMember` — required: user_id, display_name, email, joined_at, last_used_at

- `DashboardStaffProjectOperation` — required: route, state

  Recorded before staff domain work starts. A missing completion means its outcome is unknown. Contains the registered route pattern, never a raw URL, query, body, proof or credentials. Admitted work can finish after lease expiry or revocation.

- `DashboardStaffReport` — required: report, columns, metadata, rows

- `DashboardStaffReportCatalog` — required: datasets, environment, max_rows, max_window, active_window

- `DashboardStaffReportDataset` — required: name, fields, sums, defaults

- `DashboardStaffReportRequest` — required: words

- `DashboardStaffReportResponse` — required: reports

- `DashboardStaffScreen` — required: id, label, state, project_id, project_name, online, created_at

- `DashboardStaffScreenPage` — required: items, next_cursor

- `DashboardStaffShadowList` — required: items

- `DashboardStaffSignInReset` — required: user_id, delivery, expires_at, passkeys_removed, sessions_revoked

  The result of an executed person.reset-sign-in. It never carries the reset link.

- `DashboardStaffSupportConversation` — required: id, project_id, project_name, subject, created_at, updated_at, last_sequence, human_requested, closed, message_count, customer_names, awaiting_reply, unread

  A support conversation as the staff inbox shows it.

- `DashboardStaffSupportCounts` — required: open, waiting, closed, unread

  Conversations in the inbox's project, organization and q scope, whatever the folder.

- `DashboardStaffSupportInbox` — required: items, next, counts

- `DashboardStaffSupportLastMessage` — required: role, author, preview, created_at

- `DashboardStaffSupportMessage` — required: id, conversation_id, sequence, role, author, body, created_at

  A support message with who wrote it and where, for staff.

- `DashboardStaffSupportMessageList` — required: items

- `DashboardStaffSupportReply` — required: body

- `DashboardStaffSupportReplyResult` — required: conversation, message

- `DashboardStaffSupportStateChange` — required: action

- `DashboardStaffUser` — required: user_id, display_name, email, email_verified, staff, password, passkeys, created_at, projects

- `DashboardStaffUserDetail` — required: user_id, display_name, email, email_verified, staff, password, passkeys, created_at, projects, active_sessions

- `DashboardStaffUserList` — required: items

- `DashboardStaffUserProject` — required: id, name, status, screen_count, joined_at, last_used_at

  A live project the person belongs to.

- `DashboardStaffWriteMode` — required: request_id, project_id, expires_at

- `DashboardSummary` — required: screens, media, applications

- `DeletedProject` — required: id, screens, paired_screens_active, paired_screens_archived, paired_screens_completed_pending, agents, media, applications, playlists, memberships

- `DemoRequestInput` — required: email, country, screen_range, organization, budget, timezone, source, flow_id

- `DemoResult` — required: route, booking, review_requested, video_url, email, country, timezone, flow_id, source

- `DemoSlots` — required: slots, timezone

- `DemoSource` — required: none

- `DeviceSessionRequest` — required: none

  Cookie-paired browser mint. The body is optional. Unknown members are ignored (docs/player-compatibility.md §1.1).

- `DisplayWindow` — required: days

- `EmailVerifyRequest` — required: token

- `EmailVerifyResult` — required: purpose, status

- `EnrollmentInvitation` — required: id, status, expires_at

- `Event` — required: cursor, type, severity, message, at

- `EventActor` — required: user_id, display_name

  Dashboard user that caused this mutation. Optional and absent on every event an agent, the CLI, a player, or a worker produced, so the field distinguishes a dashboard mutation rather than labelling all traffic. It is descriptive attribution only and is never authorization. display_name is the name the user chose at registration and is not verified.

- `EventAgent` — required: agent_id, name, agent_type

  Safe agent principal attribution present only when the event was directly caused by an authenticated agent bearer. It is never authorization.

- `EventCommand` — required: name, method

  The agent request that caused this event. Optional and absent on every event a dashboard user, a player, or a worker produced. It is descriptive attribution only and is never authorization.
  Every field is written by the server from the route it matched or from the problem registry. A command object cannot carry an argument vector, a request body, a header, or any text the caller chose, and there is deliberately no field for one.
  name and method appear on every event an agent request committed. The outcome fields appear ONLY on an agent.command event, which is the record for a request that committed no domain event: a committed domain event is itself the proof that the request succeeded.

- `EventList` — required: items, next_cursor

- `FeedbackContext` — required: none

  Closed diagnostic envelope. Every member is an optional constrained scalar, nesting is structurally impossible, and an unknown member is rejected with invalid_request. The shape exists so a client cannot persist argument values, credentials, or free-form environment data through it.

- `FeedbackList` — required: items

- `FeedbackSubmission` — required: id, kind, title, body, created_at

  One immutable project-scoped submission. It has no revision because it never changes after it is written.

- `FeedbackWrite` — required: title, body

- `FleetLink` — required: id, label, enabled, tags, screens_created, revision, created_at, updated_at

  A Player URL that turns every device opening it into its own screen in this project.

- `FleetLinkList` — required: items

- `FleetLinkPatch` — required: none

- `FleetLinkWrite` — required: label

- `HLSStreamSource` — required: protocol, url

- `HealthResponse` — required: status

- `HealthTrend` — required: items, next_cursor

- `HealthTrendHour` — required: hour, screens_reporting, hot, display_disconnected, crashing

- `HostContext` — required: platform

  The shell and hardware a player runs on, as the player reported it. A HINT that names a device; never a credential, never authorization. Only the Ed25519 key authorizes. Stored per screen, replaced by every session mint that carries one, deleted with the screen, never written to operation logs, never echoed in problem responses, and never returned to any project other than the owner's. Read-only on the project API; ScreenPatch cannot write it.

- `HostDevice` — required: none

  Optional hardware identifiers. duid and serial are the only two the recovery hint consults; a failed read on the player omits the field.

- `Invitation` — required: id, kind, delivery, status, project_id, created_at, expires_at

- `InvitationAcceptRequest` — required: token, mode

  New credentials are required for create_login and reset. Link create_login requires the verified signup cookie and uses its display name and email. Email create_login requires display_name. signed_in needs sign-in within ten minutes or an invitation_accept proof bound to the invitation id. buyer applies only to ad_buyer; new buyer projects start at zero credit.

- `InvitationAccepted` — required: kind, project_id, next

- `InvitationAdvertising` — required: screen_ids, slot_ids, policy

  Seller-owned scope: at least one screen or slot. Screens only, slots only, or both are accepted.

- `InvitationBuyer` — required: none

- `InvitationCreate` — required: kind

  Email delivery requires emails. Link delivery is project_member only and omits emails and advertising. ad_buyer requires advertising. At most 50 outstanding member and buyer invitations per project; an outstanding email invitation of the same kind and address is reused.

- `InvitationCreated` — required: invitations

- `InvitationCredential` — required: none

- `InvitationEmailVerificationAccepted` — required: status, expires_at

- `InvitationEmailVerificationRequest` — required: token, display_name, email

- `InvitationInspect` — required: id, kind, delivery, project, expires_at, person, modes, methods

  Non-consuming inspection. person is null for links. Never reports signup verification state. Email modes are create_login or signed_in; links offer both, and resets offer reset.

- `InvitationInspectRequest` — required: token

- `InvitationIssued` — required: id, kind, delivery, status, project_id, created_at, expires_at

- `InvitationKind` — required: none

- `InvitationList` — required: items, next_cursor

- `InvitationPasskeyOptionsRequest` — required: token, mode

- `InvitationPasswordCredential` — required: type, password

- `InvitationSignup` — required: email, display_name, verified, expires_at

- `InvitationStatus` — required: none

- `KVEntry` — required: application_id, key, value_base64, content_type, bytes, sha256, revision

- `KVList` — required: items, next_cursor

- `KVSummary` — required: application_id, key, content_type, bytes, sha256, revision

- `KVWebhook` — required: id, application_id, key, description, url, created_at

  An incoming key/value webhook. Its secret is returned only by create and rotate-secret.

- `KVWebhookCreate` — required: key

- `KVWebhookList` — required: items

- `KVWebhookWithSecret` — required: none

- `KVWebhookWriteResult` — required: key, revision, bytes, sha256, content_type, updated_at

- `KVWrite` — required: value_base64

- `LinearGradientBackground` — required: type, stops

  Top-to-bottom linear gradient. There is no angle field. stops has 2 through 8 entries, strictly increasing at in [0, 1], first at=0, last at=1.

- `LinearGradientStop` — required: at, color

  One stop on a top-to-bottom linear canvas background. at=0 is the top edge, at=1 is the bottom edge.

- `ManifestActivatedContext` — required: manifest_revision

- `ManifestActivatedReport` — required: severity, code, context

- `ManifestUpgradeContext` — required: manifest_revision, state

- `ManifestUpgradePlaylist` — required: id, name, revision

  The playlist identity behind an issued or queued desired revision: desired_playlist uses its exact desired grant or the read-only shared content goal for the current effective assignment before grant issuance. active_playlist always uses the exact acknowledged historical grant, never a newer assignment or content edit. id plus revision are the playlist version an operator reads as Target v42 / Playing v41; name is the playlist's display name, null when the playlist no longer exists or cannot be resolved. A missing historical active grant is null, never a fabricated revision. A queued desired goal grants no content access.

- `ManifestUpgradeReport` — required: severity, code, context

  One latest-wins manifest-upgrade lifecycle snapshot for the session screen. The server stores one slot per screen and derives the project read model Screen.manifest_upgrade from it; a stale, superseded, or post-activation pre-activation report is accepted with no change (202), never refused as a conflict, and no report ever promotes a grant. severity is envelope-only: the derived project event severity comes from state.

- `McrString` — required: none

  Exact integer millicredit amount as a canonical decimal string. 1 credit is 1000 mcr.

- `Media` — required: id, filename, primitive, content_type, operation_id, sha256, bytes, revision, state, created_at, updated_at

- `MediaCommit` — required: content_type, bytes, sha256

  Declares only what the server can re-verify against the stored object byte for byte. Codec identity is not declared here: the server derives it from the container during asynchronous verification, so a client cannot assert a codec that drives player behaviour.

- `MediaFolder` — required: id, name, parent_id, revision, created_at, updated_at

  One media library folder. A playlist selector by:folder plays the ready files directly inside it, not those of its subfolders.

- `MediaFolderCreate` — required: name

- `MediaFolderList` — required: items

- `MediaFolderPatch` — required: none

- `MediaGeneration` — required: media, usage

  Ready generated still plus billed usage. Never image bytes, b64_json, object keys, signed URLs, vendor URLs, vendor cost, vendor tokens, the deployment model name, or the prompt.

- `MediaGenerationReference` — required: none

  Exactly one of media_id or b64. HTTP URLs are rejected.

- `MediaGenerationRequest` — required: prompt

  Requests one still image. The prompt is customer bytes and is never returned, logged, or published on project SSE. aspect_ratio defaults to 16:9. quality is low, medium, or high and defaults to medium. tag is stored on the ready media object like an upload tag. Optional references are inline base64 or a project media_id; HTTP URLs are rejected. The summed size of all references of one request, inline and media_id alike, must not exceed 25165824 bytes (24 MiB); a request over the bound is refused. 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 development.

- `MediaGenerationUsage` — required: quality, credits, usd

  Customer-facing debit for one stored still. quality is the requested quality and changes the image, not a fixed price. credits are twice the vendor cost of that still, rounded up to whole ScreenRig credits. usd is the dollar equivalent of those credits. Never a vendor name, vendor cost, or token count.

- `MediaList` — required: items

- `MediaMove` — required: folder_id

  At least one id across media_ids and folder_ids, at most 1000 in all.

- `MediaMoveResult` — required: media, folders

- `MediaSelectorTagFilter` — required: none

- `MediaSingleTagPatch` — required: tag

- `MediaTagPatch` — required: none

- `MediaTagsPatch` — required: tags

- `MediaUploadDeclaration` — required: filename, content_type, bytes, sha256

  Declares one upload. The bytes maximum is a plan-independent transport ceiling, not an achievable size. An image content type is bounded further at 20971520 bytes, 20 MiB, and a larger image declaration is rejected with invalid_request before any byte is transferred. That image bound is a byte-only gate: declaration never decodes the image to enforce it, and it applies to animated GIF and animated WebP exactly as it applies to a still. The default plan has no product storage cap. A custom storage ceiling, when present, is checked first and a declaration it cannot hold is rejected with quota_exceeded before any byte is transferred. Remaining prepaid credit of zero rejects declare and commit with payment_required. An audio/mpeg (MP3) declaration is bounded at 178257920 bytes, 170 MiB, and a larger one is rejected the same way; read media_audio_bytes to preflight it. Read media_image_bytes from the capabilities document to preflight the image bound, and project_content_bytes there, or content_limit_bytes on the project, to know any custom storage ceiling. An optional tag is stored on the ready media object and is not redeclared at commit. An optional source_filename records the caller's original file name when the client transcoded the bytes before upload; the ready object's filename is then derived from it so distinct sources never collide (photo.png uploaded as photo.webp is stored as photo.png.webp, photo.jpg as photo.jpg.webp, and a source photo.webp stays photo.webp).

- `MediaUploadSession` — required: id, operation, upload_url, method, headers, expires_at

- `MediaUploadSource` — required: filename, content_type, sha256, bytes

  Verified original upload identity when ingest normalized EXIF orientation. Read-only; use it to recognize retries. The ready media digest, size and type describe the normalized delivery bytes.

- `MemberApproverUpdate` — required: can_approve

- `NativeIdentityChallenge` — required: nonce, expires_at, aud, op, request_hash

  Compare the returned op and request_hash with your locally computed values before signing. Each nonce expires in 120 seconds and can be consumed once.

- `NativeIdentityChallengeRequest` — required: kid, op, request_hash

  Supply the operation and its request_hash for every challenge. An unknown key also requires public_key and op=pairing.start. Creating a challenge does not enroll the key.

- `NativeIdentityProof` — required: kid, public_key, signature

  Compact JWS protected header is exactly {"alg":"EdDSA"}. Claims are kid, nonce, aud=screenrig-runtime, op, integer iat/exp, nonempty jti, and request_hash. Contextual proofs last at most 120 seconds and expire no later than their issued challenge. request_hash is lowercase SHA-256 hex of UTF-8 strings joined by one NUL, with no trailing NUL, starting with screenrig.native.identity.request.v1 and op. Append Idempotency-Key for pairing.start and session.mint; append the exact srp_ pairing credential then completion_nonce for pairing.complete; append nothing for identity.reset. Every proof must include request_hash. A cached operation result still requires a fresh issued proof or the exact accepted proof while its short-lived receipt remains valid. Duplicate or unknown JWS/JWK members, noncanonical base64url, and non-prime-order keys are rejected. Unknown request body members are ignored.

- `NativeIdentitySessionRequest` — required: kid, public_key, signature

  NativeIdentityProof with op=session.mint plus the optional host hint. A present host REPLACES the host stored on the screen, because firmware and shell versions drift over a screen's life. The hint never affects authorization; only the signed proof does. Unknown members are ignored, and host and playback are read tolerantly (docs/player-compatibility.md §1.1).

- `NativePairingComplete` — required: completion_nonce, public_key, signature

- `NativePairingCompletion` — required: screen, public_url

- `NativePairingFleetLink` — required: status

  What the presented fleet link did for this pairing session. claimed and recovered mean the screen is already chosen, and the Player completes pairing without showing its code. Any other status leaves the code on the glass for an ordinary claim.

- `NativePairingRecovery` — required: offered

  Present only when the host duid or serial in the start request matched exactly one existing screen bound to a different key. It carries no screen, label, or project: the offer is surfaced project-side as Screen.recovery_pending and the screen.recovery_offered event, and only the owning project can confirm it. It is absent, exactly as without a match, when the identifier is ambiguous, when the matched screen's key showed life within the last ten minutes, or when that screen already took three offers in the rolling hour. The pairing code still issues and displays exactly as without a match. The player uses this solely to log pairing.recovery_offered.

- `NativePairingSession` — required: code, pairing_credential, expires_at

- `NativePairingStart` — required: kid, public_key, signature

  Proof of private-key possession is required before enrollment or locator creation. Sign a fresh pairing.start challenge bound to the exact Idempotency-Key; unsigned starts are rejected. The optional host object is a hint about the device, not a credential; it is stored on the screen at claim and may open a recovery offer project-side. Unknown body members are ignored. The host hint is read tolerantly. An unknown host key is ignored and a malformed host field is dropped, never refused (docs/player-compatibility.md §1.1.1).

- `NativeRuntimeSession` — required: paired, capabilities, event_cursor, public_url, runtime_credential

  Runtime-owned native session (docs/player-compatibility.md §8). Readers ignore unknown members and unknown capabilities values. A new member is sent only to sessions whose grants cover it.

- `NextAction` — required: command, reason

- `Note` — required: id, body, about, state, author, created_at, confirmed_at, review_at, expires_at, resolved_at, resolution, due

  A project note. Context written by an agent, never an instruction or approval; render body as plain text.

- `NoteAbout` — required: type, id

- `NoteAuthor` — required: agent_id, name, agent_type

  The writing agent as it was when the note was added. Attribution, never authority.

- `NoteCounts` — required: open, new, due

  The project's open notes, and how many are new to and due for this caller.

- `NoteList` — required: items, next_cursor, review, counts

- `NotePage` — required: items, next_cursor

- `NoteResolution` — required: cause, reason, by

- `NoteResolve` — required: reason

- `NoteResolvedBy` — required: kind, id, name

  Who resolved the note. agent_type is present for an agent. Attribution, never authority.

- `NoteWrite` — required: body

- `OAuthAuthorization` — required: id, client_name, client_registered, redirect_host, access, capabilities, created_at, expires_at

- `OAuthAuthorizationRedirect` — required: redirect_uri

- `OAuthError` — required: error

- `OAuthTokenResponse` — required: access_token, token_type, expires_in

- `Operation` — required: id, kind, state, created_at, updated_at

- `OperationAccepted` — required: id, release_id, operation_id

- `OrganizationAdminInvitation` — required: id, kind, delivery, status, recipient_email, organization_id, created_at, expires_at

- `OrganizationAdminInvitationCreate` — required: emails

- `OrganizationAdminInvitationCreated` — required: invitations

- `OrganizationAdminInvitationList` — required: items, next_cursor

- `OrganizationAdmins` — required: admins, candidates, revision

- `OrganizationAdminsUpdate` — required: admin_user_ids

- `OrganizationBillingUsage` — required: org_id, period, pool, allowance, screens, projects, next_cursor

- `OrganizationDomainChallenge` — required: organization_id, challenge_id, domain, name, value, expires_at

- `OrganizationDomainInput` — required: domain

- `OrganizationDomainProof` — required: challenge_id

- `OrganizationDomainRemoved` — required: status

- `OrganizationList` — required: organizations, new_organization_allowed

- `OrganizationPeople` — required: projects, people, truncated

- `OrganizationPeopleMembership` — required: project_id, role

- `OrganizationPeoplePerson` — required: user_id, display_name, email, admin, projects

- `OrganizationPeopleProject` — required: project_id, name

- `OrganizationPerson` — required: user_id, display_name, email

- `OrganizationPersonal` — required: none

  The organization is only its one project's wrapper: exactly one live project, no verified domain, no organization plan now or scheduled, and no admin other than that project's owner. The dashboard hides the organization level for it.

- `OrganizationProject` — required: id, name, organization_id, status, screen_count

- `OrganizationProjectPage` — required: projects, next_cursor

- `OrganizationRename` — required: name

- `OrganizationSummary` — required: id, name, personal

- `OrganizationView` — required: id, name, personal, revision, admin, project_count, plan_id, free, plan_required

- `PageFailure` — required: page_id, code, at

  Latest accepted playback.page_failed report for this screen. Absent until a player reports one. Read-only project health metadata; it does not change screen revision or manifest authority.

- `PageVisibility` — required: enabled

  Optional page visibility schedule. It is a sibling of advance and is never part of screenrig.canvas/v1; it describes playback orchestration, not geometry. The server validates and carries these rules as data. The player evaluates them against the screen timezone published at the manifest root. A page is eligible when enabled is true, now is inside [from, until), and either windows is absent or at least one window matches. Every playlist must keep at least one page with no visibility field at all, so eligible content always exists. Conformance vectors for the evaluation rules are the screenrig.schedule/v1 family in packages/schedule-contract.

- `PageVisibilityWindow` — required: days

  One recurring civil window. Omitting start and end selects the whole day. When end is less than or equal to start the window crosses midnight and the start day owns it, so fri 22:00 to 02:00 runs into Saturday morning.

- `PairScreen` — required: code

- `PairingClaim` — required: screen, public_url

- `PairingClaimedEvent` — required: type, completion_nonce

- `PairingComplete` — required: completion_nonce

- `PairingCompletion` — required: screen, public_url

- `PairingSession` — required: code, expires_at

- `PasskeyAdditionProof` — required: authorization_ceremony_id, authorization_credential

- `PlaybackAggregate` — required: screen_id, media_id, filename, day, play_count, last_page_id, last_manifest_revision, first_started_at, last_started_at

- `PlaybackAggregateCsv` — required: none

  RFC 4180 CSV, CRLF records, UTF-8, with the header row screen_id,media_id,filename,primitive,day,play_count,last_page_id,last_manifest_revision,first_started_at,last_started_at. Times are RFC 3339 UTC. A cell beginning with =, +, -, @, tab, or carriage return is prefixed with a single quote so spreadsheets do not evaluate it. Content-Disposition names playback-aggregates.csv. A stream that fails after the header is aborted at the connection.

- `PlaybackAggregateList` — required: items

- `PlaybackApplicationStartedContext` — required: manifest_revision, page_id, primitive_id, release_id

- `PlaybackApplicationStartedReport` — required: severity, code, context

  One visible start of an application primitive on a page: the application became visible on glass, reported once per page appearance by the Player controller — the application content never reports its own visibility, and a package download is never a start. The sending screen is authenticated; page, primitive, release and manifest identities are reported observations without content or grant membership checks. Stores one durable per-play record (GET /api/playback/plays). Project-owned release/application housekeeping timestamps follow through a recoverable batch projection. Ordinary playback acknowledgments omit optional credit balance headers. There is no daily aggregate row for application starts; the play records are the evidence.

- `PlaybackGrant` — required: none

  The playback handshake the backend granted: the intersection of the mint's PlaybackHandshake and what this backend supports, signed into the session. Omitted from the session when nothing intersects, so a session minted without a handshake is unchanged. A Player uses /screen/manifest only when manifest_versions contains 3.

- `PlaybackHandshake` — required: none

  Optional playback handshake a Player declares at mint. It is a hint that selects behavior, never authorization: unknown members and entries of the wrong type are ignored, and a value that is not an object negotiates nothing. The mint response echoes the supported intersection as PlaybackGrant. adslot-v1 is granted only to paired sessions.

- `PlaybackMediaStartedContext` — required: media_id, manifest_revision, page_id, primitive

- `PlaybackMediaStartedReport` — required: severity, code, context

  One visible start of an image or video primitive on a page. Players send this once per page appearance of the media, for images exactly as for videos; loops inside one appearance are not restarts. The sending screen is authenticated; media, page, primitive and manifest identities are observations without content or grant membership checks. Success acknowledges durable raw insertion; daily counts follow through a recoverable batched projection. Ordinary playback acknowledgments omit optional credit balance headers.

- `PlaybackPageFailedContext` — required: page_id, code

- `PlaybackPageFailedReport` — required: severity, code, context

  Reports that one page could not become ready. On acceptance the server replaces Screen.last_page_failure and appends a durable project-only playback.page_failed event in the same transaction. It does not change screen revision or manifest authority.

- `PlaybackPlay` — required: screen_id, page_id, media_id, primitive, received_at

  One durable Player-reported start of an image, video, or application. The session authenticates screen and project; item, page, manifest, primitive and release identities are reported observations and do not authorize content access. playlist_id and playlist_revision are optional metadata enriched asynchronously from a matching screen manifest; missing metadata stays absent. Application starts carry release_id with empty media_id and primitive. primitive_id and started_at are present only when the Player reports them; started_at is clamped to [received_at - 24h, received_at + 5m].

- `PlaybackPlayCsv` — required: none

  RFC 4180 CSV, CRLF records, UTF-8, with the header row screen_id,playlist_id,page_id,primitive_id,media_id,primitive,started_at,received_at,manifest_revision,playlist_revision in that fixed order; an absent value is an empty cell. Times are RFC 3339 UTC. A cell beginning with =, +, -, @, tab, or carriage return is prefixed with a single quote. Content-Disposition names playback-plays.csv. A complete export ends after its last CRLF record with no trailer; a stream that fails after the header is aborted at the connection (the client sees a transport error, never a clean end), so retry from the last received_at.

- `PlaybackPlayList` — required: items, next_cursor

- `PlaybackTotals` — required: by, day_from, day_to, truncated

  Play totals over the inclusive UTC days day_from to day_to. Exactly one of days (by=day), screens (by=screen), media (by=media) or screen_days (by=screen_day, grouped by screen in rank order, days ascending) is present, the one the request named. truncated is true when a ranking may leave out keys that ranked below the best of a slice or a store.

- `PlaybackTotalsDay` — required: day, play_count, first_started_at, last_started_at

- `PlaybackTotalsMedia` — required: media_id, play_count, first_started_at, last_started_at

- `PlaybackTotalsScreen` — required: screen_id, play_count, first_started_at, last_started_at

- `PlaybackTotalsScreenDay` — required: screen_id, day, play_count

- `PlaybackVideoStartedContext` — required: media_id, manifest_revision, page_id

- `PlaybackVideoStartedReport` — required: severity, code, context

  Report one visible video start. This uses the same daily aggregation as playback.media_started with primitive video. Send one report per start; sending both event types counts the start twice. Use playback.media_started when reporting both images and videos.

- `PlayerCardsIdentity` — required: none

  The built-in cards library the Player carries (docs/player-compatibility.md §3.1): the library CalVer and each card's semver, whose major says which card placements the screen renders. Stored on the screen with the rest of player. Malformed entries are dropped one by one; it is never a 400.

- `PlayerIdentity` — required: kind

  Player self-identification, sent only on the three mint routes (docs/player-compatibility.md §3.1). It is a hint and never authorization, and it is not covered by the native proof. It is read tolerantly: unknown members are ignored, an invalid optional member is dropped on its own, and a player that is not an object or has no valid kind declares nothing. It is never a 400. A paired mint stores it on the screen for the operator fleet histogram.

- `PlayerPublicKey` — required: kty, crv, x

- `PlayerShellIdentity` — required: kind

  The shell a web Player runs under. Its compat tokens are recorded, never granted. An invalid kind drops the shell.

- `Playlist` — required: id, name, revision, pages

- `PlaylistAdvance` — required: none

- `PlaylistAdvanceWrite` — required: none

- `PlaylistApplicationAdvance` — required: mode, max_ms

- `PlaylistApplicationPrimitive` — required: none

- `PlaylistApplicationPrimitiveWrite` — required: id, primitive, release_id, rect, layer, content_fit

- `PlaylistApproval` — required: state, review_id

  Present while a review of this playlist is pending. The playlist itself is the live (approved) version.

- `PlaylistAudio` — required: tracks, loop, volume

  Stored playlist soundtrack with defaults filled in.

- `PlaylistAudioCue` — required: track

  Optional soundtrack hint on an ordinary page. When the page becomes current, the Player switches to the named track from its start unless it is already playing; restart true restarts it even then. The sequence continues from that track. Players may ignore a hint. track must name a track id in the playlist audio; a cue without a playlist audio is invalid_request. Not allowed on adslot pages.

- `PlaylistAudioTrack` — required: id, media_id

- `PlaylistAudioWrite` — required: tracks

  Optional playlist soundtrack: ordered ready audio (MP3) media that plays continuously while pages change (docs/playlist-audio.md). Page changes never stop or restart it. PUT replaces the whole playlist, so omitting audio removes the soundtrack.

- `PlaylistCanvas` — required: width, height, background

- `PlaylistCardPrimitive` — required: id, primitive, card, major, params, rect, layer, content_fit, controller

- `PlaylistCardPrimitiveWrite` — required: id, primitive, card, rect, layer, content_fit

  A built-in card the Player draws natively (clock, weather, menu board, ticker, KPI tiles and the rest of the vendored cards catalog, screenrig.cards.contract/v1). params holds the placement's static properties (theme, view, corner, location, titles, column definitions) and changes only with a republish. data names a card data document (GET/PUT /api/card-data/{key}) that holds the card's dynamic properties (menu sections, ticker items, KPI tiles); agents change it without a republish. With data, params may not carry dynamic properties; without it, params carry the whole configuration. Publish refuses an unknown card or major and invalid params (card_invalid), and a bound key whose stored document the card rejects (card_data_invalid). A card with a third-party feed (weather) reads it from the key the server derives from its static params.

- `PlaylistDurationAdvance` — required: mode, after_ms

- `PlaylistIframePrimitive` — required: id, primitive, src, title, rect, layer, content_fit, controller

- `PlaylistIframePrimitiveWrite` — required: id, primitive, src, title, rect, layer, content_fit

- `PlaylistImagePrimitive` — required: id, primitive, selector, resolved_media, rect, layer, content_fit, controller

- `PlaylistImagePrimitiveWrite` — required: id, primitive, selector, rect, layer, content_fit

- `PlaylistIntrinsicSize` — required: width, height

- `PlaylistList` — required: items

- `PlaylistMediaEndAdvance` — required: mode, max_ms

- `PlaylistMediaEndAdvanceWrite` — required: mode

- `PlaylistMediaSelector` — required: none

- `PlaylistMediaSelectorByAll` — required: by

- `PlaylistMediaSelectorByFolder` — required: by, folder_id

  The ready files of the primitive directly inside one media folder (GET /api/media-folders), not those of its subfolders, ordered and paced like by:all. Like a tag, the folder is read again at every selector resolve, so moving files in or out changes what playing screens show from their next loop. Writing a playlist requires the folder to exist in the project; it may be empty. A folder that holds no ready file of the primitive, or is deleted later, matches nothing. Players receive it as by:all; the server resolves the folder.

- `PlaylistMediaSelectorByID` — required: by, media_id

- `PlaylistMediaSelectorByIDs` — required: by, media_ids

- `PlaylistMediaSelectorByTag` — required: by

- `PlaylistPage` — required: id, canvas, transition, advance, primitives

- `PlaylistPageV2` — required: none

- `PlaylistPageWrite` — required: id, canvas, transition, advance, primitives

- `PlaylistPageWriteV2` — required: none

- `PlaylistPrimitive` — required: none

- `PlaylistPrimitiveWrite` — required: none

- `PlaylistRect` — required: x, y, width, height

- `PlaylistResolvedMedia` — required: media_id, intrinsic_size

- `PlaylistStreamPrimitive` — required: id, primitive, sources, fallback_media_id, rect, layer, content_fit, muted, controller, resolved_media

- `PlaylistStreamPrimitiveWrite` — required: id, primitive, sources, fallback_media_id, rect, layer, content_fit

- `PlaylistTransition` — required: type, duration_ms

  Page transition. type and duration_ms are both required; OpenAPI does not default them. Swipe authoring examples use duration_ms 600; that value is not a schema default.

- `PlaylistV2` — required: id, name, revision, pages

- `PlaylistV2List` — required: items

- `PlaylistVideoPrimitive` — required: id, primitive, selector, resolved_media, muted, loop, rect, layer, content_fit, controller

- `PlaylistVideoPrimitiveWrite` — required: id, primitive, selector, rect, layer, content_fit

- `PlaylistWrite` — required: name, pages

- `PlaylistWriteV2` — required: name, pages

- `PrimitiveEnter` — required: type

  Optional object enter. Playlist write and the runtime manifest use this object as-is; there is no snake_case rename inside it. Absent means paint at rest, full opacity. Optional integer stagger 0 through 8 delays start by stagger*120 ms after the shared 500 ms delay. Absent or 0 is today's start. Duration is a named constant in screenrig.canvas/v1.

- `PrimitiveMotion` — required: none

  Optional persistent object motion. Playlist write and the runtime manifest use this object as-is; there is no snake_case rename inside it. Absent means the primitive stays at rest after enter. Copied through uninterpolated. spin and drift apply to image and video only; application and iframe with spin or drift are rejected by the server (motion_not_allowed_for_primitive). path applies to image, video, application, and iframe. This restriction is not encoded as OpenAPI if/then.

- `PrimitiveMotionDrift` — required: type, zoom, direction, speed

  Continuous slow scale about the clipRect centre combined with a slow translate. Image and video only; the server rejects drift on application and iframe. Ping-pongs; never jumps.

- `PrimitiveMotionPath` — required: type, points, rate

  Translation along a polyline of canvas-unit waypoints after the authored rect. loop defaults to loop when omitted. Valid on image, video, application, and iframe.

- `PrimitiveMotionPoint` — required: x, y

- `PrimitiveMotionSpin` — required: type, direction, speed

  Continuous rotation about the clipRect centre. Image and video only; the server rejects spin on application and iframe.

- `Problem` — required: errors, instance, request_id

  The problem document every route answers except the Player-facing runtime, content, and probe routes (RuntimeProblem). The error itself is errors[0]: its status, code, type, title, detail, and hint. Only document members stay at the top level. Branch on errors[0].code, never on detail or hint, which are prose. A blamed request field adds errors[0].field; each further blamed field is its own entry with that field's detail.

- `ProblemError` — required: status, code, type, title, detail, hint

  One error of a problem document. Members beyond these describe this error further (for example dependencies on not_ready).

- `ProblemField` — required: field, code, detail

- `Project` — required: id, revision, status, email, email_verified, used_bytes, reserved_bytes, screen_count, content_limit_bytes, screen_limit, credit_remaining, created_at, updated_at, name

- `ProjectBillingUsage` — required: period, usage, allowance

- `ProjectCapabilities` — required: project_id, plan_id, features, feature_revision, capabilities

- `ProjectContext` — required: project, organization

- `ProjectContextList` — required: projects

- `ProjectCreate` — required: name

- `ProjectDeleteConfirmation` — required: name, revision

- `ProjectDeletionPreview` — required: project, organization, devices, allowed

- `ProjectFeatures` — required: advertiser, screens

- `ProjectMove` — required: organization_id

- `ProjectMoveDestinations` — required: organizations

- `ProjectName` — required: none

  Trimmed printable project name. Rejects controls, URL-looking text, @, www., and ://.

- `ProjectOwnerTransfer` — required: user_id

- `ProjectPatch` — required: name

- `ProjectUptime` — required: items

- `ProvisionScreen` — required: none

- `ReadyResponse` — required: status, degraded

- `ReleaseLaunch` — required: launch_url, expires_at

- `Review` — required: id, kind, state, subject, proposer, url, created_at, updated_at, impact

- `ReviewAdCreative` — required: creative_id, creative_revision, content_path

- `ReviewApproveRequest` — required: none

- `ReviewBlocker` — required: id, state, subject

- `ReviewChangesRequest` — required: note

- `ReviewDecider` — required: none

- `ReviewDecisionResult` — required: review

- `ReviewDependentResult` — required: id, state

- `ReviewImpact` — required: screens

- `ReviewList` — required: items, next_cursor, pending_count

- `ReviewLocation` — required: project_id, project_name

- `ReviewMediaChange` — required: media_id, filename, before, after

  The pending change of one media item a selector_refresh review asks about.

- `ReviewMediaFields` — required: tags, folder_id

- `ReviewMediaPlaylist` — required: id, name, screens, media_added, media_removed

- `ReviewPending` — required: status, review

  A review-class write under Propose access. The change is not on glass until a person approves the review.

- `ReviewPerson` — required: user_id, name

- `ReviewPlaylistChange` — required: operation, base_revision, proposed, diff

- `ReviewPlaylistDiff` — required: name_changed, pages_added, pages_removed, pages_changed, soundtrack_changed, media_added, media_removed

- `ReviewPolicy` — required: agent_changes_require_review, review_policy_revision

- `ReviewPolicyUpdate` — required: agent_changes_require_review

- `ReviewPreview` — required: live, proposed

  Live and proposed playlists in the stored shape GET /api/playlists/{id} returns (pages with resolved_media).

- `ReviewProposer` — required: kind

- `ReviewRejectRequest` — required: reason

- `ReviewScreenChange` — required: action, screens, request, playlist_ids

- `ReviewSubject` — required: type, id, name

- `ReviewSummary` — required: id, kind, state, url, subject, impact

- `RuntimeAdslotCandidate` — required: page_id, occurrence_id, check_sequence, earliest_start_at

- `RuntimeAdslotEvent` — required: event_id, decision_id, occurrence_id, kind, client_sequence

- `RuntimeAdslotEventBatch` — required: events

- `RuntimeAdslotEventResponse` — required: results

- `RuntimeAdslotEventResult` — required: event_id, state

- `RuntimeAdslotMedia` — required: kind, duration_ms, content_type, bytes, sha256, content_path, fit

- `RuntimeAdslotPage` — required: id, type, adslot_id

  Whole-page advertising slot, sent only to a paired session granted adslot-v1. A Player that meets an unknown page type drops that page.

- `RuntimeAdslotResolveRequest` — required: manifest_revision, candidates

- `RuntimeAdslotResolveResponse` — required: server_time, results

- `RuntimeAdslotResult` — required: occurrence_id, check_sequence, state

- `RuntimeAdvance` — required: mode

- `RuntimeApplicationPackage` — required: version, url, bytes, sha256, entrypoint, contentType

  Immutable normalized package built only from the independently validated application tree. Native players verify bytes and SHA-256 before atomically retaining this release for offline last-known-good restoration. The URL is an authorization-bound transport reference, not a public or durable object URL.

- `RuntimeApplicationPrimitive` — required: id, primitive, applicationId, releaseId, launchUrl, origin, protocol, grantId, package, rect, layer, contentFit

- `RuntimeAudio` — required: loop, volume, tracks

  Playlist soundtrack (docs/playlist-audio.md). Emitted only when the playlist has one and at least one track projected. The Player plays tracks in order, independently of the page sequencer; page changes, transitions and visibility never pause or restart it. Track identity is id plus mediaId plus sha256, never src, so a manifest refresh that keeps the playing track does not restart it. Effective gain is volume times the Player volume. A defective track is dropped on its own (invalid_audio_track).

- `RuntimeAudioCue` — required: track, restart

  Soundtrack hint emitted on a page that authored audio_cue. When the page becomes current: if a different track is playing, or none, switch to track from its start; if track is playing, restart it only when restart is true. The sequence continues from track. A hint may be ignored. A cue that names no manifest track is dropped (invalid_audio_cue).

- `RuntimeAudioTrack` — required: id, mediaId, src, sha256, bytes, contentType, durationMs, grantId

- `RuntimeCanvas` — required: width, height, viewportFit, background

- `RuntimeCardPrimitive` — required: id, primitive, card, major, params, rect, layer, contentFit

  A built-in card. The Player merges the card's defaults, then params, then, when data is present, the dynamic properties of the document at GET /screen/card-data/{data} (only keys the card marks dynamic). A card with data stays hidden until its first document arrives and then fades in; screen.card_data_changed for the key refetches it in place. feed, when present, is fetched from the same route and passed to the card as its feed. A Player that lacks the card or its major draws nothing for it.

- `RuntimeChrome` — required: schema_version, banner

  Runtime-owned chrome (docs/player-compatibility.md §8). Readers read the members they know and ignore the rest; a banner shape a reader does not understand shows no banner.

- `RuntimeCodecs` — required: none

  Server-derived RFC 6381 codec list for this exact rendition, never client-declared. It is a sibling of contentType; a Player can form a capability query as contentType followed by codecs="VALUE". A video whose codec cannot be derived is rejected at upload. The property is optional. An absent value does not mean that the codec is unsupported. A present value currently contains one video-track entry; the grammar permits comma-separated entries.

- `RuntimeConditionReport` — required: severity, code, context

- `RuntimeDisplay` — required: none

  The screen's display power control, evaluated on the device (docs/player-compatibility.md §6.5). Omitted when the screen has none; then the display stays on. Unknown members are ignored.

- `RuntimeHealthWrite` — required: none

  The PUT /screen/health body. Every member is optional; send what the platform can measure. Unknown members are ignored and a value out of range is dropped (not refused). Integers are whole JSON numbers.

- `RuntimeHostHint` — required: platform

  The optional native host hint on pairing start, pairing completion, and identity session mint. It names a device and is never authorization. It is read tolerantly (docs/player-compatibility.md §1.1.1): unknown members are ignored, a malformed field or capabilities entry is dropped, at most 32 capabilities are kept, and a value that is not an object or a platform outside the pattern drops the whole hint. None of these refuses the request. A Player still sends only the registered platform values and the members below, because a pre-contract backend refuses anything else.

- `RuntimeHostHintDevice` — required: none

  Optional hardware identifiers in the native host hint. A malformed field is dropped.

- `RuntimeIframePrimitive` — required: id, primitive, src, title, rect, layer, contentFit

- `RuntimeImagePrimitive` — required: id, primitive, selector, resolvedMedia, grantId, rect, layer, contentFit

- `RuntimeImageResolvedMedia` — required: mediaId, src, intrinsicSize, sha256, bytes, contentType

- `RuntimeIntrinsicSize` — required: width, height

- `RuntimeKVEntry` — required: application_id, key, value_base64, content_type, bytes, sha256, revision

- `RuntimeKVList` — required: items

- `RuntimeKVSummary` — required: application_id, key, content_type, bytes, sha256, revision

- `RuntimeKVWrite` — required: value_base64

- `RuntimeManifest` — required: schemaVersion, manifestRevision, grantId, contentGeneration, screenId, screenLabel, playlistRevision, generatedAt, pages

  Immutable runtime manifest using the deterministic screenrig.canvas/v1 canvas, rectangle, fit, and source-order contract. Read-side additional properties are permitted so a player on this contract ignores unknown keys (compatibility rule 2). Write schemas stay closed. Pages are ordinary canvas pages and, for a paired session granted adslot-v1, whole-page adslot pages; a manifest with no adslot page is house content.

- `RuntimeManifestDiagnostic` — required: level, code, detail

  One fail-safe runtime projection omission. Players and the CLI may show these; they are never authorization.

- `RuntimeMediaSelector` — required: none

- `RuntimeMediaSelectorByAll` — required: by, oneAtATime

- `RuntimeMediaSelectorByID` — required: by, mediaId, oneAtATime

- `RuntimeMediaSelectorByIDs` — required: by, mediaIds, oneAtATime

- `RuntimeMediaSelectorByTag` — required: by, oneAtATime

- `RuntimeObservationSurfaceWrite` — required: id, width, height, pixel_ratio, presentation

- `RuntimeObservationWrite` — required: observed_at, surfaces

  The PUT /screen/observation body. Unknown members are ignored (docs/player-compatibility.md §1.1). The stored value is the project ScreenObservation.

- `RuntimeOrigins` — required: content

  The delivered origin allowlist (docs/player-compatibility.md §2.3). Present on a mint response only for a Player granted origins-v1. The Player unions content with its compiled content origins and ignores an invalid entry. release_suffix replaces the compiled release suffix only when it is a valid DNS suffix of at least two labels; it is absent when the runtime origin serves releases itself (local development). The delivered set never changes the credential origin, and the backend never puts a URL on an origin this session's mint did not deliver. A Player stores the object it validated last-known-good under, so a later session without origins keeps that LKG valid.

- `RuntimePage` — required: contractVersion, id, canvas, transition, advance, primitives

- `RuntimePairingScreen` — required: id, public_id, label, state, revision, manifest_revision, content_access_generation, created_at, updated_at

  Runtime-owned screen summary in pairing and browser provisioning completions. Its member set is frozen: project Screen fields never appear here (docs/player-compatibility.md §8). state stays within the three values below.

- `RuntimePrimitive` — required: none

- `RuntimeProblem` — required: type, title, status, detail, instance, code, request_id, errors

  The problem document of the Player-facing screen (/screen/) and media (/media/) routes, the pairing streams and the probe routes (/.ready, /.health): every member flat at the top level. Store-released Players read its top-level code and action (docs/player-compatibility.md §5), so this shape stays as it is; every other route answers Problem.

- `RuntimeRect` — required: x, y, width, height

- `RuntimeReport` — required: none

  Report bodies and contexts are read tolerantly (docs/player-compatibility.md §1.1). A code needs its own context members; other members are ignored. Players still send only the members below, because a pre-contract backend refuses any other.

- `RuntimeScreenLabel` — required: screen_label

  The stored screen name, the value the manifest carries as screen_label.

- `RuntimeScreenLabelWrite` — required: screen_label

  The PUT /screen/screen-label body. The server trims screen_label and stores 1 to 120 UTF-8 bytes without control characters. Unknown members are ignored (docs/player-compatibility.md §1.1).

- `RuntimeSelectorSnapshot` — required: manifestRevision, snapshotRevision, selections, pageMaxMs

- `RuntimeSession` — required: paired, capabilities, event_cursor, public_url

  Runtime-owned session (docs/player-compatibility.md §8). Readers ignore unknown members and unknown capabilities values. A new member is sent only to sessions whose grants cover it.

- `RuntimeSessionRequest` — required: public_id

  Anonymous viewer mint. Unknown members are ignored (docs/player-compatibility.md §1.1).

- `RuntimeStorageWrite` — required: observed_at, volume, cache, durability, plan, transfer_24h

  The PUT /screen/storage body. Unknown members are ignored (docs/player-compatibility.md §1.1). The stored value is the project ScreenStorage plus server received_at.

- `RuntimeStreamPrimitive` — required: id, primitive, sources, muted, fallback, grantId, rect, layer, contentFit

- `RuntimeTelemetryBatch` — required: telemetry_epoch, stream_id, sequence, created_at, reports

  A durable FIFO Player queue sends at most one ordinary batch per minute with a randomized phase, or earlier when full. Total JSON body is at most 256 KiB; at most 64 reports, each at most 32 KiB. Every report retains its occurrence timestamp and stable play_id. Keep one immutable in-flight batch on disk until 202, increment sequence only after acknowledgement, and retry the same bytes after lost replies or restarts. Sequence starts at 1 and has no gaps. Dedupe retains at most eight live streams per screen, each watermark for at least the 24 hours in which its batch can still be replayed; keep the same stream through credential renewal. Records expire locally after 24 hours with explicit accounting. manifest.activated and manifest.upgrade stay individual and immediate. Interactive writes, ad resolution, content fetches, screenshots, and SDK emitConfirmed() also stay immediate. Health sampling retains its fifteen-minute cadence; urgent display/health transitions are sent immediately. Observation and ordinary health snapshots are latest-wins with original occurrence times. Health requires health.write. Existing individual reports remain accepted. Report item abuse limits apply per new item rather than per HTTP envelope; committed replay consumes only request admission.

- `RuntimeTransition` — required: type, durationMs

  Authored page transition. The runtime mint passes type through; it does not rewrite swipe to crossfade.

- `RuntimeUpgradeNotice` — required: state, player_kind, recommended

  Present on a mint response when the player's comparable version is below the recommended version for player_kind (docs/player-compatibility.md §6.1). The Player starts a platform update check and shows nothing on glass. A reader ignores the member for a state it does not know.

- `RuntimeVideoPrimitive` — required: id, primitive, selector, resolvedMedia, muted, loop, grantId, rect, layer, contentFit

- `RuntimeVideoResolvedMedia` — required: mediaId, src, intrinsicSize, sha256, bytes, contentType

- `Screen` — required: id, public_id, label, revision, manifest_revision, manifest_upgrade, content_access_generation, state, online, created_at, updated_at

- `ScreenAction` — required: none

  One fleet action, discriminated by type. Each member carries only its own parameters; a member of another type is an unknown field and invalid_request.

- `ScreenActionAddTags` — required: type, tags

  Adds tags a screen does not already carry. A screen that would exceed 16 tags fails with invalid_request.

- `ScreenActionAssign` — required: type, playlist_id

- `ScreenActionClearDisplaySchedule` — required: type

  Removes each screen's display schedule; a screen without one is ok and unchanged.

- `ScreenActionClearPlaylistSchedule` — required: type

  Removes each screen's playlist schedule; a screen without one is ok and unchanged.

- `ScreenActionDisplay` — required: type, power

  Same rules as ScreenDisplayWrite. until is checked against the request clock when the Idempotency-Key has no fleet record, and again by each screen.

- `ScreenActionDisplayClear` — required: type

  Ends each screen's manual display override (DELETE /api/screens/{id}/display); a screen without one is ok and unchanged.

- `ScreenActionReboot` — required: type

  POST /api/screens/{id}/reboot per screen; a screen whose host did not declare reboot fails alone with reboot_unsupported.

- `ScreenActionReload` — required: type

- `ScreenActionRemoveTags` — required: type, tags

  Removes the listed tags; a tag the screen does not carry is ignored.

- `ScreenActionRequest` — required: selector, action

- `ScreenActionResult` — required: action, matched, succeeded, failed, results

- `ScreenActionScreenResult` — required: screen_id, status

  One screen's outcome. ok carries the result of the single-screen path: revision (and tags for tag actions) for assign, tag, takeover, takeover_clear, set_playlist_schedule, and clear_playlist_schedule actions, reload for reload, toast for toast. failed carries the problem that screen's own request would have answered.

- `ScreenActionSelector` — required: none

- `ScreenActionSelectorIds` — required: by, screen_ids

- `ScreenActionSelectorTag` — required: by, tag

  Every active screen whose tags contain tag, in creation order. There is no screen-count cap.

- `ScreenActionSetDisplaySchedule` — required: type, enabled, windows

  Replaces each screen's display schedule (ScreenDisplayScheduleWrite), normalized once before fan-out. A screen without a timezone fails alone with invalid_request.

- `ScreenActionSetPlaylistSchedule` — required: type, entries

  Replaces each screen's playlist schedule with the same entries (ScreenPlaylistScheduleWrite). Entry ids are assigned once before fan-out. A screen without a timezone or without a default playlist fails alone with invalid_request.

- `ScreenActionSetTags` — required: type, tags

  Replaces each screen's tag set; an empty array clears it.

- `ScreenActionTakeover` — required: type, playlist_id

  Same rules as ScreenTakeoverWrite. until is checked against the request clock before fan-out when the Idempotency-Key has no fleet record, and again by each screen.

- `ScreenActionTakeoverClear` — required: type

  Ends each screen's takeover; a screen without one is ok and unchanged.

- `ScreenActionToast` — required: type, level, text

  Same level, text, and duration rules as ScreenToastWrite, validated once before fan-out.

- `ScreenDisplay` — required: requested, source

  Display power state. requested is what the screen should show now: the active override, else the enabled display schedule evaluated in the screen timezone, else on (source override, schedule, or default); until is when that next changes, when known. reported is the Player's latest health display (power, connected, reported_at, stale after 35 minutes). Absent when the screen has no schedule, no override, and no reported display.

- `ScreenDisplayOverride` — required: override_id, power, until, ends_at, set_at

- `ScreenDisplaySchedule` — required: enabled, windows, updated_at

- `ScreenDisplayScheduleView` — required: display_schedule

- `ScreenDisplayScheduleWrite` — required: enabled, windows

- `ScreenDisplayWrite` — required: power

- `ScreenEffectivePlaylist` — required: id, source

  The playlist the screen's runtime manifest is built from: the takeover, else the first matching playlist schedule entry (entry_id), else the assigned default. until is the next instant this is known to change (takeover end or the next schedule boundary within eight days), absent when nothing time-based changes it. The boundary worker applies a change at most 60 seconds late. A takeover or entry whose playlist cannot be shown is skipped. Absent when the screen shows no playlist.

- `ScreenHealth` — required: reported_at, stale

  The latest device health report (PUT /screen/health), sanitized, with server reported_at. A member the Player did not send is absent. stale is true when reported_at is more than 35 minutes old; stale health is still shown, never deleted; archive and screen recovery clear it. Transitions append the project-only screen.health_changed with details.changes, a list of {change, ...}: display_disconnected, display_reconnected, display_power (from, to), temperature_high (at or above 80 °C), temperature_normal (below 75 °C again), crash_spike and renderer_restart_spike (the 24-hour count rose by 3 or more since the last spike event; crashes_24h or renderer_restarts_24h and previous). Read-only; never authorization.

- `ScreenHealthHistory` — required: none

  A page of reports (resolution raw) or hourly summaries (resolution hour).

- `ScreenHealthHour` — required: hour, reports, temperature_max_c, temperature_avg_c, cpu_load_max, memory_used_ratio_max, wifi_rssi_min_dbm, display_disconnected, crashes_24h_max, renderer_restarts_24h_max, last_reported_at

  One screen's reports in one UTC hour. A member is null when no report of the hour carried it.

- `ScreenHealthHours` — required: resolution, hours, next_cursor

- `ScreenHealthSample` — required: reported_at

  One stored device health report: Screen.health without stale. A member the Player did not send is absent.

- `ScreenHealthSamples` — required: resolution, samples, next_cursor

- `ScreenList` — required: items

- `ScreenManifestUpgrade` — required: desired_revision, active_revision, desired_playlist, active_playlist, state, code, attempt, retry_at, missing_page_count, state_since, reported_at

  Always-present computed manifest-upgrade state for this screen, derived at read time from its issued desired revision or queued shared-playlist content goal, its last server-acknowledged active manifest revision (manifest.activated), and the latest accepted manifest.upgrade report slot. Read-only project state: no body can write it, and no report ever promotes a grant. active_revision is the last acknowledged activation, not proof that every simultaneous session already shows it. Being offline never produces failure, and an overdue retry_at on a retrying state is client presentation, never a server judgment. desired_revision null is none. state pending names a desired revision with no matching report; activating names a target already on glass whose acknowledgement is still pending; a report slot whose revision stopped matching desired_revision is ignored until a new report matches, so a new desired manifest derives pending without clearing anything. Before the next manifest contact issues a shared edit's desired grant, the desired revision and playlist can expose that queued goal as read-only intention; it grants no access. Active identity always comes from the exact acknowledged historical grant. An older activation cannot announce the newer queued goal as current.

- `ScreenObservation` — required: observed_at, surfaces

  Player-reported playback surface. Absent on Screen until the first accepted PUT /screen/observation. Read-only on the project API; ScreenPatch, PairScreen, pairing bodies, session bodies, canvas, runtime manifest, and POST /screen/reports cannot write it.

- `ScreenObservationSurface` — required: id, width, height, pixel_ratio, presentation

- `ScreenPatch` — required: none

- `ScreenPlaylistSchedule` — required: entries, updated_at

  Server-evaluated playlist schedule. It never reaches the runtime manifest; the manifest is built from the effective playlist it selects. Write it with PUT /api/screens/{id}/playlist-schedule.

- `ScreenPlaylistScheduleView` — required: entries

- `ScreenPlaylistScheduleWrite` — required: entries

- `ScreenPresenceHistory` — required: items, next_cursor

- `ScreenPresenceTransition` — required: at, state

- `ScreenProvisioning` — required: screen, public_url, provisioning_url, expires_at

- `ScreenRebootAccepted` — required: reboot_id, expires_at

- `ScreenRecoveryHost` — required: none

  What the device asking to recover this screen says it is, copied from the pairing start's host object. Descriptive fields only; it never carries duid, serial, or mac. Absent fields were not reported.

- `ScreenRecoveryPending` — required: expires_at

  Present while a native pairing session that presented this screen's hardware identity is waiting for the owning project to confirm with POST /api/screens/{id}/recovery/confirm. Carries the deadline, which is the pairing session's own expiry, and the offering device's descriptive host fields (never duid, serial, or mac). A DUID is readable by any application on the panel, so an offer is a phishing surface: compare host.model and host.firmware with the display in front of you before confirming. The server refuses an offer while the screen's bound key has shown life within the last ten minutes (session mint, runtime stream, manifest fetch, or presence heartbeat), and accepts at most three offers per screen per rolling hour; a new offer replaces the pending one. Absent once confirmed, lapsed, or claimed as a new screen. Read-only.

- `ScreenReloadAccepted` — required: reload_id, expires_at

- `ScreenScheduleEntry` — required: id, playlist_id, windows

- `ScreenScheduleEntryWrite` — required: playlist_id, windows

  One playlist schedule entry. id is optional (entry_N is assigned in order when omitted) and unique in the schedule. from (inclusive) and until (exclusive) are optional civil minute-precision bounds with no offset, from before until.

- `ScreenScheduleWindow` — required: days

  One screenrig.schedule/v1 window in the screen timezone. days are stored mon-to-sun. Omit start and end for the whole day. end <= start crosses midnight and the start day owns it (fri 22:00-02:00 runs into Saturday morning); equal edges run a full 24 hours.

- `ScreenScreenshotAccepted` — required: capture_id, expires_at

- `ScreenScreenshotFailedDetails` — required: capture_id, reason

  details object on a durable screen.screenshot_failed event.

- `ScreenScreenshotReadyDetails` — required: capture_id, bytes, width, height, sha256

  details object on a durable screen.screenshot_ready event. Pixels and object keys are not present.

- `ScreenScreenshotRequestedDetails` — required: capture_id, expires_at, max_width_divisor, max_height_divisor, format, quality, max_bytes

  details object on a durable screen.screenshot_requested event. Image bytes and object keys are not present.

- `ScreenScreenshotStatus` — required: state

- `ScreenStorage` — required: observed_at, received_at, volume, cache, durability, plan, transfer_24h

  Sanitized player storage report. Absent until the first accepted PUT /screen/storage. Cleared on identity reset, unpair, and archive. Consumers treat received_at older than 24 hours as stale. Read-only on the project API. Never authorization or admission. ScreenPatch cannot write it.

- `ScreenStorageCache` — required: house_used_bytes, capacity_bytes, reserve_bytes, protected_bytes, fallback_bytes, warm_bytes, ad_headroom_bytes

- `ScreenStorageExcludedPage` — required: page_id, reason

- `ScreenStorageForecast` — required: manifest_revision, fit, excluded_page_count, basis, received_at

  Approximate steady-state target selection (plan A.3 only, no transition) from the last reported capacity and the screen's desired manifest. Absent without a storage report, which consumers read as unknown. basis is reported_capacity. Recomputed when a storage report arrives and when the screen's manifest revision changes. Never authorization or admission.

- `ScreenStorageForecastDryRun` — required: fit, excluded_page_count, required_bytes, capacity_bytes, basis, received_at

  Pre-assignment fit dry run for one playlist. fit, excluded_page_count, and required_bytes come from plan A.3 target selection against the screen's last reported capacity (basis reported_capacity); there is no transition prediction and nothing is treated as local. required_bytes is the retained cost of every page of the playlist. fit unknown means the screen has no storage report, or the playlist's content references are not ready; required_bytes, capacity_bytes, and received_at are null then. received_at is the storage report's receipt time, and a report older than 24 hours is stale but still forecast from, exactly like Screen.storage_forecast. It is read-only metadata and never authorization or admission.

- `ScreenStorageForecastRequest` — required: playlist_id

  Body of the pre-assignment fit dry run. playlist_id must be a playlist of the authenticated project; another project's or an unknown id is not_found.

- `ScreenStoragePlan` — required: manifest_revision, fit, transition, required_bytes, target_bytes, excluded_pages

- `ScreenStorageShortfall` — required: at, fit, required_bytes, capacity_bytes, excluded_page_count

  Present while the player's reported fit is partial, transition_blocked, or none_fit, except a transition_blocked plan whose only blocking reason is anchor_unavailable: that is a failed download on a device with room, not a shortfall. at is when the condition began. It is set once when the condition begins and removed when it ends, each transition appending one project-only event, screen.storage_shortfall or screen.storage_shortfall_cleared. Those events are not delivered on GET /screen/events. Cleared with Screen.storage on identity reset, unpair, and archive. Read-only project health metadata; it does not change screen revision or manifest authority and is never authorization.

- `ScreenStorageTransfer` — required: new, refetch, repair, failed

- `ScreenStorageVolume` — required: total_bytes, available_bytes

- `ScreenSurfaceChangedDetails` — required: observed_at, surfaces

  details object on a durable screen.surface_changed project event. Pixels are not present. This type is not a runtime audience command and is not delivered on GET /screen/events.

- `ScreenTags` — required: none

  Fleet selector tags, 0 to 16 unique exact tags, each matching the media tag grammar. On ScreenPatch the array replaces the whole set and an empty array clears it; a duplicate or malformed tag is invalid_request. Changing tags bumps the screen revision (If-Match guards it) and appends screen.updated with details.tags; it never changes the manifest revision. Tags are never on the runtime manifest and never authorization.

- `ScreenTakeover` — required: playlist_id, until, set_at

  The playlist that wins over the schedule and the assignment until until, or until cleared when until is null.

- `ScreenTakeoverWrite` — required: playlist_id

- `ScreenToastAccepted` — required: expires_at

- `ScreenToastDetails` — required: level, text, duration_ms, expires_at

  details object on a durable screen.toast event. Colours are not present.

- `ScreenToastWrite` — required: level, text

- `ScreenUptime` — required: items

- `ScreeningResult` — required: checked_at

  Staff-only intake screening verdict. label is the Jev decision model's label (real) with its probability; unscreened is true when the model did not run (no key installed, timeout or provider error) and only the deterministic checks did. contains_credential is true when credential-shaped text was redacted. Submissions recorded before screening have no screening member.

- `ScreenshotCaptureID` — required: none

  Optional stage_, qa_, or development_ prefix, then shot_ and a random suffix. New suffixes use 16 alphanumeric characters excluding oOlL; retained legacy suffixes are accepted.

- `SelectorPreview` — required: primitive, selector, matched_count, candidates

- `SelectorPreviewInput` — required: primitive, selector

- `SelectorPreviewMedia` — required: media_id, filename, tags

- `ServiceClient` — required: id, project_id, name, capabilities, access, state, created_by, keys, secrets, created_at, updated_at, authenticated_requests, tokens_issued

- `ServiceClientChange` — required: none

- `ServiceClientKey` — required: kid, alg, thumbprint, jwk, added_at

- `ServiceClientList` — required: items

- `ServiceClientResult` — required: client

- `ServiceClientSecret` — required: id, hint, added_at

- `SessionKeySet` — required: keys

- `SignInPolicy` — required: password, passkey, passkey_enrollment_required, policy_revision, federated

  Verified person sign-in policy. Passive defaults preserve local sign-in. An enforced organization policy directs sign-in through its identity provider.

- `SignInResetAccepted` — required: status

  Identical for known and unknown addresses; no address, credential, project, or delivery status is returned.

- `SignInResetRequest` — required: email

- `StreamSource` — required: none

- `SupportConversation` — required: id, subject, created_at, updated_at, last_sequence, human_requested, unread_staff, closed

- `SupportConversationList` — required: items, next, unread_staff_total

- `SupportHours` — required: timezone, start, end, weekdays, excludes_bc_holidays

- `SupportMessage` — required: id, conversation_id, sequence, role, author, body, created_at

- `SupportMessageList` — required: items

- `SupportRead` — required: sequence

- `SupportStatus` — required: chat_allowed, available, staffed, hours, human_wait_seconds, docs_url

- `SupportSubmission` — required: conversation, message

- `SupportWrite` — required: body

- `Trigger` — required: id, kind, label, target, default_for_seconds, url, username, revision, created_at, updated_at

  A CAP alert trigger. Its password is returned only by create and rotate-secret.

- `TriggerAlert` — required: id, revision, state, status, msg_type, sender, identifier, until, screens, started_at, updated_at

  The trigger's latest alert, from the CAP message fields.

- `TriggerAlertScreens` — required: targeted, taken_over, cleared, skipped, failed, pending

  What the latest takeover change did. skipped counts screens without a default playlist, archived or gone, and, when clearing, screens another takeover has replaced since.

- `TriggerList` — required: items

- `TriggerReceipt` — required: outcome, trigger_id

- `TriggerTarget` — required: none

  Exactly one of tag and screen_ids.

- `TriggerWithPassword` — required: none

- `TriggerWrite` — required: label, target

- `UDPStreamSource` — required: protocol, group, port

- `UptimeDay` — required: day, online_s, offline_s, availability, coverage

  One UTC day. availability is online_s over online_s plus offline_s, null when nothing was recorded. coverage is the share of the day (of every reporting screen's day in a project row) with a recorded state, 0 to 1: a gap lowers coverage and is never offline time. intervals (screen rows) is how many recorded intervals touched the day; screens (project rows) how many screens had a recorded state; live marks a screen day that includes the open interval up to now.

- `UserEmailChangeAccepted` — required: none

- `UserEmailChangeRequest` — required: email, proof

  Requests a verified-email change using fresh proof. The current email stays effective while verification is pending. The old address receives a warning only, with no undo link.

- `VerifiedOrganizationDomain` — required: domain, organization_id, state, verified_at

- `VersionResponse` — required: version, commit, api_version, protocol_version

- `Webhook` — required: id, url, event_types, enabled, revision, status, created_at, updated_at

  A project webhook. Player log records (runtime.reported) are not delivered. Each matching project event is POSTed to url as the same JSON object GET /api/events returns, with headers ScreenRig-Webhook-Id, ScreenRig-Event-Id (the event cursor, stable across retries), User-Agent, and ScreenRig-Signature t=<unix seconds>,v1=<hex HMAC-SHA256 over "<t>.<raw body>" keyed with the secret string>. A 2xx answer within 10 seconds succeeds; any other status, a redirect (never followed), a timeout, or a connection or TLS failure is retried with exponential backoff (30 seconds doubling to one hour, jittered) for 24 hours and then failed. After three consecutive timeouts the attempt timeout drops to 5 seconds until a response arrives. A webhook has at most one attempt in flight and a project at most two, across all servers. Delivery is at least once and unordered; deduplicate on ScreenRig-Event-Id. Each attempt is metered like a listen-stream event (1 credit after the monthly 10,000 API/SSE allowance), and the same events that are free on the listen stream are free here: screen.*, runtime.*, application.event, playback.page_failed, and agent.command. An attempt refused before anything is sent (url_rejected, dns_failed) is not billed. payment_required and secret_unavailable back off but never count toward failing or auto-disable. status is failing while no delivery has succeeded since failing_since; after 72 hours of continuous failure the webhook is disabled (disabled_reason delivery_failures) and webhook.disabled is appended.

- `WebhookDelivery` — required: id, webhook_id, event_id, event_type, state, attempts, created_at

- `WebhookDeliveryAttempt` — required: attempt, at, status, error, duration_ms

- `WebhookDeliveryAttemptList` — required: items

- `WebhookDeliveryList` — required: items, next_cursor

- `WebhookEventTypes` — required: none

  Exact event types such as screen.online, or prefixes ending in .* such as screen.* (every type starting with screen.). webhook.test is never fanned out by a subscription; it is sent only by POST /api/webhooks/{id}/test.

- `WebhookList` — required: items

- `WebhookPatch` — required: none

- `WebhookWithSecret` — required: id, url, event_types, enabled, revision, status, created_at, updated_at, secret

  A Webhook plus its signing secret. Returned only by create and rotate-secret.

- `WebhookWrite` — required: url, event_types

- `X25519PublicJWK` — required: kty, crv, x


## Sitemap

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