Error Codes

How the Blue GraphQL API reports errors, the safe vs. masked exposure rules, and a complete reference of every error code.


When a Blue GraphQL operation fails, the response contains a top-level errors array instead of (or alongside) data. Each entry carries a human-readable message and a machine-readable code at extensions.code. Switch on extensions.code in your client — never parse the message string, which is wording that can change.

Blue throws 123 intentional error codes from api/src/lib/errors.ts. They are listed in full below, grouped by area. Two codes are not in that file but you will still see them: BAD_USER_INPUT (input validation) and INTERNAL_SERVER_ERROR (the masked code applied to unexpected server failures — see Safe vs. masked errors).

Error response shape

{
  "errors": [
    {
      "message": "Todo was not found.",
      "path": ["updateTodo"],
      "extensions": {
        "code": "TODO_NOT_FOUND"
      }
    }
  ],
  "data": null
}

Each error object includes:

  • message — a human-readable description. For display only; do not branch on it.
  • extensions.code — the stable, machine-readable code. This is what you handle programmatically.
  • path — the response path of the field that failed (e.g. the mutation name).
  • extensions.data — present only on some codes; carries structured detail (see below).

A GraphQL response can succeed partially: data may hold results for the fields that resolved while errors describes the ones that didn’t. Always check errors even when data is non-null.

Structured error detail (extensions.data)

Some errors attach a structured payload so your client can react precisely instead of showing a generic toast.

BAD_USER_INPUT

Thrown for input that fails validation (Blue’s UserInputError). The validation detail is carried under extensions.data:

{
  "errors": [
    {
      "message": "Invalid input",
      "extensions": {
        "code": "BAD_USER_INPUT",
        "data": { "field": "email", "reason": "must be a valid email address" }
      }
    }
  ]
}

The data shape varies by what was validated — treat it as an opaque object keyed by the offending input. FORMULA_VALIDATION_ERROR is a specialization of this error for name-formula validation and uses the same BAD_USER_INPUT mechanism with its own code.

PLAN_LIMIT_REACHED

This is the single signal for anything blocked by your plan or tier — over a resource cap, a feature your plan doesn’t include, a locked workspace, or a tier-level rate limit. There are no per-resource limit codes; route on the kind inside extensions:

{
  "errors": [
    {
      "message": "You've reached the workspace limit for your plan.",
      "extensions": {
        "code": "PLAN_LIMIT_REACHED",
        "kind": "over_limit",
        "resource": "workspaces",
        "current": 10,
        "limit": 10
      }
    }
  ]
}
FieldWhen presentMeaning
kindalwaysOne of over_limit, missing_feature, locked, rate_limited
resourceover_limit, rate_limitedThe resource at its cap, e.g. workspaces, records, users
currentover_limitCurrent count
limitover_limitThe tier cap for that resource
featuremissing_featureThe feature-flag key the plan is missing
cooldownEndsAtrate_limitedISO timestamp when the action becomes available again
Plan limits vs. permissions

PLAN_LIMIT_REACHED means “your plan can’t do this — upgrade.” FORBIDDEN means “your role isn’t allowed to do this.” They are deliberately distinct so a client can show an upgrade prompt in one case and an access message in the other.

Safe vs. masked errors

In production Blue runs every error through a formatter (api/src/lib/format-error.ts) before returning it. The rule is simple:

  • An error is safe when the server intentionally threw a GraphQLError carrying an explicit code other than INTERNAL_SERVER_ERROR. Every code in this page’s tables is thrown that way, so its real message and code reach the client unchanged. There is no special-casing of *_NOT_FOUND — those are safe only because they’re thrown as coded GraphQLErrors, exactly like every other code here.
  • An error is unsafe when it wasn’t thrown intentionally — an uncaught exception, or any error Apollo auto-wraps. The message is replaced with "Internal server error" and the code becomes INTERNAL_SERVER_ERROR (the original code is dropped). The full detail is logged server-side, never sent to the client.
  • Prisma / database errors are a special unsafe case: the client receives the generic message "A database error occurred" with code INTERNAL_SERVER_ERROR.
{
  "errors": [
    {
      "message": "Internal server error",
      "extensions": { "code": "INTERNAL_SERVER_ERROR" }
    }
  ]
}

Treat INTERNAL_SERVER_ERROR as “retry or contact support” — it never carries actionable detail. In non-production environments the formatter is bypassed and the full original error (including stack traces) is returned for debugging.

Handling errors in your client

Switch on extensions.code, with a default branch for anything unrecognized:

const errors = response.errors ?? []

for (const err of errors) {
  switch (err.extensions?.code) {
    case 'UNAUTHENTICATED':
      // Token missing or invalid — re-authenticate.
      break
    case 'FORBIDDEN':
      // Authenticated, but the role can't perform this action.
      break
    case 'PLAN_LIMIT_REACHED':
      // Blocked by plan/tier — inspect extensions.kind and show an upgrade path.
      break
    case 'BAD_USER_INPUT':
      // Validation failed — read extensions.data for the offending field.
      break
    case 'RATE_LIMITED':
      // Back off and retry (see Rate Limits).
      break
    case 'TODO_NOT_FOUND':
      // The record doesn't exist or you can't access it.
      break
    default:
    // INTERNAL_SERVER_ERROR or any unhandled code — show a generic message and log it.
  }
}

Rate limiting

Most operations are governed by the per-tier rate-limit middleware documented on Rate Limits. A handful of sensitive or expensive operations have their own per-operation caps enforced by graphql-rate-limit (keyed by user, falling back to IP). Exceeding any of these returns the RATE_LIMITED code.

RuleWindowMaxApplies to
Default60s5Per-operation guard on assorted authenticated mutations (e.g. submitForm, sendTestEmail, createDocument)
Request60s3High-impact account actions (e.g. deleteOrganization, updateEmail, verifySecurityCode)
Sign-in60s10signIn, socialAuth
Sign-in request60s10signInRequest, signUpRequest
Export50s1exportRecords, exportReport
Public view60s100publicViewData, publicViewRecords
Accept invitation60s100verifyAcceptInvitation
Check auth method60s20checkAuthMethod
Notify org admin24h3notifyOrgAdminCustomFieldLimit

These per-operation limits sit on top of the tier limits — a request must pass both. See Rate Limits for the tier middleware and recommended backoff.

Error code reference

Every code below is thrown by the API. Messages are the exact strings the server returns (some are templated with context, indicated by ).

Authentication & access

CodeMessage
UNAUTHENTICATEDYou are not authenticated.
FORBIDDENYou are not authorized.
PLAN_LIMIT_REACHED(varies; see structured detail above)
PRO_REQUIREDThis feature requires a Pro subscription.
COMPANY_BANNEDCompany is banned
NOT_AVAILABLE_COUNTRYBlue is currently not available in your country.
RATE_LIMITEDToo many requests, please try again later.

Validation & input

CodeMessage
BAD_USER_INPUT(varies; carries extensions.data)
FORMULA_VALIDATION_ERROR(name-formula validation message)
BAD_EMAILWhoops, something wrong with your email address.
MISSING_PARAM_ERRORmissing param
MISSING_PROP_ERRORMissing the property.
CUSTOM_FIELD_VALUE_PARSE_ERRORUnable to parse custom field value.
TITLE_COLUMN_NOT_FOUNDNo title column found.

Sign-in, sessions & invitations

CodeMessage
EMAIL_IS_TAKENUser is already existed with this email.
EMAIL_NOT_REGISTEREDThis email is not registered in Blue.
DISPOSABLE_EMAIL_NOT_ALLOWEDTemporary or disposable email addresses are not allowed.
PENDING_INVITATIONYou have a pending invitation… Please check your email and accept the invitation before signing in.
INVITATION_NOT_FOUNDThe invitation has expired or no longer exists.
INVITATION_EXPIREDInvitation has expired.
UNABLE_TO_ACCEPT_INVITATIONUnable to accept the invitation.
SECURITY_CODE_NOT_FOUNDSecurity code was not found.
SECURITY_CODE_EXPIREDSecurity code has expired.
SECURITY_CODE_INCORRECTSecurity code is incorrect.
SOCIAL_TOKEN_INVALIDSocial login token is invalid or expired
SOCIAL_EMAIL_NOT_PROVIDEDEmail not provided by social provider
SOCIAL_EMAIL_NOT_VERIFIEDEmail not verified by social provider
SOCIAL_EMAIL_NOT_REGISTEREDThis email is not registered in Blue.
SOCIAL_UID_ALREADY_LINKEDThis social account is already linked to another user
OAUTH_CONNECTION_NOT_FOUNDOAuth connection was not found.
PERSONAL_ACCESS_TOKEN_NOT_FOUNDPersonal access token was not found.

Users & membership

CodeMessage
USER_NOT_FOUNDUser was not found.
USER_ALREADY_IN_THE_COMPANYUser is already in the company.
USER_ALREADY_IN_THE_PROJECTUser is already in the project.
USER_NOT_IN_COMPANYUser is not in the company.
USER_NOT_IN_PROJECTUser is not in the project.
ASSIGNEE_NOT_FOUNDAssignee was not found.
ADD_SELFYou are not allowed to add yourself.
ADDED_ALREADYYou are not allowed to add again.
REMOVE_SELFYou are not allowed to remove yourself.
UPDATE_LEVEL_SELFYou are not allowed to update level for yourself.
UPDATE_COMPANY_LEVEL_TO_OWNERYou are not allowed to update anyone to be the owner.
COMPANY_USER_NOT_FOUNDCompany user was not found.
PROJECT_USER_ROLE_NOT_FOUNDProject user role was not found.
PROJECT_USER_ROLE_LIMITProject user role limit reached.

Records, lists & dependencies

CodeMessage
TODO_NOT_FOUNDTodo was not found.
TODO_LIST_NOT_FOUNDTodo list was not found.
TODO_ALREADY_IN_LISTTodo is already in the specified todo list.
TODO_ALREADY_IN_PROJECTTodo is already associated with the target project.
TODO_DEPENDENCY_ALREADY_EXISTSTodo dependency already exists
TODO_LIST_CREATE_TODO_LIMIT_ERRORTodo list has reached the maximum number of todos.
TODO_UDPATE_LIMITYou are updating too many todos
TITLE_EDIT_NOT_ALLOWEDCannot edit title when name formula is enabled.
FIELD_REFERENCED_BY_FORMULACannot delete field “” - it is used in the project name formula.
FORMULA_RECOMPUTATION_IN_PROGRESSA name formula recomputation is already in progress.

Workspaces, organizations & limits

CodeMessage
PROJECT_NOT_FOUNDProject was not found.
PROJECT_URL_ALREADY_EXISTSProject URL already exists.
CREATE_PROJECT_LIMITProject size exceeds maximum limit.
COMPANY_NOT_FOUNDCompany was not found.
COMPANY_URL_ALREADY_EXISTSCompany URL already exists.
COMPANY_LICENSE_NOT_FOUNDCompany license was not found.
CUSTOM_FIELD_LIMITThis workspace has reached the 30 custom field limit. Upgrade to Enterprise to add more.
TOO_MANY_LINKSThe company has too many links.

Custom fields, tags & templates

CodeMessage
CUSTOM_FIELD_NOT_FOUNDCustom field was not found.
CUSTOM_FIELD_OPTION_NOT_FOUNDCustom field option was not found.
TAG_NOT_FOUNDTag was not found
TEMPLATE_NOT_FOUNDTemplate was not found.
SAVED_VIEW_NOT_FOUNDSaved view was not found.
PUBLIC_VIEW_NOT_FOUNDPublic view was not found.

Comments, activity & notifications

CodeMessage
COMMENT_NOT_FOUNDComment was not found.
DISCUSSION_NOT_FOUNDDiscussion was not found.
MENTION_NOT_FOUNDMention not found
ACTIVITY_NOT_FOUNDActivity was not found.
STATUS_UPDATE_NOT_FOUNDStatus update was not found.
NOTIFICATION_OPTION_NOT_FOUNDNotification option was not found.
CHAT_NOT_FOUNDChat was not found.

Checklists

CodeMessage
CHECKLIST_NOT_FOUNDChecklist was not found.
CHECKLIST_ITEM_NOT_FOUNDChecklist item was not found.

Automations & webhooks

CodeMessage
AUTOMATION_NOT_FOUNDAutomation was not found.
WEBHOOK_NOT_FOUNDWebhook was not found.

Dashboards & charts

CodeMessage
DASHBOARD_NOT_FOUNDDashboard was not found.
CHART_NOT_FOUNDChart was not found.
CHART_ALREADY_EXPORTINGChart is already being exported.
CHART_SEGMENT_NOT_FOUNDChart segment was not found.
CHART_SEGMENT_VALUE_NOT_FOUNDChart segment value was not found.
REPORT_NOT_FOUNDReport was not found.

Files, folders & documents

CodeMessage
FILE_NOT_FOUNDFile was not found.
UNABLE_TO_DELETE_FILEUnable to delete the file.
FILES_DISABLED_FOR_ROLEFile uploads are disabled for your role in this workspace.
FOLDER_NOT_FOUNDFolder was not found.
DOCUMENT_NOT_FOUNDDocument not found.
PORTABLE_DOCUMENT_NOT_FOUNDPortable document was not found.
PORTABLE_DOCUMENT_FIELD_NOT_FOUNDPortable document field was not found.

Forms

CodeMessage
FORM_NOT_FOUNDForm was not found.
FORM_TOKEN_INVALIDForm token is not valid.

Imports

CodeMessage
IMPORT_ALREADY_IN_PROGRESSAn import is already running for this workspace. Please wait for it to finish before starting another.

An import that would exceed your plan’s per-workspace record cap is rejected before it starts with PLAN_LIMIT_REACHED (resource: "records") — see that section above for the full error shape. There’s no import-specific limit code; the cap scales with your plan, same as everywhere else records are counted.

Custom domains & email

CodeMessage
CUSTOM_DOMAIN_NOT_FOUNDCustom domain was not found.
CUSTOM_DOMAIN_ALREADY_EXISTSDomain already exists.
CUSTOM_DOMAIN_IN_USEDomain is already in use.
CUSTOM_DOMAIN_CREATE_ERRORCouldn’t set up . This usually means its DNS isn’t pointing to Blue yet…
SMTP_CREDENTIAL_NOT_FOUNDSMTP Credential was not found.
SMTP_CREDENTIAL_INVALIDInvalid SMTP Credential.
SMTP_CREDENTIAL_RESERVED_SENDER_NAMEReserved sender name.
SMTP_CREDENTIAL_RESERVED_SENDER_EMAILReserved sender email.
EMAIL_TEMPLATE_NOT_FOUNDEmail template was not found.
APPLICATION_TYPE_NOT_FOUNDApplication type was not found.

Billing & subscriptions

CodeMessage
PAYMENT_REQUIREDWhoops, something went wrong. Try reloading the page.
SUBSCRIPTION_PLAN_MISSINGSubscription plan is missing.
COMPANY_SUBSCRIPTION_PLAN_NOT_FOUNDThe company does not have a subscription plan.
COMPANY_SUBSCRIPTION_PLAN_ALREADY_EXISTThe company already has an active subscription plan.
COMPANY_SUBSCRIPTION_PLAN_PROMO_CODE_NOT_FOUNDInvalid promo code
UPGRADE_TO_SAME_PLAN_ERRORCannot upgrade to the same plan.
CREATE_CHECKOUT_SESSION_FAILEDUnable to create a checkout session.
CREATE_STRIPE_CUSTOMER_ERRORFailed to create a stripe customer.
RETRIEVE_STRIPE_CUSTOMER_ERRORFailed to retrieve a stripe customer.
UPDATE_STRIPE_CUSTOMER_ERRORFailed to update a stripe customer.
CREATE_STRIPE_SUBSCRIPTION_ERRORFailed to create a stripe subscription.
RETRIEVE_STRIPE_SUBSCRIPTION_ERRORFailed to retrieve a stripe subscription.
UPDATE_STRIPE_SUBSCRIPTION_ERRORFailed to update stripe subscription.
RETRIEVE_STRIPE_SUBSCRIPTION_PLAN_ERRORFailed to retrieve a stripe subscription plan.
RETRIEVE_STRIPE_PRICE_ERRORFailed to retrieve a stripe price.

System

CodeMessage
RESOURCE_NOT_FOUNDResource was not found.
INTERNAL_SERVER_ERRORInternal server error. (masked code; see Safe vs. masked errors)