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

CodeHTTP StatusWhose ErrorRetryableDescription & Hint
invalid_request400callerfalseMalformed parameters, missing required arguments, or invalid JSON. Check inputs against the endpoint schema in GET /openapi.json.
unauthorized401callerfalseMissing, invalid, expired or revoked API key or session. Provide Authorization: Bearer tf_live_....
sign_in_required401callerfalseUnauthenticated (keyless) caller attempted a fresh_pull or ai_brief endpoint. Sign in or provide an API key to access live ingest and AI analyses.
forbidden403callerfalseAPI key lacks required scope, CSRF validation failed, or route is operator-only. Check scopes or permissions.
not_found404callerfalseRoute, 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_balance402callerfalseWorkspace wallet balance is lower than the endpoint price. Top up credit with POST /api/billing/checkout.
spend_cap_exceeded402callerfalsePer-key monthly spend cap reached. Raise the cap or wait for the monthly allowance refresh.
fresh_pull_unpriced402callerfalseEndpoint cost class fresh_pull is operator-only until pricing calibration completes. Use cached library reads.
plan_limit402callerfalseWorkspace plan quota exceeded (e.g. tracked apps limit, target markets limit, or analysis allowance). Upgrade with POST /api/billing/checkout.
conflict409callertrueA request with the same Idempotency-Key is currently in flight. Wait for the in-progress operation to settle.
rate_limited429callertrueRequest rate limit exceeded. Respect the Retry-After header before retrying.
not_implemented501callerfalseFeature unavailable in current mode (e.g. priced endpoints or Play Store scraping in JSON mode without Postgres; PDF rendering without Playwright).
upstream_error502 / 504upstreamtrueUpstream data provider (Apple, Google Play, Meta, TikTok, YouTube, Wayback) failed or timed out. Retry with exponential backoff.
internal_error500threefoldfalseUnexpected 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."
}