Errors

When a request fails, Tracore returns a JSON error response with an appropriate HTTP status code.

Error response shape

{
  "error": {
    "message": "Workspace with id 'ws_123' not found",
    "code": "NOT_FOUND",
    "details": {}
  }
}

The code and details fields are optional and may not be present on all errors.

Status codes

StatusNameDescription
400Bad RequestThe request body or parameters are invalid
401UnauthorizedMissing or invalid API key
403ForbiddenValid API key but insufficient permissions or plan limits exceeded
404Not FoundThe requested resource does not exist
409ConflictThe request conflicts with the current state (e.g., duplicate slug)
429Rate LimitedToo many requests, retry after the indicated delay
500Internal Server ErrorAn unexpected error occurred on the server

Error codes

The error.code field is a free-form string on the wire (new codes are not a breaking change), but the known vocabulary is published in the API contract as the ApiErrorCode schema and exported by the SDKs as constants. Codes you can encounter through the public API:

CodeStatusMeaning
NOT_FOUND404Resource does not exist
VALIDATION_ERROR400Invalid request body or parameters (details may carry specifics)
UNAUTHORIZED401Missing or invalid credentials
INTERNAL_ERROR500Unexpected server error
plan_limit_exceeded403A plan gate fired — see Plan limit errors
rate_limited429Rolling-window rate limit exceeded
forbidden_key403The provider key cannot be used in this context
invalid_provider400Unknown AI provider
invalid_model400Model not in the provider allowlist
no_key412No stored provider key where one is required

Key-validation failures on provider-key endpoints arrive as VALIDATION_ERROR with details.code = "invalid_key" — the top-level code stays VALIDATION_ERROR. A few additional codes (not_implemented, stripe_not_configured, no_stripe_customer, email_not_verified, checkout_missing_user, invalid_signature) surface only on internal billing surfaces and the Stripe webhook, not on public API endpoints.

SDK error handling

The TypeScript SDK throws a TracoreError for non-2xx responses. You can inspect the status and error details; the known codes are exported as ERROR_CODES:

import { ERROR_CODES, TracoreClient, TracoreError } from '@tracore/sdk';

const client = new TracoreClient({
  apiKey: process.env.TRACORE_API_KEY!,
});

try {
  const workspace = await client.workspaces.get('my-workspace');
} catch (error) {
  if (error instanceof TracoreError) {
    console.error(error.status); // 404
    console.error(error.message); // "Workspace not found"
    console.error(error.code); // ERROR_CODES.NOT_FOUND
  }
}

Failures that never produced an HTTP response (network or decoding errors) have status === 0 and a synthetic client-side code (ERROR_CODES.TRANSPORT, ERROR_CODES.CONFIG, ERROR_CODES.POLLING_TIMEOUT), with the underlying error reachable via the standard Error#cause.

Plan limit errors

When a request is rejected due to plan limits, the response is HTTP 403 with a structured body. The code is always plan_limit_exceeded; the limit discriminator identifies which gate fired and selects the in-app paywall copy.

{
  "error": {
    "code": "plan_limit_exceeded",
    "limit": "max_pages_per_month",
    "plan": "free",
    "limitValue": 100,
    "currentValue": 100,
    "windowStart": "2026-04-01T00:00:00Z",
    "windowEnd": "2026-05-01T00:00:00Z",
    "message": "Free plan allows 100 pages per month. Upgrade to Pro for 2,000.",
    "upgradeUrl": "/upgrade"
  }
}
limitTriggered when
max_workspacesCreating a workspace beyond the plan cap
max_schemasCreating a schema beyond the plan cap
max_pages_per_monthAn extraction whose pages would push you over the window total
max_extractions_per_monthAn extraction that would exceed your monthly successful-run count
env_not_allowedTargeting a non-production env on Free
webhook_not_allowedCreating a webhook (legacy: webhooks are now allowed on every plan)
re_extract_not_allowedRe-extracting an already-counted document on Free

windowStart / windowEnd are present on max_pages_per_month and max_extractions_per_month. meta is present on env_not_allowed ({ requestedEnv, allowed }). See the Plans and Limits guide for the user-facing explanation.