# OAuth API reference

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

OAuth sign-in, tokens and revocation. This group has 10 operations, each with its authentication, parameters, request body fields, request template and responses. The [API overview](https://screenrig.ai/docs/api.md) covers services, authentication, requests and retries, problem codes and versioning.

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

## 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 an opaque refresh credential (RFC 9700): every use returns a new pair; a request_id (16 to 200 characters) makes an exact retry return the same response while its credential and grant are live and the successor is still current, without an elapsed-time retry deadline.

Any other reuse of a superseded refresh token revokes the whole family (invalid_grant). A removed selected membership or project returns invalid_scope with error_description beginning "project_selection_inactive:"; the refresh credential is not consumed.

Discover live memberships with GET /api/projects, intersect them with the previously consented selection, and retry with projects= and a new request_id. Never end the refresh family for this selection error.

Requested scope narrowing applies independently to every project and to identity authority; an explicit scope without identity excludes identity authority. 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; projects selects up to 64 consented project IDs.

Omission selects all only when the set fits; otherwise the client must choose a subset. project_id selects one during the compatibility release. 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" |
| projects | optional | string | Space- or comma-separated project IDs; omit for all consented projects up to the configured cap (default 64). Cannot be combined with project_id. |
| project_id | optional | string |  |

#### Request template

~~~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; install_source (optional) says where it was installed from and is kept on the agent the approval creates.

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 |
| install_source | optional | string | Optional where the integration was installed from: the host and, after a slash, the marketplace (claude/screenrig, codex/screenrig, grok/screenrig), or a bare host such as npm. A missing or wrongly shaped value is ignored and never fails the request. pattern: "^[a-z][a-z0-9-]{0,31}(/[a-z0-9][a-z0-9._-]{0,63})?$" |

#### Request template

~~~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, another MCP resource whose protected resource metadata names this server, 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

~~~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. An opaque refresh credential (or a previous-release 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

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

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

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

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

~~~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. A member limited to screens does not connect agents, issue credentials or manage the project's people: outside_screen_scope (ask the project owner).


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

~~~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) |
| 403 | outside_screen_scope - the caller is limited to screens and the screen, screen folder, selector or audience the request names or reaches is outside them (docs/screen-scope.md). An id outside the scope and an id that does not exist get the same answer; errors[0].screen_scope carries the caller's scope, and the write is refused whole, never narrowed. | 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/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. A member limited to screens does not connect agents, issue credentials or manage the project's people: outside_screen_scope (ask the project owner).


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

~~~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) |
| 403 | outside_screen_scope - the caller is limited to screens and the screen, screen folder, selector or audience the request names or reaches is outside them (docs/screen-scope.md). An id outside the scope and an id that does not exist get the same answer; errors[0].screen_scope carries the caller's scope, and the write is refused whole, never narrowed. | Problem (application/problem+json) |
| default | Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value. | Problem (application/problem+json) |


## Sitemap

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