Skip to content
On this site

OAuth

The authorization server's routes, and the grants a team has given. For how they fit together, start with the OAuth guide.

Three credentials

What each OAuth route is authorised by
RoutesAuthorization
/oauth/register, /oauth/authorize, /oauth/token, /oauth/revokeNone. A public client calls them before it holds anything, so the samples send no Authorization header.
/oauth/register/{client_id}The registration access token, Bearer rkor_. An API key is refused.
/oauth/grantsA full_access rk_ key. An OAuth access token (rko_) is refused, whatever its scopes.

Two error shapes

  • /oauth/token, /oauth/revoke and /oauth/register answer the standard OAuth shape, { error, error_description } (RFC 6749 §5.2 and RFC 7591 §3.2.2), because that is what an OAuth library parses.
  • /oauth/grants answers the Rasket error body every other endpoint uses.

Both vocabularies are listed on Errors.

Endpoints

POST /oauth/register

Dynamic client registration (RFC 7591). Unauthenticated; do it once per app.

Body

Body
FieldTypeDescription
redirect_uris*string[]At most 10. https URLs, or loopback-address URLs for a native app (RFC 8252 §7.3). Matched exactly at authorize time.
client_name*stringShown to the person on the consent page.
scopestringSpace-separated scopes the client may ask for, from the catalogue on the OAuth guide. An authorize request that names none asks for these.
client_uristringAn https link to the app, shown on the consent page.
logo_uristringAn https image, shown on the consent page.
5 more fields (grant_types, response_types, token_endpoint_auth_method, software_id, software_version)
Body, less common
FieldTypeDescription
grant_typesstring[]authorization_code and refresh_token, which is also the default.
response_typesstring[]Only ["code"].
token_endpoint_auth_methodstringOnly none: every client is a public client.
software_idstringYour own identifier for the app, stored and returned.
software_versionstringYour own version string, stored and returned.
curl -X POST "https://api.rasket.com/oauth/register" \
  -H "User-Agent: acme-billing/1.0" \
  -H "Content-Type: application/json" \
  -d '{
  "client_name": "Acme Invoices",
  "redirect_uris": ["https://invoices.acme.example/rasket/callback"],
  "scope": "emails:send emails:read"
}'

Response 201

{
  "client_id": "rkoc_7Fq2Lm9pXw3Kd8Vn1Zt5Rb4C",
  "client_id_issued_at": 1789205400,
  "registration_client_uri": "/oauth/register/rkoc_7Fq2Lm9pXw3Kd8Vn1Zt5Rb4C",
  "client_name": "Acme Invoices",
  "redirect_uris": ["https://invoices.acme.example/rasket/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none",
  "scope": "emails:send emails:read",
  "registration_access_token": "rkor_…"
}
  • registration_access_token is shown exactly once. Store it: it is the only credential for reading, replacing or deleting this client.
  • Public clients only. A token_endpoint_auth_method other than none, or response_types other than code, is 400 invalid_client_metadata; a redirect URI that is not https or loopback is 400 invalid_redirect_uri.
  • At most 10 registrations per IP address per hour and 1,000 a day across the platform; past either, 429.
  • Errors from this route are RFC 7591's { error, error_description }, not the Rasket error body.

GET /oauth/authorize

Send the person's browser here. It comes back to your redirect URI with a code.

Query parameters

Query parameters
FieldTypeDescription
response_type*stringAlways code.
client_id*stringYour rkoc_ identifier.
redirect_uri*stringExactly one of the client's registered redirect URIs.
code_challenge*stringBASE64URL(SHA256(code_verifier)): 43 characters.
code_challenge_method*stringAlways S256. plain is refused.
state*stringAn unguessable value you check when the browser comes back. Returned unchanged; at most 512 characters.
scopestringSpace-separated. The client's registered scopes when omitted.
1 more field (resource)
Query parameters, less common
FieldTypeDescription
resourcestringAn RFC 8707 resource indicator. When sent, it must name this API.
curl -X GET "https://api.rasket.com/oauth/authorize?response_type=code&client_id=rkoc_7Fq2Lm9pXw3Kd8Vn1Zt5Rb4C&redirect_uri=https%3A%2F%2Finvoices.acme.example%2Frasket%2Fcallback&scope=emails%3Asend+emails%3Aread&state=af0ifjsldkj&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256" \
  -H "User-Agent: acme-billing/1.0"
  • This is a URL to open in the person's browser, not a call your server makes: the 302 goes to our consent page, which asks them to sign in, pick one team and approve the scopes. Server-side, fetch and requests would follow it to a login page.
  • Approval redirects to redirect_uri?code=rkc_…&state=…. Refusal, or any other failure, redirects there with error, error_description and your state.
  • An unknown or disabled client_id, or a redirect_uri the client never registered, never redirects: it answers 400 with an error page, because sending the browser to an unverified address is how codes leak (RFC 6749 §4.1.2.1).

POST /oauth/token

Swap an authorization code for tokens, or rotate a refresh token.

Body

Body
FieldTypeDescription
grant_type*stringauthorization_code or refresh_token.
client_id*stringYour client identifier.
codestringauthorization_code only: the rkc_ code from the redirect.
redirect_uristringauthorization_code only: the same URI the authorize request named.
code_verifierstringauthorization_code only: the verifier whose hash you sent as the challenge.
refresh_tokenstringrefresh_token only: the rkr_ token.
1 more field (scope)
Body, less common
FieldTypeDescription
scopestringrefresh_token only: a narrower set. It can never widen the grant.
curl -X POST "https://api.rasket.com/oauth/token" \
  -H "User-Agent: acme-billing/1.0" \
  -H "Content-Type: application/json" \
  -d '{
  "grant_type": "authorization_code",
  "client_id": "rkoc_7Fq2Lm9pXw3Kd8Vn1Zt5Rb4C",
  "code": "rkc_…",
  "redirect_uri": "https://invoices.acme.example/rasket/callback",
  "code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
}'

Response 200

{
  "access_token": "rko_…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "rkr_…",
  "scope": "emails:send emails:read"
}
  • The body may be application/x-www-form-urlencoded — what a conforming OAuth library sends — or JSON, as shown.
  • A refresh returns a new pair and revokes the refresh token you presented. Presenting a code, or a rotated refresh token, a second time revokes the whole grant and every token under it.
  • Errors are RFC 6749 §5.2's { error, error_description }: 400 with invalid_request, invalid_grant, unauthorized_client, unsupported_grant_type or invalid_scope, and 401 invalid_client for an unknown or disabled client.
  • The response carries Cache-Control: no-store.

POST /oauth/revoke

RFC 7009. Sign a person out of your app without revoking the grant.

Body

Body
FieldTypeDescription
token*stringThe access or refresh token to revoke.
client_id*stringThe client the token was issued to.
1 more field (token_type_hint)
Body, less common
FieldTypeDescription
token_type_hintstringaccess_token or refresh_token. Accepted and ignored.
curl -X POST "https://api.rasket.com/oauth/revoke" \
  -H "User-Agent: acme-billing/1.0" \
  -H "Content-Type: application/json" \
  -d '{
  "token": "rkr_…",
  "client_id": "rkoc_7Fq2Lm9pXw3Kd8Vn1Zt5Rb4C"
}'
  • An access token is revoked alone. A refresh token takes its whole family with it, and every access token of the same grant.
  • The grant itself stays live. Only the team revoking it, or the client being deleted, ends a grant.
  • A token that is unknown, expired, already revoked or another client's still answers 200 with an empty body, as RFC 7009 §2.2 requires.
  • Form-encoded or JSON, as for the token endpoint.

GET /oauth/register/{client_id}

RFC 7592. The metadata stored for your client.

Path parameters

Path parameters
FieldTypeDescription
client_id*stringThe rkoc_ identifier registration returned.

Headers

Headers
FieldTypeDescription
Authorization*stringBearer rkor_… — the registration access token, shown once when the client was registered. An API key is refused.
curl -X GET "https://api.rasket.com/oauth/register/rkoc_7Fq2Lm9pXw3Kd8Vn1Zt5Rb4C" \
  -H "Authorization: Bearer $RASKET_REGISTRATION_TOKEN" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "client_id": "rkoc_7Fq2Lm9pXw3Kd8Vn1Zt5Rb4C",
  "client_id_issued_at": 1789205400,
  "registration_client_uri": "/oauth/register/rkoc_7Fq2Lm9pXw3Kd8Vn1Zt5Rb4C",
  "client_name": "Acme Invoices",
  "redirect_uris": ["https://invoices.acme.example/rasket/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none",
  "scope": "emails:send emails:read"
}
  • A missing, malformed or unknown registration token is 401 with WWW-Authenticate: Bearer error="invalid_token".

PUT /oauth/register/{client_id}

RFC 7592. A full replacement: a field you leave out is cleared.

Path parameters

Path parameters
FieldTypeDescription
client_id*stringThe rkoc_ identifier registration returned.

Headers

Headers
FieldTypeDescription
Authorization*stringBearer rkor_… — the registration access token, shown once when the client was registered. An API key is refused.

Body

Body
FieldTypeDescription
redirect_uris*string[]At most 10. https URLs, or loopback-address URLs for a native app (RFC 8252 §7.3). Matched exactly at authorize time.
client_name*stringShown to the person on the consent page.
scopestringSpace-separated scopes the client may ask for, from the catalogue on the OAuth guide. An authorize request that names none asks for these.
client_uristringAn https link to the app, shown on the consent page.
logo_uristringAn https image, shown on the consent page.
5 more fields (grant_types, response_types, token_endpoint_auth_method, software_id, software_version)
Body, less common
FieldTypeDescription
grant_typesstring[]authorization_code and refresh_token, which is also the default.
response_typesstring[]Only ["code"].
token_endpoint_auth_methodstringOnly none: every client is a public client.
software_idstringYour own identifier for the app, stored and returned.
software_versionstringYour own version string, stored and returned.
curl -X PUT "https://api.rasket.com/oauth/register/rkoc_7Fq2Lm9pXw3Kd8Vn1Zt5Rb4C" \
  -H "Authorization: Bearer $RASKET_REGISTRATION_TOKEN" \
  -H "User-Agent: acme-billing/1.0" \
  -H "Content-Type: application/json" \
  -d '{
  "client_name": "Acme Invoices",
  "redirect_uris": ["https://invoices.acme.example/rasket/callback", "https://invoices.acme.example/rasket/callback-eu"],
  "scope": "emails:send emails:read"
}'

Response 200

{
  "client_id": "rkoc_7Fq2Lm9pXw3Kd8Vn1Zt5Rb4C",
  "client_id_issued_at": 1789205400,
  "registration_client_uri": "/oauth/register/rkoc_7Fq2Lm9pXw3Kd8Vn1Zt5Rb4C",
  "client_name": "Acme Invoices",
  "redirect_uris": ["https://invoices.acme.example/rasket/callback", "https://invoices.acme.example/rasket/callback-eu"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none",
  "scope": "emails:send emails:read"
}
  • The registration access token is not rotated: a client has nowhere to learn a new one.

DELETE /oauth/register/{client_id}

RFC 7592. Revokes every grant the client holds, on every team.

Path parameters

Path parameters
FieldTypeDescription
client_id*stringThe rkoc_ identifier registration returned.

Headers

Headers
FieldTypeDescription
Authorization*stringBearer rkor_… — the registration access token, shown once when the client was registered. An API key is refused.
curl -X DELETE "https://api.rasket.com/oauth/register/rkoc_7Fq2Lm9pXw3Kd8Vn1Zt5Rb4C" \
  -H "Authorization: Bearer $RASKET_REGISTRATION_TOKEN" \
  -H "User-Agent: acme-billing/1.0"
  • Every grant is revoked with revoked_reason client, and every token under them stops working at once.
  • Each team still sees its grant, revoked, in GET /oauth/grants: the history is kept.

GET /oauth/grants

Every app this team has connected, live and revoked, newest first.

Query parameters

Query parameters
FieldTypeDescription
limitintegerHow many items to return, 1–100. Defaults to 20.
afterstringReturn the page that follows this item ID. Mutually exclusive with before.
beforestringReturn the page that precedes this item ID. Mutually exclusive with after.
curl -X GET "https://api.rasket.com/oauth/grants?limit=20" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "id": "0199e0a4-6b2d-7c1e-8f3a-2d5b9c7e1a40",
      "client_id": "rkoc_7Fq2Lm9pXw3Kd8Vn1Zt5Rb4C",
      "scopes": ["emails:send", "emails:read"],
      "created_at": "2026-09-12T09:30:00.000Z",
      "revoked_at": null,
      "revoked_reason": null,
      "client": {
        "name": "Acme Invoices",
        "logo_uri": null
      }
    }
  ]
}
  • A revoked grant stays in the list with revoked_at and revoked_reasonuser, client, code_reuse, refresh_reuse or operator — so an app never silently disappears.
  • Needs a full_access key. An OAuth access token is refused here whatever its scopes: no scope reaches the grants, so a connected app cannot see or manage connected apps.

DELETE /oauth/grants/{oauth_grant_id}

Disconnect an app: the grant and every token issued under it.

Path parameters

Path parameters
FieldTypeDescription
oauth_grant_id*stringThe grant's ID.
curl -X DELETE "https://api.rasket.com/oauth/grants/0199e0a4-6b2d-7c1e-8f3a-2d5b9c7e1a40" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "oauth_grant",
  "id": "0199e0a4-6b2d-7c1e-8f3a-2d5b9c7e1a40",
  "revoked_at": "2026-09-12T14:05:12.000Z",
  "revoked_reason": "user"
}
  • The app's next call with any of its tokens is 401 missing_api_key.
  • Idempotent: revoking a revoked grant answers the revocation it already has.
  • Needs a full_access key; an OAuth access token is refused.