Errors

When a request fails, the Reassign REST API returns an HTTP status code and a JSON error body. Use the error code to find the cause.

Error format

All REST API errors have an error object. A rejected batch also has the result of each op. Refer to Batch requests.

  • Name
    error.code
    Type
    string
    Description

    A stable code for the type of error. Refer to Error codes.

  • Name
    error.message
    Type
    string
    Description

    A text for humans that tells what went wrong.

  • Name
    error.issues
    Type
    array
    Description

    Optional, on a validation error from a schema check. Up to 20 items { path, message, expected? }. Refer to "Validation errors (422)" below.

  • Name
    error.feature
    Type
    string
    Description

    Optional, on a permission error from the plan. The value is api: the account has no trial or Pro plan. The schema also allows calendar_sync, but every public route refuses an account with no plan first.

  • Name
    error.conflictingRows
    Type
    array
    Description

    Optional, on a stale undo row. Up to 50 rows that block the undo. Refer to Stale tokens.

  • Name
    error.conflictingRowsTruncated
    Type
    boolean
    Description

    Optional. true when more than 50 rows block the undo.

  • Name
    error.usage
    Type
    object
    Description

    Optional, when a category delete needs a reassignTo choice. The counts { events, inboxItems, trackers, calendars }. For an area, it also has projects: the count of its projects that are not archived. Refer to Delete a category that items use.

  • Name
    error.reassignTargets
    Type
    array
    Description

    Optional, when a category delete needs a reassignTo choice. The categories { id, name } that can take the links.

Use error.code in your code. Do not use the text of error.message to make decisions.

The API sends a detail field only when it has a value. In a rejected batch, the detail fields are only in the error of each row. The top-level error has only code and message.

401 Unauthorized

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

422 Unprocessable Content

{
  "error": {
    "code": "validation",
    "message": "`from`: date must be ISO \"YYYY-MM-DD\"",
    "issues": [
      {
        "path": ["from"],
        "message": "date must be ISO \"YYYY-MM-DD\"",
        "expected": "YYYY-MM-DD"
      }
    ]
  }
}

Error codes

CodeHTTP statusMeaning
batch_rejected400The server applied no op of a batch, and the failed ops have different codes. Refer to Batch requests.
unauthorized401The token is missing, not valid, expired, or revoked.
permission403The account does not have a trial or Pro plan. Or the target item does not allow the action, for example a park of a recurring event.
scope403The token does not have the necessary scope.
read_only403You cannot change this event or this field from Reassign. For example, the event is on a calendar that the user does not own. Or a task app owns the planned day of an Inbox block. Or a project change cannot move the linked task in its app.
not_found404The event, area, activity type, project, or day does not exist. Also a path or a method that no route serves.
conflict409The time, the name, or the id is already taken, or a short busy state blocks the write. A retry can work only for the busy state.
stale409An undo cannot apply, because a row that the write changed has a newer change. Do not retry.
validation422An argument is not correct: out of range, contradictory, or not complete.
rate_limited429You sent too many requests.
internal503A fault occurred on the server. A retry can work.
  • The API returns HTTP status 400 only with the code batch_rejected.
  • A POST /events or POST /backlog call that changed data never gets HTTP status 503. Refer to Batch requests.
  • unauthorized and batch_rejected occur only in the REST API. An op row in results never has these codes.
  • The schema also lists the code checkout_unlinked and the field unlinkedCheckout. Only the billing routes of the Reassign web app send them. A token or OAuth caller never gets them.

Notes on some codes

  • unauthorized: The response also has a WWW-Authenticate header. Refer to Authentication.
  • permission: If the account does not have a trial or Pro plan, the message is "Reassign needs an active subscription." The error also has feature: "api".
  • scope: A read needs events:read or events:write. A change needs events:write. Refer to Scopes.
  • conflict: An undo can meet a calendar sync that sends the same change now. The undo row then fails with conflict and the message "A calendar sync is sending this change now. Try the undo again in a moment." The undo changes nothing. Send it again after a short time.
  • conflict on POST /categories: A create or a rename uses the name of a different category of the same type. The message gives the name and the id of that category. Use that id, or select a different name. Do not retry.
  • conflict on POST /categories for a project: Two live projects cannot have the same name, also in different areas. The message is, for example, "A project named Launch exists in Work." A delete of a project that a task list or a Linear project feeds also fails with conflict, for example "Link the task list to a different project first." Link the list to a different project, or archive the project.
  • stale: Only POST /actions/undo sends this code. A row that the write changed has a newer change, or the token is from before the change stamps. The undo changes nothing. The row can have conflictingRows. Refer to Stale tokens.
  • internal: The API also uses this code for an unexpected server error. The message is then "Something went wrong on our side. Please try again."
  • rate_limited: Refer to Rate limits.
  • validation: The error can have issues. Refer to "Validation errors (422)" below. A category delete refused because it needs a reassignTo choice also has usage and reassignTargets.

Order of the checks

The API checks a request in this order. The first check that fails gives the error.

OrderCheckError
1The signature and the audience of an OAuth access token401 unauthorized
2The limit for each OAuth grant or personal access token429 rate_limited
3The token401 unauthorized
4The plan403 permission
5The events:write scope, on a route that is not GET403 scope
6The query and the body422 validation
7The events:read scope, on a GET route403 scope
  • The API counts a personal access token before it looks up the token. Thus, a personal access token that is not valid can get 429, not 401. An OAuth access token that is not valid gets 401 before the count. Refer to Rate limits.
  • A token with only events:read gets 403 scope on a write route, also when the body is not correct.
  • On a GET route, a query that is not correct gets 422 before the scope check.
  • GET /me needs no plan and no scope.

Removed routes

These routes do not exist now: POST /events/batch, PATCH /events/{id}, DELETE /events/{id}, and GET /reviews/{date}. The API answers them as a path that no route serves:

RequestAnswer
More than 600 requests in one minute for the same OAuth grant or personal access token429 rate_limited
No token, or a token that is not valid401 unauthorized
A valid token, but no trial or Pro plan403 permission
A valid token404 not_found, with the message "No API route has this path."
A request from the Reassign Chrome extension 1.1.x422 validation, with the message "Update the Reassign extension to 1.2.0."

Use POST /events for all event writes. GET /schedule gives the review of a confirmed day.

Validation errors (422)

Each route checks its query and its body against a schema. The API refuses a value in a different format. It does not change the value for you.

  • The message names the first bad field by its path. The format is `<path>`: <reason>, for example `ops`: Too small: expected array to have >=1 items.
  • The path body means the full body. The path query means the full query.
  • The reason text comes from the schema library. It can change. Use error.code, not the text.
  • error.issues lists up to 20 bad fields.
  • Each issue has path, message, and an optional expected. path is a list of keys and indexes. It starts at the root of the body or the query, with no body or query part.
  • expected names the expected type or format, for example number, uuid, YYYY-MM-DD, or YYYY-MM-DDTHH:MM. An issue about a relation between fields has no expected.
  • The message and issues of a schema check do not repeat the values that you sent.
  • These 422 errors have no issues: a repeated query parameter, a query parameter on a route that takes none, and a body that is not valid JSON.
  • A query value that is not a real date gets one of these messages:
    • `from`: date must be ISO "YYYY-MM-DD"
    • `from`: date is not a real calendar date (e.g. "2026-13-45" and "2027-02-29" are invalid)

The API refuses keys that it does not know:

CaseMessage
An unknown key at the top level of the bodyUnknown body key `<key>`.
An unknown key in a nested object`<path>`: Unknown key `<key>`.
An unknown query parameter, or a query parameter on a route that takes noneUnknown query parameter `<name>`.
The same query parameter two timesRepeated query parameter `<name>`.

An unknown key is the first error that the message shows, also when the request has other errors.

AI model errors (503)

POST /command sends the input to an AI model. When the model fails, the API returns HTTP status 503 with the code internal.

MessageCause
"The AI model did not respond. Try again."The model call failed.
"The AI model reply could not be read. Try again."The reply of the model is not a readable plan.

These 503 responses have no Retry-After header.

Request bodies

  • Send request bodies as JSON, with Content-Type: application/json.
  • A body that is not valid JSON gets HTTP status 422 with the message "The request body is not valid JSON."
  • An empty body reads as an empty object ({}).

Batch requests

A batch route takes a list of ops and returns one row for each op in results. The batch routes are POST /events, POST /schedule/plan, POST /actions/undo, and POST /backlog. POST /categories has the row lists areas and activityTypes, and projects when the request sent project ops. Each list has its own index values.

Result rows

FieldMeaning
indexThe position of the op in the request, from 0
status"ok", "error", or "skipped"
resultOn ok only: the result of the op
errorOn error only: { code, message }, and for a conflict also conflicts and nearestSlots. It can also have the optional detail fields. Refer to Error format.
reasonOn skipped only: why the server did not apply the op
warningsOptional on ok only. An event write can return ["classification_pending"] when queued AI classification can still fill eligible fields. A create keeps its kind. The write succeeded. Refer to Rows.
  • A conflict item is a span with name, and id for a stored event or batchIndex for an op of the same batch.
  • nearestSlots gives free spans near the requested time: up to three for event and backlog writes, and up to five for POST /schedule/plan. For their start times, refer to Two forms of a request.

Atomic and partial batches

  • A batch of POST /events, POST /backlog, POST /categories, or POST /schedule/plan is atomic by default. If one op fails, the server applies none of them. The failed ops have status: "error". The valid ops have status: "skipped".
  • With "partial": true, the server applies the ops that succeed. The failed ops have status: "error".
  • In POST /events, POST /backlog, and POST /categories, a bad op does not fail the full request. An unknown op, an unknown key, or a bad field becomes an error row with the code validation. The row message is one of these:
    • Invalid "<op>" op. `<path>`: <reason>, for example Invalid "create" op. `name`: Too small: …
    • Unknown op "<op>". Use one of: … for an unknown op value, or Unknown op undefined. Use one of: … for an object with no op
    • "Each op must be an object." for an item that is not an object, for example null, a string, or an array
  • Each of these rows also has error.issues. The paths start at the op.
  • Thus, the response always has the rows. But in an atomic batch, a bad op still rejects the batch. Refer to "Rejected batch" below.
  • For example, an atomic POST /categories request with a bad op writes nothing. It returns HTTP status 422 with a validation row for the bad op and skipped rows for the valid ops.
  • The request fails with HTTP status 422 and no rows only when the body itself is not correct. For example, ops is missing, ops has more than 50 items, areas is not an array, or the body has an unknown top-level key.
  • In POST /backlog, a schedule op obeys the rules of an event write. The span is 5 minutes to 168 hours. A span that overlaps a blocking event fails with conflict, conflicts, and nearestSlots. A non-blocking block can overlap. A park op earlier in the same batch makes its time free for a later schedule op.
  • In POST /schedule/plan, each request row is also checked alone. A bad row becomes an error row with the code validation. A taken exact start gives up to 5 nearestSlots.
  • POST /actions/undo is not atomic. Each token succeeds or fails alone.

Rejected batch

The server rejects a batch when one or more ops have status: "error" and no op has status: "ok". The server then applied nothing.

Failed opsHTTP statuserror.code
All failed ops have the same codeThe status of that code, for example 409 for conflict or 422 for validationThat code
The failed ops have different codes400batch_rejected
  • The body is { error, timezone?, results }. error has only the normal code and message. results shows the row of each op, with its detail fields.
  • For POST /categories, the body is { error, areas, activityTypes }, and projects when the request sent project ops.
  • When one op failed, error.message is the message of that op. Otherwise, it is "No op was applied; each failed op is in results."
  • For POST /categories, that message names each list that has a failed op: areas, activityTypes, or projects.
  • A rejected batch has no undoToken.
  • If one or more ops succeed, the response has HTTP status 200. Examine the row of each op.

400 Bad Request (rejected batch)

{
  "error": {
    "code": "batch_rejected",
    "message": "No op was applied; each failed op is in `results`."
  },
  "timezone": "Europe/Ljubljana",
  "results": [
    {
      "index": 0,
      "status": "error",
      "error": {
        "code": "conflict",
        "message": "The time overlaps an existing event.",
        "conflicts": [
          {
            "id": "b7f1c2a0-1d2e-4f3a-9b8c-5d6e7f801234",
            "name": "Team sync",
            "start": "2026-09-24T09:00",
            "end": "2026-09-24T10:30"
          }
        ],
        "nearestSlots": [
          { "start": "2026-09-24T11:00", "end": "2026-09-24T12:00" }
        ]
      }
    },
    {
      "index": 1,
      "status": "error",
      "error": {
        "code": "not_found",
        "message": "Event \"00000000-0000-4000-8000-00000000dead\" not found."
      }
    },
    {
      "index": 2,
      "status": "skipped",
      "reason": "Not applied — another op in this batch failed; fix the errors and resend."
    }
  ]
}

A write that changed data

The server never rejects a batch after its writes changed data. This is true for POST /events and POST /backlog.

  • The response has HTTP status 200. It has the undoToken when the server recorded the undo.
  • After the write, the server reads each changed item again for its row. If this read fails, the row has status: "error" and the code internal. For an unexpected fault, the message is "The change was saved, but the read-back failed. Read it again."
  • The server reads the rows after the full batch commits. Thus a later op in the batch can change the row of an earlier op.
  • Do not send the write again. Read the data again, for example with GET /schedule. To reverse the write, send the undoToken to POST /actions/undo.

Rate limits

The Vercel Firewall limits the requests from each IP address.

PathLimit for each IP address
/api/v1 (all REST API routes)2000 requests per minute
/api/oauth/token600 requests per minute
/api/oauth/register200 requests per 10 minutes
  • The app also limits each OAuth grant and each personal access token to 600 requests per minute. An OAuth grant is one app for one user. All the access tokens of the grant share one count.
    • The refusal is HTTP status 429 with the code rate_limited, the message "Too many requests.", and Retry-After: 60.
    • The API counts a personal access token before it looks up the token. Thus, a personal access token that is not valid can also get 429.
    • The limit applies to every /api/v1 route that needs a token, also to a path that no route serves.
    • The limit does not apply to the web session of the Reassign app. The MCP server has no limit for each token.
    • Each server instance counts alone. Thus, the limit is a soft limit. The real limit across all instances can be higher.
  • The endpoints in this reference have no limit for each user. Some routes of the Reassign app have a limit for each user. The MCP tool send_feedback also has one. Refer to Rate limits.
  • A limit of the firewall returns 429. Its body can differ from the normal error format. It can have no Retry-After header. Then, wait 60 seconds and retry.
  • A limit of the app returns 429 with the normal error format and the code rate_limited. It always has a Retry-After header.
  • Retry-After gives the window of the limit in seconds: 60 for a limit for each minute, 120 for a lock of the app, and 3600 for the feedback limits. When the limit gives no window, the value is 3600.
  • After a 429, wait and retry. Use Retry-After when it is present.

OAuth errors

The OAuth endpoints (/api/oauth/*) use the OAuth 2.0 error format (RFC 6749), not the REST API format. The default HTTP status is 400.

OAuth error

{
  "error": "invalid_grant",
  "error_description": "PKCE verification failed"
}
ErrorCause
invalid_requestA parameter is missing or not correct. For example, code_challenge is missing.
invalid_clientThe client_id or the client_secret is not correct.
invalid_grantThe code or the refresh token is not valid, is used, or is revoked. Or the redirect_uri or the PKCE check does not match.
invalid_scopeThe refresh request asks only for scopes that the grant does not have. The server removes extra scopes from a request that also asks for a granted scope.
invalid_targetThe resource value is not known.
unsupported_response_typeThe response_type is not code.
unsupported_grant_typeThe grant_type is not authorization_code or refresh_token.
invalid_client_metadataThe registration body is not correct.
invalid_redirect_uriA redirect URI in the registration body is not allowed.
access_deniedThe user denied access, or the account does not have Reassign Pro or the 7-day free trial.
temporarily_unavailableThe server cannot do the request now. Try again after the Retry-After time.
server_errorA fault occurred on the server.

For the OAuth flow, refer to Authentication.

Handle errors

StatusWhat to do
400The server applied no op of a batch (batch_rejected). Examine the row of each op in results. Then correct the request.
401Get a new token. For OAuth 2.0, refresh the access token. For a personal access token, make sure that you did not revoke it.
403For scope, get a token with the necessary scope. For permission, the account needs a trial or Pro plan (error.feature names the feature), or the action is not possible on that item. For read_only, change the item in its calendar or task app.
404Make sure that the ID, the date, or the path is correct.
409For conflict, select a different time, for example one of the nearestSlots. For a category name, use the category that has the name, or select a different name. For a project delete, link the task list or the Linear project to a different project first, or archive the project. For stale, do not retry.
422Correct the request. Do not send the same request again.
429Wait and retry. Use Retry-After when it is present.
503Wait, and then send the request again.
  • Send a request again only after a 429 or a 503.
  • Increase the wait time after each retry that fails. Stop after a small number of retries.
  • Do not send a request again after a different 4xx status. The same request gets the same error.

Get help

If you cannot correct an error, send an email to developers@reassign.dev. Include these details:

  • The HTTP method and the path
  • The time of the request, with the time zone
  • The error code and the error message