Errors

The error format, every error code the API returns, and what to do about each one.

Every error has the same shape:

{
  "code": "INSUFFICIENT_SCOPE",
  "error": "This API key is missing the `write` scope. Edit the key in your workspace settings to add it.",
  "details": { "required": "write", "granted": ["read"] }
}
FieldDescription
codeMachine-readable code, UPPER_SNAKE_CASE. A stable contract: branch your logic on it.
errorHuman-readable message. It may be reworded: never parse it.
detailsOptional. Context that helps fix the request: faulty fields, the bound that was exceeded, ids.

A business refusal returns a 4xx, not a 500: it describes a request you can fix, and retrying it unchanged will never succeed.

Authentication, 401

CodeMeaningWhat to do
INVALID_API_KEYKey missing, malformed or unknown.Check the Authorization: Bearer mk_... header.
API_KEY_REVOKEDThe key was revoked.Create a new one in the workspace settings.
API_KEY_EXPIREDThe key's expiry date has passed.Create a new one; details.expiredAt says when.

A session cookie never works on the API, whatever its validity. 401 responses carry no X-RateLimit-* headers.

Payment required, 402

CodeMeaningWhat to do
PLAN_REQUIREDTurning on auto-draft or auto-reply on an AI label rule needs a subscription.Upgrade the workspace. Turning these features off always works.

Authorization, 403

CodeMeaningWhat to do
INSUFFICIENT_SCOPEThe key lacks a scope.details.required names it, details.granted lists what the key has.
OUTBOUND_SEND_NOT_ALLOWEDThe action makes email leave the workspace and the key does not have the Send emails permission.Create a key with Send emails ticked.
WORKSPACE_SUSPENDEDThe workspace is suspended.Reactivate the subscription.

OUTBOUND_SEND_NOT_ALLOWED is not a missing scope but a separate permission: write alone must not be enough to write to your customers, or to forward their mail elsewhere, if the key leaks. It is returned by POST /drafts/send, by every write on /auto-forward-rules, and by /ai-label-rules when a request sets autoReplyEnabled: true.

Not found, 404

CodeReturned by
EMAIL_NOT_FOUNDGET /emails/{emailId}, and label actions on an email.
NOT_FOUNDGET /emails/{emailId}/body, PATCH /emails/{emailId}, DELETE /emails/{emailId}.
DRAFT_NOT_FOUNDGET /drafts/{draftId}.
MAILBOX_NOT_FOUNDPOST /drafts, POST /drafts/send and POST /ai-label-rules/backfill, when mailboxId is unknown.
LABEL_NOT_FOUNDThe /labels/{labelId} endpoints, applying a label to an email, and POST /emails/bulk with a label action.
RULE_NOT_FOUNDThe /ai-label-rules/{ruleId} and /auto-forward-rules/{ruleId} endpoints.
VIEW_NOT_FOUNDThe /views/{viewId} endpoints.
FILTER_NOT_FOUNDThe /filters/{filterId} endpoints.

A resource that belongs to another workspace returns 404, not 403: a 403 would confirm that it exists. Treat EMAIL_NOT_FOUND and NOT_FOUND alike: both mean the email is not reachable with this key.

Invalid request, 400

CodeMeaning
VALIDATION_FAILEDInvalid body, parameter or filter tree.
INVALID_SENDER_ALIASThe from alias is not a verified send-as address of the mailbox.
MAILBOX_INACTIVEThe mailbox is disconnected: reconnect it in Maylee before sending.
NO_TRASH_FOLDERThe mailbox has no trash folder, so the email cannot be moved there.
INVALID_TAGThe label is an AI label: rename or delete it through its rule on /ai-label-rules.
REQUEST_ERRORThe request itself could not be read, typically malformed JSON.

On VALIDATION_FAILED, details names the faulty fields:

{
  "code": "VALIDATION_FAILED",
  "error": "Invalid email payload.",
  "details": {
    "mailboxId": ["Required"],
    "to": ["Required"],
    "body": ["Required"]
  }
}

Timeout, 408

CodeMeaning
QUERY_TIMEOUTThe query ran for more than 10 seconds.

The query was cancelled on the database side: it is not still running. Narrow the filters, lower limit, or target a single mailbox.

Conflict, 409

CodeMeaning
CONFLICTIdempotency-Key reused with a different body, or the original call is still in progress.
DUPLICATE_TAG_NAMEA label with this name already exists in the workspace.
DUPLICATE_RULEAn AI label with this name already exists in the workspace.
DUPLICATE_FILTERAn inbox filter with this type and value already exists (details.filterId names it).

For CONFLICT, generate a new key if you are describing a new operation, or retry in a moment if the original call is still running. Label names are unique per workspace, manual and AI labels alike.

Payload too large, 413

CodeMeaning
PAYLOAD_TOO_LARGEThe JSON body exceeds 256 KB.

Too many requests, 429

CodeMeaningWhat to do
RATE_LIMIT_EXCEEDEDToo many requests this minute.Wait Retry-After seconds.
QUOTA_EXCEEDEDThe monthly allowance is used up.Wait for the 1st of next month (UTC), or upgrade the plan.

See Rate limits and quotas.

Server error, 500

CodeMeaning
INTERNAL_ERRORUnexpected failure, including a refusal from the email provider on POST /drafts/send.

This is the only case where retrying the same request makes sense. On a send, nothing was sent and the idempotency key is released, so a retry with the same key sends again rather than replaying a failure. If a 500 persists on a request you believe is valid, report it with the time and the path you called.

On this page