Authentication

Every request to the Reassign REST API must have a bearer token. Use a personal access token for your own scripts. Use OAuth 2.0 for an app that acts for other users.

The base URL of the REST API is https://reassign.app/api/v1.

Choose a method

MethodUse it forREST APIMCP server
Personal access tokenYour own scripts and toolsYesNo
OAuth 2.0 access tokenAn app that other users connect to their accountYes, with the REST API audienceYes, with the MCP audience

The MCP server accepts only OAuth 2.0 access tokens. It does not accept personal access tokens.

GET /schedule gives integrations.sources[].account, the account name at the calendar provider, only to a personal access token and the Reassign apps. A third-party OAuth 2.0 app does not get it.

Personal access tokens

A personal access token (a "key") gives access to your own account. Create it in the Reassign app.

Create a key

  1. Sign in to the Reassign app.
  2. Go to Settings › Integrations › API.
  3. In New key, type a name. The name can have 100 characters or less.
  4. Choose Read only or Read and write.
  5. Click Create key.
  6. Copy the key from Your new key, and keep it in a safe place.
  7. Click I saved it.

The API page also shows the base URL of the REST API in the Base URL field.

Access levels

AccessScopes of the key
Read only (the default)events:read
Read and writeevents:read events:write

Refer to Scopes for the access that each scope gives.

Key format

  • A key starts with rsk_live_.
  • 43 characters follow the prefix.
  • A key that you create in the app does not expire.

Revoke a key

  1. Go to Settings › Integrations › API.
  2. In Your keys, find the key.
  3. Click Revoke.

The app does not ask you to confirm. The key does not work after that. The Your keys list also shows the date that you created each key and the date of its last use.

Send the token

Send the token in the Authorization header of each request. Use the same header for a personal access token and for an OAuth 2.0 access token.

Header

Authorization: Bearer rsk_live_YOUR_TOKEN
  • Write Bearer with a capital B. The API does not accept other letter cases.
  • Keep a personal access token on your server. Do not put it in browser code or in a public repository.

To test your token, call GET /api/v1/me. This route shows who the token belongs to, the plan, the scopes, and the type of token. It works with all valid tokens on all plans, also with the plan value none.

Test your token

GET
/api/v1/me
curl "https://reassign.app/api/v1/me" \
  -H "Authorization: Bearer rsk_live_YOUR_TOKEN"

Response

{
  "id": "00000000-0000-4000-8000-000000000042",
  "email": "user@example.com",
  "plan": "pro",
  "trialEndsAt": null,
  "offer": {
    "kind": "entitled"
  },
  "subscription": {
    "provider": "web",
    "status": "active",
    "currentPeriodEndsAt": "2026-09-13T08:00:00.000Z",
    "cancelAtPeriodEnd": false
  },
  "emailSubscribed": true,
  "scopes": [
    "events:read",
    "events:write"
  ],
  "auth": {
    "via": "oauth",
    "clientId": "reassign-ios"
  }
}
  • The example shows an OAuth 2.0 token. For a personal access token, auth is { "via": "pat" }.
  • trialEndsAt is an ISO 8601 instant in UTC, for example "2026-09-30T08:00:00.000Z", on a trial. It is null on all other plans.
  • email is always in the response. It is null when the account has no email address.
  • auth.via is "pat" for a personal access token and "oauth" for an OAuth 2.0 access token.

OAuth 2.0

Use OAuth 2.0 when your app acts for other Reassign users. Reassign supports the authorization code flow with PKCE.

FeatureValue
Grant typesauthorization_code, refresh_token
Response typecode
PKCERequired. S256 only. The server rejects plain.
Client registrationDynamic (RFC 7591)
Token endpoint auth methodsnone, client_secret_post
Resource indicatorsRequired for the REST API (RFC 8707)

Endpoints

EndpointMethodURL
Authorization server metadata (RFC 8414)GEThttps://reassign.app/.well-known/oauth-authorization-server
Protected resource metadata, REST API (RFC 9728)GEThttps://reassign.app/.well-known/oauth-protected-resource/api/v1
Protected resource metadata, MCP (RFC 9728)GEThttps://reassign.app/.well-known/oauth-protected-resource/api/mcp
AuthorizationGEThttps://reassign.app/api/oauth/authorize
TokenPOSThttps://reassign.app/api/oauth/token
RegistrationPOSThttps://reassign.app/api/oauth/register
Revocation (RFC 7009)POSThttps://reassign.app/api/oauth/revoke
  • The metadata documents allow all origins (CORS).
  • Reassign does not publish an OpenID Connect document (/.well-known/openid-configuration).
  • The authorization server metadata has no jwks_uri, no introspection_endpoint, and no userinfo_endpoint.

Resource and audience

Each access token is for one resource (the audience). A token works only on the API of its audience.

Resource valueThe token works onThe token does not work on
https://reassign.app/api/v1The REST APIThe MCP server
https://reassign.app/api/mcpThe MCP serverThe REST API
  • For a REST API token, send resource=https://reassign.app/api/v1 to the authorization endpoint and to the token endpoint.
  • If you do not send resource, the server gives a token for the MCP server.
  • If you send a different value, the server returns the error invalid_target with HTTP status 400.
  • A refreshed token keeps the audience of the original token.
  • A client from dynamic registration can get tokens for the REST API and for the MCP server. The server does not limit a client to one audience.

Flow steps

  1. Register your client one time. You get a client_id.
  2. Make a PKCE code verifier. Make the S256 code challenge from it.
  3. Send the user to the authorization endpoint.
  4. The user signs in and allows access on the Reassign consent screen.
  5. Reassign sends the user to your redirect_uri with code, state, and iss.
  6. Make sure that state is the value that you sent, and that iss is the issuer.
  7. Exchange the code for tokens at the token endpoint in 60 seconds or less.
  8. Send the access token in the Authorization header.
  9. Before the access token expires, use the refresh token to get new tokens. Store the new refresh token each time.

Register a client

Send a POST request with a JSON body to the registration endpoint.

  • Name
    redirect_uris
    Type
    string[]
    Description

    Required. 1 to 16 URIs. Each URI can have 2048 characters or less. Use https on any host, http only on localhost, 127.0.0.1, or [::1], or a native app scheme (for example cursor: or vscode:). The server rejects javascript:, data:, vbscript:, file:, blob:, about:, and filesystem:.

  • Name
    client_name
    Type
    string
    Description

    Optional. The name that the consent screen shows. The server cuts it to 128 characters.

  • Name
    token_endpoint_auth_method
    Type
    string
    Description

    Optional. Send client_secret_post to get a client secret. All other values give none, the default.

  • Name
    application_type
    Type
    string
    Description

    Optional. web (the default) or native.

Register a client

POST
/api/oauth/register
curl https://reassign.app/api/oauth/register \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "My planner app",
    "redirect_uris": ["https://example.com/oauth/callback"]
  }'

The server returns HTTP status 201 with these fields:

  • client_id and client_id_issued_at (Unix seconds).
  • redirect_uris, token_endpoint_auth_method, application_type, and client_name (if you sent it).
  • grant_types (["authorization_code", "refresh_token"]) and response_types (["code"]).
  • client_secret and client_secret_expires_at: 0, for a client_secret_post client only.

The server does not return a registration_access_token. You cannot read, change, or delete a client after you register it.

Authorize the user

Send the user's browser to the authorization endpoint with these query parameters.

ParameterValue
response_typecode
client_idYour client ID
redirect_uriOne of your registered URIs, exactly
code_challengeThe base64url SHA-256 hash of your code verifier
code_challenge_methodS256
scopeevents:read, events:write, or both, with a space between them
stateA random value that you check on return
resourcehttps://reassign.app/api/v1 for the REST API

Authorization URL

https://reassign.app/api/oauth/authorize
  ?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https%3A%2F%2Fexample.com%2Foauth%2Fcallback
  &code_challenge=YOUR_CODE_CHALLENGE
  &code_challenge_method=S256
  &scope=events%3Aread%20events%3Awrite
  &state=YOUR_STATE
  &resource=https%3A%2F%2Freassign.app%2Fapi%2Fv1
  • The authorization request expires after 10 minutes. The user must allow access in this time.
  • If the user denies access, your redirect_uri gets error=access_denied.
  • If a parameter is not correct, your redirect_uri gets error and error_description.
  • If client_id or redirect_uri is not correct, the server does not redirect. It returns HTTP status 400 with a JSON error.

Exchange the code

Send a POST request to the token endpoint. The body can be JSON or form-encoded.

ParameterValue
grant_typeauthorization_code
codeThe code from your redirect_uri
client_idYour client ID
client_secretYour client secret, for a client_secret_post client only
redirect_uriThe same redirect_uri that you sent to the authorization endpoint
code_verifierYour PKCE code verifier
resourceThe same resource that you sent to the authorization endpoint

Exchange the code

POST
/api/oauth/token
curl https://reassign.app/api/oauth/token \
  -d grant_type=authorization_code \
  -d code=YOUR_CODE \
  -d client_id=YOUR_CLIENT_ID \
  --data-urlencode redirect_uri=https://example.com/oauth/callback \
  -d code_verifier=YOUR_CODE_VERIFIER \
  --data-urlencode resource=https://reassign.app/api/v1

Response

{
  "access_token": "YOUR_ACCESS_TOKEN",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "YOUR_REFRESH_TOKEN",
  "scope": "events:read events:write"
}
  • You can use a code one time only. A used code gets HTTP status 400 with invalid_grant ("authorization code has already been used").
  • A code expires after 60 seconds.
  • If the server cannot store the new refresh token, it returns HTTP status 503 with temporarily_unavailable. The code stays usable, so you can send the same request again.

Scopes

Reassign has two scopes.

ScopeAccess
events:readRead your events, plans, and reflections.
events:writeRead, create, change and delete events and other plan data. It includes events:read.
  • If you do not send scope, you get events:read events:write.
  • events:read covers all read calls. GET /api/v1/me needs no scope.
  • The server ignores a scope value that it does not know.
  • If the server knows no value in scope, you get events:read events:write.
  • A request without the necessary scope gets HTTP status 403 with the error code scope.
  • A write route checks the scope before it reads the body. Thus, a token with only events:read gets scope, not validation, when the body is not correct.

Token lifetimes and refresh

TokenLifetime
Authorization code60 seconds
Access token3600 seconds (1 hour)
Refresh token30 days

To get new tokens, send a POST request to the token endpoint with grant_type=refresh_token.

ParameterValue
grant_typerefresh_token
refresh_tokenYour refresh token
client_idYour client ID (optional)
client_secretYour client secret, for a client_secret_post client only
scopeOptional. A smaller set of scopes. You cannot add scopes.

Refresh the tokens

POST
/api/oauth/token
curl https://reassign.app/api/oauth/token \
  -d grant_type=refresh_token \
  -d refresh_token=YOUR_REFRESH_TOKEN \
  -d client_id=YOUR_CLIENT_ID
  • Each refresh returns a new access token and a new refresh token.
  • The new refresh token has a new 30-day lifetime.
  • The server ignores resource in a refresh request. The new tokens keep the audience of the old token.

Refresh token rotation

Each refresh spends the refresh token that you send. The server returns a new refresh token of the same family (the same sign-in).

  • Store the new refresh token after each refresh. The old refresh token is spent.
  • Before a refresh, read your storage again. Another process can have rotated the token already.
  • Send one refresh at a time for each sign-in, if you can.
You sendResult
The live refresh tokenHTTP status 200. You get a new refresh token, and the token that you sent is spent.
A spent token, 60 seconds or less after its refreshHTTP status 200. You get the live refresh token of the family. Thus, two parallel refreshes, or a retry after a lost response, are safe. The scope is the scope of the live token, cut to the scope of the token that you sent.
A spent token, more than 60 seconds after its refreshHTTP status 400 with invalid_grant ("refresh token is no longer valid"). The server revokes all refresh tokens of the family.
A token of a revoked familyHTTP status 400 with invalid_grant ("refresh token is no longer valid").
  • In the 60-second window, the scope of the response and the new access token follow the cut scope. Thus, a replay cannot widen a family that a refresh made narrower.
  • The server treats the reuse of a spent token after 60 seconds as a possible theft. Thus, it revokes the family.
  • After invalid_grant, do not try again. Send the user through the authorization flow again.
  • A refresh token from before rotation does not work. The server returns HTTP status 400 with invalid_grant. Send the user through the authorization flow again.
  • If the server cannot read the grant or store the new token, it returns HTTP status 503 with temporarily_unavailable and retry-after: 5. Keep your current refresh token, wait 5 seconds, and send the request again.

Revoke access

To revoke access from your app, send the access token or the refresh token to the revocation endpoint.

Revoke a token

POST
/api/oauth/revoke
curl https://reassign.app/api/oauth/revoke \
  -d token=YOUR_REFRESH_TOKEN
  • Revocation removes the full grant for that user and that client. All tokens of that client for that user stop working.
  • Revocation also revokes all refresh token families of that client for that user. A new consent does not make an old refresh token work again.
  • The endpoint does not need client authentication.
  • The endpoint returns HTTP status 200 with {}, also for an unknown token.
  • If the server cannot store the change, it returns HTTP status 503 with temporarily_unavailable and retry-after: 5. Send the request again.

A user can also revoke your app in the Reassign app. The path is Settings › Integrations › Authorized apps › Revoke.

Plan requirement

Most requests need Reassign Pro. The 7-day free trial includes it.

ActionNeeds Reassign Pro (the 7-day free trial includes it)
Create a personal access tokenYes
Revoke a personal access tokenNo
Allow an app on the OAuth consent screenYes
Call GET /api/v1/meNo
Call most other REST API routesYes
Call the MCP serverYes

Without Reassign Pro or the trial, the REST API returns HTTP status 403 with the error code permission and feature: "api". The MCP server returns HTTP status 403 with the JSON-RPC code -32600 and data: { "code": "permission", "feature": "mcp" }.

Authentication errors

If the token is missing, not valid, or expired, the REST API returns HTTP status 401.

401 response

HTTP/1.1 401 Unauthorized
Content-Type: application/json
WWW-Authenticate: Bearer error="invalid_token", error_description="missing token", resource_metadata="https://reassign.app/.well-known/oauth-protected-resource/api/v1"

{ "error": { "code": "unauthorized", "message": "Authentication required." } }

The error_description in the WWW-Authenticate header has one of these values:

error_descriptionCause
missing tokenThe request has no bearer token.
invalid or expired tokenThe token is not valid, is expired, is revoked, or is for a different audience.

The resource_metadata value gives the URL of the protected resource metadata. An OAuth 2.0 client can use it to find the authorization server.

Rate limits of the OAuth endpoints

The Vercel Firewall limits the requests from each IP address.

EndpointLimit for each IP address
POST /api/oauth/token600 requests per minute
POST /api/oauth/register200 requests per 10 minutes
  • When a client goes above a limit, the API returns 429. The body can differ from the OAuth error format.
  • After a 429, wait and retry. Use Retry-After when it is present.

For the REST API limits and all error codes, refer to Errors.