threefold Error Contract
Every threefold API error and MCP tool error returns a uniform, agent-friendly error body:
{
"error": "Human-readable description of the problem",
"code": "machine_readable_code",
"whose_error": "caller" | "upstream" | "threefold",
"retryable": true | false,
"hint": "Actionable suggestion to resolve or work around the error"
}
Payment-related errors (402) include additional quantitative fields from phase 2 metering to explain balance and budget limits. Rate limit responses (429) always include the Retry-After HTTP header.
Closed Set of Error Codes
| Code | HTTP Status | Whose Error | Retryable | Description & Hint |
|---|---|---|---|---|
invalid_request | 400 | caller | false | Malformed parameters, missing required arguments, or invalid JSON. Check inputs against the endpoint schema in GET /openapi.json. |
unauthorized | 401 | caller | false | Missing, invalid, expired or revoked API key or session. Provide Authorization: Bearer tf_live_.... |
sign_in_required | 401 | caller | false | Unauthenticated (keyless) caller attempted a fresh_pull or ai_brief endpoint. Sign in or provide an API key to access live ingest and AI analyses. |
forbidden | 403 | caller | false | API key lacks required scope, CSRF validation failed, or route is operator-only. Check scopes or permissions. |
not_found | 404 | caller | false | Route, app, job, analysis, or report not found in the library, or a chart (market × store × genre × chart type) that has not been collected: chart endpoints never answer an uncollected chart with empty lists, and the hint lists the charts that market has. Label endpoints (label_profile, label_compare, language_fit) answer the same way when nothing in scope has been labelled yet (a compare's message names the from or to market), and market_take when no take has been written. Verify the ID or discover available endpoints via GET /api/discover. |
insufficient_balance | 402 | caller | false | Workspace wallet balance is lower than the endpoint price. Top up credit with POST /api/billing/checkout. |
spend_cap_exceeded | 402 | caller | false | Per-key monthly spend cap reached. Raise the cap or wait for the monthly allowance refresh. |
fresh_pull_unpriced | 402 | caller | false | Endpoint cost class fresh_pull is operator-only until pricing calibration completes. Use cached library reads. |
plan_limit | 402 | caller | false | Workspace plan quota exceeded (e.g. tracked apps limit, target markets limit, or analysis allowance). Upgrade with POST /api/billing/checkout. |
conflict | 409 | caller | true | A request with the same Idempotency-Key is currently in flight. Wait for the in-progress operation to settle. |
rate_limited | 429 | caller | true | Request rate limit exceeded. Respect the Retry-After header before retrying. |
not_implemented | 501 | caller | false | Feature unavailable in current mode (e.g. priced endpoints or Play Store scraping in JSON mode without Postgres; PDF rendering without Playwright). |
upstream_error | 502 / 504 | upstream | true | Upstream data provider (Apple, Google Play, Meta, TikTok, YouTube, Wayback) failed or timed out. Retry with exponential backoff. |
internal_error | 500 | threefold | false | Unexpected threefold internal server error. Contact support if the problem persists. |
Payment Error Fields
When code is one of insufficient_balance, spend_cap_exceeded, fresh_pull_unpriced, or plan_limit, the response body also includes:
endpoint_id(string): The identifier of the endpoint called.balance_micro_usd(integer, optional): Current workspace balance in micro-USD ($1.00 = 1,000,000 micro-USD).required_micro_usd(integer, optional): Price required to execute the call.cap_micro_usd(integer, optional): The key's monthly spend cap in micro-USD.spent_micro_usd(integer, optional): The key's settled + reserved spend this month in micro-USD.
Example Error Responses
Missing Parameter (400)
{
"error": "compare_markets needs --to, --niche",
"code": "invalid_request",
"whose_error": "caller",
"retryable": false,
"hint": "Check request parameters against GET /openapi.json."
}
Keyless Fresh Pull Refusal (401)
{
"error": "Sign in required for fresh_pull endpoints.",
"code": "sign_in_required",
"whose_error": "caller",
"retryable": false,
"hint": "Keyless callers can read the library for free. To trigger live supplier pulls or AI briefs, sign in or pass an API key."
}
Insufficient Balance (402)
{
"error": "Insufficient balance.",
"code": "insufficient_balance",
"whose_error": "caller",
"retryable": false,
"hint": "Top up your wallet balance with POST /api/billing/checkout.",
"balance_micro_usd": 100,
"required_micro_usd": 200,
"endpoint_id": "compare_markets"
}
Rate Limited (429)
HTTP Headers:
HTTP/1.1 429 Too Many Requests
Retry-After: 30
Body:
{
"error": "Rate limit exceeded. Retry in 30s.",
"code": "rate_limited",
"whose_error": "caller",
"retryable": true,
"hint": "Wait for 30 seconds before retrying this request."
}