API reference
OAuth and device login
How a hosted MCP client connects without a pasted key, and how the CLI signs in with a code.
Two flows need no key to start. The OAuth 2.1 authorization server of /v1/mcp lets a hosted client register itself, send the person to the console to approve, and exchange a code (with PKCE) for an access token and a refresh token; the discovery documents are at /.well-known/oauth-authorization-server and /.well-known/oauth-protected-resource/v1/mcp. The device login is what ry login does: it asks for a code, the person confirms it in the console, and the CLI receives a key.
Scopes of the OAuth server: mcp and offline_access. There are no client secrets; every client is a public client bound to PKCE.
Endpoints
Section titled “Endpoints”POST /v1/oauth/register: Registers a public OAuth client (dynamic client registration) and answers its client id. No secretGET /v1/oauth/authorize: Starts an authorization: answers a redirect into the console, where the person approvesPOST /v1/oauth/token: Exchanges an authorization code (with the PKCE verifier) for an access token and a refresh token, or rotates a refresh tokenPOST /v1/oauth/consent/lookup: The console reads a pending authorization to show itPOST /v1/oauth/consent/decide: The console records the person’s decisionPOST /v1/device/code: Starts a device login: answers a device code and a user code to confirm in the consolePOST /v1/device/token: The CLI polls here until the login was confirmed, then receives a key
POST /v1/oauth/register
Section titled “POST /v1/oauth/register”Registers a public OAuth client (dynamic client registration) and answers its client id. No secret.
Auth: No API key: this is an entrance of a login or OAuth flow.
Request body
Section titled “Request body”JSON, the shape OAuthClientRegistration of the contract.
| Field | Type | Required | Description |
|---|---|---|---|
redirect_uris |
array of string | required | 1 to 10 items. |
client_name |
string | optional | 1 to 120 characters. |
token_endpoint_auth_method |
string | optional | at most 40 characters. |
grant_types |
array of string | optional | 0 to 4 items. |
response_types |
array of string | optional | 0 to 4 items. |
scope |
string | optional | at most 200 characters. |
Example request
Section titled “Example request”curl -X POST "https://api.rytiv.app/v1/oauth/register" \ -H "Content-Type: application/json" \ -d '{"redirect_uris":["<redirect_uri>"]}'Response
Section titled “Response”201 with a JSON object carrying client_id, client_name, redirect_uris, grant_types, response_types, token_endpoint_auth_method, client_id_issued_at.
GET /v1/oauth/authorize
Section titled “GET /v1/oauth/authorize”Starts an authorization: answers a redirect into the console, where the person approves.
Auth: No API key: this is an entrance of a login or OAuth flow.
Example request
Section titled “Example request”curl -X GET "https://api.rytiv.app/v1/oauth/authorize"Response
Section titled “Response”302 with a JSON body.
POST /v1/oauth/token
Section titled “POST /v1/oauth/token”Exchanges an authorization code (with the PKCE verifier) for an access token and a refresh token, or rotates a refresh token.
Auth: No API key: this is an entrance of a login or OAuth flow.
Example request
Section titled “Example request”curl -X POST "https://api.rytiv.app/v1/oauth/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "grant_type=authorization_code" \ --data-urlencode "code=<code>" \ --data-urlencode "code_verifier=<verifier>" \ --data-urlencode "client_id=<client id>" \ --data-urlencode "redirect_uri=<redirect uri>"Response
Section titled “Response”A JSON body on success.
POST /v1/oauth/consent/lookup
Section titled “POST /v1/oauth/consent/lookup”The console reads a pending authorization to show it.
Auth: API key with full access (a key with scopes cannot call this route). Refused for every agent token (agent-token-credential).
Request body
Section titled “Request body”JSON, the shape OAuthConsentLookupRequest of the contract.
| Field | Type | Required | Description |
|---|---|---|---|
requestId |
string | required | 1 to 120 characters. |
Example request
Section titled “Example request”curl -X POST "https://api.rytiv.app/v1/oauth/consent/lookup" \ -H "Authorization: Bearer $RYTIV_TOKEN" \ -H "Content-Type: application/json" \ -d '{"requestId":"<requestId>"}'Response
Section titled “Response”200 with a JSON body.
Errors
Section titled “Errors”| Status | Code | Meaning |
|---|---|---|
| 400 | bad-request |
The request is malformed or a field is out of range. error names every field; issues lists them. |
| 401 | unauthenticated |
No API key, or one that does not work. |
| 403 | token-scope |
The key’s scopes do not cover this route. |
| 404 | not-found |
Not found, or not yours. An object of another organization answers like one that does not exist. |
| 409 | not-answerable |
POST /v1/oauth/consent/decide
Section titled “POST /v1/oauth/consent/decide”The console records the person’s decision.
Auth: API key with full access (a key with scopes cannot call this route). Refused for every agent token (agent-token-credential).
Request body
Section titled “Request body”JSON, the shape OAuthConsentDecideRequest of the contract.
| Field | Type | Required | Description |
|---|---|---|---|
requestId |
string | required | 1 to 120 characters. |
approve |
boolean | required |
Example request
Section titled “Example request”curl -X POST "https://api.rytiv.app/v1/oauth/consent/decide" \ -H "Authorization: Bearer $RYTIV_TOKEN" \ -H "Content-Type: application/json" \ -d '{"requestId":"<requestId>","approve":true}'Response
Section titled “Response”200 with a JSON object carrying redirectUrl.
Errors
Section titled “Errors”| Status | Code | Meaning |
|---|---|---|
| 400 | bad-request |
The request is malformed or a field is out of range. error names every field; issues lists them. |
| 401 | unauthenticated |
No API key, or one that does not work. |
| 403 | token-scope |
The key’s scopes do not cover this route. |
| 404 | not-found |
Not found, or not yours. An object of another organization answers like one that does not exist. |
| 409 | not-answerable |
POST /v1/device/code
Section titled “POST /v1/device/code”Starts a device login: answers a device code and a user code to confirm in the console.
Auth: No API key: this is an entrance of a login or OAuth flow.
Request body
Section titled “Request body”JSON, the shape DeviceCodeRequest of the contract.
| Field | Type | Required | Description |
|---|---|---|---|
client |
string | optional | 1 to 120 characters. |
version |
string | optional | 1 to 40 characters. |
Example request
Section titled “Example request”curl -X POST "https://api.rytiv.app/v1/device/code" \ -H "Content-Type: application/json" \ -d '{"client":"<client>"}'Response
Section titled “Response”201 with a JSON object carrying deviceCode, userCode, verificationUri, verificationUriComplete, expiresIn, interval.
Errors
Section titled “Errors”| Status | Code | Meaning |
|---|---|---|
| 400 | bad-request |
The request is malformed or a field is out of range. error names every field; issues lists them. |
POST /v1/device/token
Section titled “POST /v1/device/token”The CLI polls here until the login was confirmed, then receives a key.
Auth: No API key: this is an entrance of a login or OAuth flow.
Request body
Section titled “Request body”JSON, the shape DeviceTokenRequest of the contract.
| Field | Type | Required | Description |
|---|---|---|---|
deviceCode |
string | required | 1 to 200 characters. |
Example request
Section titled “Example request”curl -X POST "https://api.rytiv.app/v1/device/token" \ -H "Content-Type: application/json" \ -d '{"deviceCode":"<deviceCode>"}'Response
Section titled “Response”200 with a JSON object carrying token, org.