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
| Status | Name | Description |
|---|---|---|
400 | Bad Request | The request body or parameters are invalid |
401 | Unauthorized | Missing or invalid API key |
403 | Forbidden | Valid API key but insufficient permissions or plan limits exceeded |
404 | Not Found | The requested resource does not exist |
409 | Conflict | The request conflicts with the current state (e.g., duplicate slug) |
429 | Rate Limited | Too many requests, retry after the indicated delay |
500 | Internal Server Error | An 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:
| Code | Status | Meaning |
|---|---|---|
NOT_FOUND | 404 | Resource does not exist |
VALIDATION_ERROR | 400 | Invalid request body or parameters (details may carry specifics) |
UNAUTHORIZED | 401 | Missing or invalid credentials |
INTERNAL_ERROR | 500 | Unexpected server error |
plan_limit_exceeded | 403 | A plan gate fired — see Plan limit errors |
rate_limited | 429 | Rolling-window rate limit exceeded |
forbidden_key | 403 | The provider key cannot be used in this context |
invalid_provider | 400 | Unknown AI provider |
invalid_model | 400 | Model not in the provider allowlist |
no_key | 412 | No 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"
}
}
limit | Triggered when |
|---|---|
max_workspaces | Creating a workspace beyond the plan cap |
max_schemas | Creating a schema beyond the plan cap |
max_pages_per_month | An extraction whose pages would push you over the window total |
max_extractions_per_month | An extraction that would exceed your monthly successful-run count |
env_not_allowed | Targeting a non-production env on Free |
webhook_not_allowed | Creating a webhook (legacy: webhooks are now allowed on every plan) |
re_extract_not_allowed | Re-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.