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
| Method | Use it for | REST API | MCP server |
|---|---|---|---|
| Personal access token | Your own scripts and tools | Yes | No |
| OAuth 2.0 access token | An app that other users connect to their account | Yes, with the REST API audience | Yes, 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
- Sign in to the Reassign app.
- Go to Settings › Integrations › API.
- In New key, type a name. The name can have 100 characters or less.
- Choose Read only or Read and write.
- Click Create key.
- Copy the key from Your new key, and keep it in a safe place.
- Click I saved it.
The app shows the key one time only. Reassign keeps only a hash of the key. If you lose the key, revoke it and create a new key.
The API page also shows the base URL of the REST API in the Base URL field.
Access levels
| Access | Scopes of the key |
|---|---|
| Read only (the default) | events:read |
| Read and write | events: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
- Go to Settings › Integrations › API.
- In Your keys, find the key.
- 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
Bearerwith a capitalB. 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
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,
authis{ "via": "pat" }. trialEndsAtis an ISO 8601 instant in UTC, for example"2026-09-30T08:00:00.000Z", on a trial. It isnullon all other plans.emailis always in the response. It isnullwhen the account has no email address.auth.viais"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.
| Feature | Value |
|---|---|
| Grant types | authorization_code, refresh_token |
| Response type | code |
| PKCE | Required. S256 only. The server rejects plain. |
| Client registration | Dynamic (RFC 7591) |
| Token endpoint auth methods | none, client_secret_post |
| Resource indicators | Required for the REST API (RFC 8707) |
Endpoints
| Endpoint | Method | URL |
|---|---|---|
| Authorization server metadata (RFC 8414) | GET | https://reassign.app/.well-known/oauth-authorization-server |
| Protected resource metadata, REST API (RFC 9728) | GET | https://reassign.app/.well-known/oauth-protected-resource/api/v1 |
| Protected resource metadata, MCP (RFC 9728) | GET | https://reassign.app/.well-known/oauth-protected-resource/api/mcp |
| Authorization | GET | https://reassign.app/api/oauth/authorize |
| Token | POST | https://reassign.app/api/oauth/token |
| Registration | POST | https://reassign.app/api/oauth/register |
| Revocation (RFC 7009) | POST | https://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, nointrospection_endpoint, and nouserinfo_endpoint.
Resource and audience
Each access token is for one resource (the audience). A token works only on the API of its audience.
| Resource value | The token works on | The token does not work on |
|---|---|---|
https://reassign.app/api/v1 | The REST API | The MCP server |
https://reassign.app/api/mcp | The MCP server | The REST API |
- For a REST API token, send
resource=https://reassign.app/api/v1to 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_targetwith 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
- Register your client one time. You get a
client_id. - Make a PKCE code verifier. Make the
S256code challenge from it. - Send the user to the authorization endpoint.
- The user signs in and allows access on the Reassign consent screen.
- Reassign sends the user to your
redirect_uriwithcode,state, andiss. - Make sure that
stateis the value that you sent, and thatissis the issuer. - Exchange the code for tokens at the token endpoint in 60 seconds or less.
- Send the access token in the
Authorizationheader. - 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
httpson any host,httponly onlocalhost,127.0.0.1, or[::1], or a native app scheme (for examplecursor:orvscode:). The server rejectsjavascript:,data:,vbscript:,file:,blob:,about:, andfilesystem:.
- 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_postto get a client secret. All other values givenone, the default.
- Name
application_type- Type
- string
- Description
Optional.
web(the default) ornative.
Register a client
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_idandclient_id_issued_at(Unix seconds).redirect_uris,token_endpoint_auth_method,application_type, andclient_name(if you sent it).grant_types(["authorization_code", "refresh_token"]) andresponse_types(["code"]).client_secretandclient_secret_expires_at: 0, for aclient_secret_postclient 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.
| Parameter | Value |
|---|---|
response_type | code |
client_id | Your client ID |
redirect_uri | One of your registered URIs, exactly |
code_challenge | The base64url SHA-256 hash of your code verifier |
code_challenge_method | S256 |
scope | events:read, events:write, or both, with a space between them |
state | A random value that you check on return |
resource | https://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_urigetserror=access_denied. - If a parameter is not correct, your
redirect_urigetserroranderror_description. - If
client_idorredirect_uriis 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.
| Parameter | Value |
|---|---|
grant_type | authorization_code |
code | The code from your redirect_uri |
client_id | Your client ID |
client_secret | Your client secret, for a client_secret_post client only |
redirect_uri | The same redirect_uri that you sent to the authorization endpoint |
code_verifier | Your PKCE code verifier |
resource | The same resource that you sent to the authorization endpoint |
Exchange the code
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.
| Scope | Access |
|---|---|
events:read | Read your events, plans, and reflections. |
events:write | Read, create, change and delete events and other plan data. It includes events:read. |
- If you do not send
scope, you getevents:read events:write. events:readcovers all read calls.GET /api/v1/meneeds no scope.- The server ignores a scope value that it does not know.
- If the server knows no value in
scope, you getevents: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:readgetsscope, notvalidation, when the body is not correct.
Token lifetimes and refresh
| Token | Lifetime |
|---|---|
| Authorization code | 60 seconds |
| Access token | 3600 seconds (1 hour) |
| Refresh token | 30 days |
To get new tokens, send a POST request to the token endpoint with grant_type=refresh_token.
| Parameter | Value |
|---|---|
grant_type | refresh_token |
refresh_token | Your refresh token |
client_id | Your client ID (optional) |
client_secret | Your client secret, for a client_secret_post client only |
scope | Optional. A smaller set of scopes. You cannot add scopes. |
Refresh the tokens
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
resourcein 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 send | Result |
|---|---|
| The live refresh token | HTTP 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 refresh | HTTP 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 refresh | HTTP 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 family | HTTP status 400 with invalid_grant ("refresh token is no longer valid"). |
- In the 60-second window, the
scopeof 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_unavailableandretry-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
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_unavailableandretry-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.
| Action | Needs Reassign Pro (the 7-day free trial includes it) |
|---|---|
| Create a personal access token | Yes |
| Revoke a personal access token | No |
| Allow an app on the OAuth consent screen | Yes |
Call GET /api/v1/me | No |
| Call most other REST API routes | Yes |
| Call the MCP server | Yes |
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_description | Cause |
|---|---|
missing token | The request has no bearer token. |
invalid or expired token | The 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.
| Endpoint | Limit for each IP address |
|---|---|
POST /api/oauth/token | 600 requests per minute |
POST /api/oauth/register | 200 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. UseRetry-Afterwhen it is present.
For the REST API limits and all error codes, refer to Errors.