{"openapi":"3.1.0","info":{"title":"crypto-comp public API","version":"1.0.0","description":"Compose, validate, save and backtest trading strategies, and read the results.\n\n**Authentication.** A single bearer token: `Authorization: Bearer mcpk_…`.\nMint one in the panel at `/settings/api-keys`; it is shown once and cannot be recovered.\nThe token's first segment identifies the credential and the second proves it —\nD-19's \"key and secret\" travelling as one value.\n\n**Scopes.** Each key carries a subset of 5 scopes and each endpoint\nrequires exactly one. There is no implication: `:write` does not grant `:read`.\n\n**Rate limits.** Per key AND per account, against the same tier number, in a fixed\n60-second window. Every response carries `X-RateLimit-Limit`,\n`X-RateLimit-Remaining` and `X-RateLimit-Reset`; a refusal is `429` with `Retry-After`.\nBecause the window is fixed rather than sliding, a caller can send up to twice the\nlimit across a window boundary. That is a documented property, not a defect.\n\n**Money is always a decimal STRING**, never a JSON number — `\"10000.00000000\"`, not\n`10000`. Sending a number where a decimal is expected is a `400`. Parse these with a\ndecimal type, not a float.\n\n**Versioning.** Within `v1`, changes are additive only: fields may be added, never\nremoved, renamed or narrowed. A breaking change would be `/api/v2`.\n\n**Not metered against AI credit.** Calls to this API cost nothing from your credit\nbalance; they are bounded by the rate limit alone. Credit is spent by the in-app AI\nassistant, which is a different surface."},"servers":[{"url":"/","description":"This installation."}],"tags":[{"name":"catalog","description":"What the engine can do, and whether a spec is valid."},{"name":"specs","description":"Spec validation."},{"name":"strategies","description":"Saved strategies."},{"name":"runs","description":"Backtests and their results."}],"paths":{"/api/v1/catalog":{"get":{"operationId":"catalog","summary":"List every strategy block the engine supports.","description":"List every strategy block the engine supports (indicators, conditions and their inputs/outputs), projected from the engine's own registry. Call this before composing a spec.\n\nRequired scope: `catalog:read`.","tags":["catalog"],"security":[{"ApiKeyAuth":["catalog:read"]}],"responses":{"200":{"description":"List every strategy block the engine supports.","content":{"application/json":{"schema":{"type":"object"}}},"headers":{"X-RateLimit-Limit":{"description":"Requests permitted per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}}},"400":{"description":"`bad_request` — The request is malformed — a bad uuid, a missing field, a number where a decimal string belongs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No key, or a key that is unknown, malformed or revoked. All four are the same answer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`insufficient_scope` — Your key does not hold the scope this endpoint needs. The detail names the scope; mint a key that has it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — You are over your tier's request ceiling. Retry-After says when; the detail says whether the KEY or the ACCOUNT tripped.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`internal` — Something failed on our side. Nothing about it is your request's fault; retrying is reasonable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`unavailable` — The engine's validation service is unreachable. Never treated as 'valid' — a spec is not stored on a guess.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/specs/validate":{"post":{"operationId":"validate_spec","summary":"Deep-validate a strategy spec and return the verdict.","description":"Deep-validate a strategy spec through the engine's own validator and return the verdict. On failure the response carries structured errors; fix them and validate again. This is the same validator that guards saving and launching — nothing bypasses it.\n\nRequired scope: `catalog:read`.","tags":["specs"],"security":[{"ApiKeyAuth":["catalog:read"]}],"responses":{"200":{"description":"Deep-validate a strategy spec and return the verdict.","content":{"application/json":{"schema":{"type":"object"}}},"headers":{"X-RateLimit-Limit":{"description":"Requests permitted per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}}},"400":{"description":"`bad_request` — The request is malformed — a bad uuid, a missing field, a number where a decimal string belongs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No key, or a key that is unknown, malformed or revoked. All four are the same answer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`insufficient_scope` — Your key does not hold the scope this endpoint needs. The detail names the scope; mint a key that has it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — You are over your tier's request ceiling. Retry-After says when; the detail says whether the KEY or the ACCOUNT tripped.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`internal` — Something failed on our side. Nothing about it is your request's fault; retrying is reasonable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`unavailable` — The engine's validation service is unreachable. Never treated as 'valid' — a spec is not stored on a guess.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"spec":{"type":"object","description":"The strategy spec, including its string `version` field."}},"required":["spec"],"additionalProperties":false}}}}}},"/api/v1/strategies":{"get":{"operationId":"list_strategies","summary":"List your saved strategies, newest first.","description":"List your saved strategies, newest first. Specs are omitted; use get_strategy for one.\n\nRequired scope: `strategies:read`.","tags":["strategies"],"security":[{"ApiKeyAuth":["strategies:read"]}],"responses":{"200":{"description":"List your saved strategies, newest first.","content":{"application/json":{"schema":{"type":"object"}}},"headers":{"X-RateLimit-Limit":{"description":"Requests permitted per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}}},"400":{"description":"`bad_request` — The request is malformed — a bad uuid, a missing field, a number where a decimal string belongs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No key, or a key that is unknown, malformed or revoked. All four are the same answer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`insufficient_scope` — Your key does not hold the scope this endpoint needs. The detail names the scope; mint a key that has it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — You are over your tier's request ceiling. Retry-After says when; the detail says whether the KEY or the ACCOUNT tripped.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`internal` — Something failed on our side. Nothing about it is your request's fault; retrying is reasonable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`unavailable` — The engine's validation service is unreachable. Never treated as 'valid' — a spec is not stored on a guess.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"save_strategy","summary":"Save a new strategy. The spec is validated first.","description":"Save a NEW strategy under your account. The spec is deep-validated by the engine first; an invalid spec is rejected and nothing is stored. Saving never overwrites an existing strategy.\n\nRequired scope: `strategies:write`.","tags":["strategies"],"security":[{"ApiKeyAuth":["strategies:write"]}],"responses":{"201":{"description":"Save a new strategy. The spec is validated first.","content":{"application/json":{"schema":{"type":"object"}}},"headers":{"X-RateLimit-Limit":{"description":"Requests permitted per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}}},"400":{"description":"`bad_request` — The request is malformed — a bad uuid, a missing field, a number where a decimal string belongs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No key, or a key that is unknown, malformed or revoked. All four are the same answer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`insufficient_scope` — Your key does not hold the scope this endpoint needs. The detail names the scope; mint a key that has it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"`spec_invalid` — The engine rejected the spec. POST /api/v1/specs/validate returns the structured errors.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — You are over your tier's request ceiling. Retry-After says when; the detail says whether the KEY or the ACCOUNT tripped.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`internal` — Something failed on our side. Nothing about it is your request's fault; retrying is reasonable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`unavailable` — The engine's validation service is unreachable. Never treated as 'valid' — a spec is not stored on a guess.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"A short human name for the strategy."},"spec":{"type":"object","description":"The strategy spec, including its string `version` field."}},"required":["name","spec"],"additionalProperties":false}}}}}},"/api/v1/strategies/{id}":{"get":{"operationId":"get_strategy","summary":"Read one of your strategies, including its full spec.","description":"Read one of your saved strategies, including its full spec.\n\nRequired scope: `strategies:read`.","tags":["strategies"],"security":[{"ApiKeyAuth":["strategies:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"UUID of your own object. Another account's id answers 404."}],"responses":{"200":{"description":"Read one of your strategies, including its full spec.","content":{"application/json":{"schema":{"type":"object"}}},"headers":{"X-RateLimit-Limit":{"description":"Requests permitted per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}}},"400":{"description":"`bad_request` — The request is malformed — a bad uuid, a missing field, a number where a decimal string belongs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No key, or a key that is unknown, malformed or revoked. All four are the same answer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`insufficient_scope` — Your key does not hold the scope this endpoint needs. The detail names the scope; mint a key that has it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No such object — or it is not yours. Deliberately the same answer, so this can never confirm that someone else's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — You are over your tier's request ceiling. Retry-After says when; the detail says whether the KEY or the ACCOUNT tripped.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`internal` — Something failed on our side. Nothing about it is your request's fault; retrying is reasonable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`unavailable` — The engine's validation service is unreachable. Never treated as 'valid' — a spec is not stored on a guess.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/runs":{"get":{"operationId":"list_runs","summary":"List your runs, newest first, each with its summary.","description":"List your runs, newest first, each with its summary. Optionally filter to one of your strategies.\n\nRequired scope: `runs:read`.","tags":["runs"],"security":[{"ApiKeyAuth":["runs:read"]}],"parameters":[{"name":"strategy_id","in":"query","required":false,"schema":{"type":"string","format":"uuid"},"description":"Optional UUID of YOUR strategy to filter by."}],"responses":{"200":{"description":"List your runs, newest first, each with its summary.","content":{"application/json":{"schema":{"type":"object"}}},"headers":{"X-RateLimit-Limit":{"description":"Requests permitted per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}}},"400":{"description":"`bad_request` — The request is malformed — a bad uuid, a missing field, a number where a decimal string belongs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No key, or a key that is unknown, malformed or revoked. All four are the same answer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`insufficient_scope` — Your key does not hold the scope this endpoint needs. The detail names the scope; mint a key that has it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — You are over your tier's request ceiling. Retry-After says when; the detail says whether the KEY or the ACCOUNT tripped.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`internal` — Something failed on our side. Nothing about it is your request's fault; retrying is reasonable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`unavailable` — The engine's validation service is unreachable. Never treated as 'valid' — a spec is not stored on a guess.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"launch_run","summary":"Queue a backtest. Subject to your subscription limits.","description":"Queue a backtest for a spec and a run window. Subject to the same subscription limits as the UI (concurrent runs, how far back the data window may reach, spot-only markets) — a refusal comes back as a tier_limit error naming which limit. Returns a run_id; poll get_run for the outcome.\n\nRequired scope: `runs:write`.","tags":["runs"],"security":[{"ApiKeyAuth":["runs:write"]}],"responses":{"201":{"description":"Queue a backtest. Subject to your subscription limits.","content":{"application/json":{"schema":{"type":"object"}}},"headers":{"X-RateLimit-Limit":{"description":"Requests permitted per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}}},"400":{"description":"`bad_request` — The request is malformed — a bad uuid, a missing field, a number where a decimal string belongs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No key, or a key that is unknown, malformed or revoked. All four are the same answer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"`tier_limit` — Your subscription does not permit this launch (concurrency, data depth or market). Upgrading fixes it; the detail says which limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`insufficient_scope` — Your key does not hold the scope this endpoint needs. The detail names the scope; mint a key that has it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No such object — or it is not yours. Deliberately the same answer, so this can never confirm that someone else's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — You are over your tier's request ceiling. Retry-After says when; the detail says whether the KEY or the ACCOUNT tripped.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`internal` — Something failed on our side. Nothing about it is your request's fault; retrying is reasonable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`unavailable` — The engine's validation service is unreachable. Never treated as 'valid' — a spec is not stored on a guess.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"spec":{"type":"object","description":"The strategy spec, including its string `version` field."},"run_config":{"type":"object","description":"symbol, base_interval, timeframes[], start, end and initial_balance — every value a string (initial_balance is a decimal string, never a number)."},"strategy_id":{"type":"string","description":"Optional UUID of YOUR saved strategy, to link this run to it."}},"required":["spec","run_config"],"additionalProperties":false}}}}}},"/api/v1/runs/{id}":{"get":{"operationId":"get_run","summary":"Read one of your runs: status, timings, error and full result.","description":"Read one of your runs: its status, timings, error and full result.\n\nRequired scope: `runs:read`.","tags":["runs"],"security":[{"ApiKeyAuth":["runs:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"UUID of your own object. Another account's id answers 404."}],"responses":{"200":{"description":"Read one of your runs: status, timings, error and full result.","content":{"application/json":{"schema":{"type":"object"}}},"headers":{"X-RateLimit-Limit":{"description":"Requests permitted per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}}},"400":{"description":"`bad_request` — The request is malformed — a bad uuid, a missing field, a number where a decimal string belongs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No key, or a key that is unknown, malformed or revoked. All four are the same answer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`insufficient_scope` — Your key does not hold the scope this endpoint needs. The detail names the scope; mint a key that has it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No such object — or it is not yours. Deliberately the same answer, so this can never confirm that someone else's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — You are over your tier's request ceiling. Retry-After says when; the detail says whether the KEY or the ACCOUNT tripped.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`internal` — Something failed on our side. Nothing about it is your request's fault; retrying is reasonable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`unavailable` — The engine's validation service is unreachable. Never treated as 'valid' — a spec is not stored on a guess.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/runs/{id}/results":{"get":{"operationId":"get_results","summary":"Read the full stored result of one of your runs.","description":"Read the full stored result of one of your runs (summary, equity curves, fills, signal log, and for a futures run the funding ledger, its net total, any liquidations and the execution stamp). `result.version` is the branch point: \"0.1\" predates the metric suite, \"0.2\" predates futures, \"0.3\" carries everything; stored results are immutable and never recomputed, so treat later fields as optional. Null until the run finishes. Large — prefer get_metrics when you only need performance figures.\n\nRequired scope: `runs:read`.","tags":["runs"],"security":[{"ApiKeyAuth":["runs:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"UUID of your own object. Another account's id answers 404."}],"responses":{"200":{"description":"Read the full stored result of one of your runs.","content":{"application/json":{"schema":{"type":"object"}}},"headers":{"X-RateLimit-Limit":{"description":"Requests permitted per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}}},"400":{"description":"`bad_request` — The request is malformed — a bad uuid, a missing field, a number where a decimal string belongs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No key, or a key that is unknown, malformed or revoked. All four are the same answer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`insufficient_scope` — Your key does not hold the scope this endpoint needs. The detail names the scope; mint a key that has it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No such object — or it is not yours. Deliberately the same answer, so this can never confirm that someone else's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — You are over your tier's request ceiling. Retry-After says when; the detail says whether the KEY or the ACCOUNT tripped.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`internal` — Something failed on our side. Nothing about it is your request's fault; retrying is reasonable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`unavailable` — The engine's validation service is unreachable. Never treated as 'valid' — a spec is not stored on a guess.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/runs/{id}/metrics":{"get":{"operationId":"get_metrics","summary":"Read just the performance metrics of one of your runs.","description":"Read just the performance metrics of one of your runs (Sharpe, Sortino, max drawdown, win rate, profit factor, expectancy and the rest). Every figure is a decimal string. Null for a run that has not finished, and for runs recorded before the metric suite existed.\n\nRequired scope: `runs:read`.","tags":["runs"],"security":[{"ApiKeyAuth":["runs:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"UUID of your own object. Another account's id answers 404."}],"responses":{"200":{"description":"Read just the performance metrics of one of your runs.","content":{"application/json":{"schema":{"type":"object"}}},"headers":{"X-RateLimit-Limit":{"description":"Requests permitted per window.","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in this window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}}}},"400":{"description":"`bad_request` — The request is malformed — a bad uuid, a missing field, a number where a decimal string belongs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No key, or a key that is unknown, malformed or revoked. All four are the same answer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`insufficient_scope` — Your key does not hold the scope this endpoint needs. The detail names the scope; mint a key that has it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No such object — or it is not yours. Deliberately the same answer, so this can never confirm that someone else's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — You are over your tier's request ceiling. Retry-After says when; the detail says whether the KEY or the ACCOUNT tripped.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`internal` — Something failed on our side. Nothing about it is your request's fault; retrying is reasonable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`unavailable` — The engine's validation service is unreachable. Never treated as 'valid' — a spec is not stored on a guess.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string","enum":["bad_request","unauthorized","tier_limit","insufficient_scope","not_found","spec_invalid","rate_limited","internal","unavailable"]},"detail":{"type":"string","description":"Human-readable. Never machine-parsed; branch on `error`."}},"required":["error","detail"],"additionalProperties":false}},"securitySchemes":{"ApiKeyAuth":{"type":"http","scheme":"bearer","description":"An API key minted at `/settings/api-keys`. Shown once; store it then."}}},"security":[{"ApiKeyAuth":[]}],"x-scopes":{"catalog:read":{"description":"Read the block catalog and validate specs","tools":["GET /api/v1/catalog","POST /api/v1/specs/validate"]},"strategies:read":{"description":"List and read your saved strategies","tools":["GET /api/v1/strategies","GET /api/v1/strategies/{id}"]},"strategies:write":{"description":"Save new strategies","tools":["POST /api/v1/strategies"]},"runs:read":{"description":"List and read your runs, their results and their metrics","tools":["GET /api/v1/runs","GET /api/v1/runs/{id}","GET /api/v1/runs/{id}/results","GET /api/v1/runs/{id}/metrics"]},"runs:write":{"description":"Launch backtests (subject to your subscription limits)","tools":["POST /api/v1/runs"]}},"x-rate-limits":{"window_seconds":60,"subjects":["key","account"],"per_tier":{"free":60,"pro":600,"team":600},"boundary_burst":"A fixed window admits up to 2x the limit across a boundary. Documented property, not a defect."},"x-curl-examples":[{"tool":"catalog","title":"1. See what the engine can do","note":"Every indicator and condition, projected from the engine's own registry. Call this before composing a spec.","command":"curl -H \"Authorization: Bearer $CRYPTO_COMP_API_KEY\" $CRYPTO_COMP_BASE_URL/api/v1/catalog"},{"tool":"validate_spec","title":"2. Check a spec before you save it","note":"Always 200, even for an invalid spec: the body is a verdict, and an invalid one carries structured errors you can fix and re-submit.","command":"curl -H \"Authorization: Bearer $CRYPTO_COMP_API_KEY\" -H \"Content-Type: application/json\" -d '{\"spec\":{\"version\":\"0.1\",\"name\":\"Simple RSI reversion\",\"symbol\":\"BTCUSDT\",\"base_timeframe\":\"15m\",\"indicators\":{\"rsi\":{\"type\":\"RSI\",\"inputs\":{\"length\":14,\"source\":\"close\"}}},\"entry\":{\"long\":{\"op\":\"crosses_below\",\"left\":\"rsi\",\"right\":30}},\"exit\":{\"long\":{\"op\":\"crosses_above\",\"left\":\"rsi\",\"right\":70}}}}' $CRYPTO_COMP_BASE_URL/api/v1/specs/validate"},{"tool":"save_strategy","title":"3. Save it","note":"201 with {\"strategy_id\"}. The spec is validated again server-side; an invalid one is 422 and nothing is stored.","command":"curl -H \"Authorization: Bearer $CRYPTO_COMP_API_KEY\" -H \"Content-Type: application/json\" -d '{\"name\":\"RSI reversion\",\"spec\":{\"version\":\"0.1\",\"name\":\"Simple RSI reversion\",\"symbol\":\"BTCUSDT\",\"base_timeframe\":\"15m\",\"indicators\":{\"rsi\":{\"type\":\"RSI\",\"inputs\":{\"length\":14,\"source\":\"close\"}}},\"entry\":{\"long\":{\"op\":\"crosses_below\",\"left\":\"rsi\",\"right\":30}},\"exit\":{\"long\":{\"op\":\"crosses_above\",\"left\":\"rsi\",\"right\":70}}}}' $CRYPTO_COMP_BASE_URL/api/v1/strategies"},{"tool":"launch_run","title":"4. Launch a backtest","note":"201 with {\"run_id\"}. Returns immediately — the run is queued, not finished. `initial_balance` is a decimal STRING; a number is a 400.","command":"curl -H \"Authorization: Bearer $CRYPTO_COMP_API_KEY\" -H \"Content-Type: application/json\" -d '{\"strategy_id\":\"$STRATEGY_ID\",\"spec\":{\"version\":\"0.1\",\"name\":\"Simple RSI reversion\",\"symbol\":\"BTCUSDT\",\"base_timeframe\":\"15m\",\"indicators\":{\"rsi\":{\"type\":\"RSI\",\"inputs\":{\"length\":14,\"source\":\"close\"}}},\"entry\":{\"long\":{\"op\":\"crosses_below\",\"left\":\"rsi\",\"right\":30}},\"exit\":{\"long\":{\"op\":\"crosses_above\",\"left\":\"rsi\",\"right\":70}}},\"run_config\":{\"symbol\":\"BTCUSDT\",\"base_interval\":\"15m\",\"timeframes\":[\"15m\"],\"start\":\"2026-06-15T00:00:00Z\",\"end\":\"2026-06-17T00:00:00Z\",\"initial_balance\":\"10000.00000000\"}}' $CRYPTO_COMP_BASE_URL/api/v1/runs"},{"tool":"get_run","title":"5. Poll it","note":"`status` moves queued -> running -> succeeded (or failed). A run that has not finished is a STATUS, not an error.","command":"curl -H \"Authorization: Bearer $CRYPTO_COMP_API_KEY\" $CRYPTO_COMP_BASE_URL/api/v1/runs/$RUN_ID"},{"tool":"get_metrics","title":"6. Read the numbers","note":"The whole performance suite. Every ratio is a decimal STRING; the three counts and `base_timeframe` are not.","command":"curl -H \"Authorization: Bearer $CRYPTO_COMP_API_KEY\" $CRYPTO_COMP_BASE_URL/api/v1/runs/$RUN_ID/metrics"},{"tool":"get_results","title":"7. Read everything","note":"The full stored result: summary, both equity curves, every fill, the signal log, and — for a futures run — the funding ledger, its net total, any liquidations and the execution stamp (market, account type, leverage, margin mode). Large — prefer /metrics when you only need the figures. `result.version` is the reader's branch point: \"0.1\" predates the metric suite, \"0.2\" predates futures, \"0.3\" carries everything. Stored results are immutable and are never recomputed, so treat every field after `signal_log` as optional.","command":"curl -H \"Authorization: Bearer $CRYPTO_COMP_API_KEY\" $CRYPTO_COMP_BASE_URL/api/v1/runs/$RUN_ID/results"},{"tool":"list_strategies","title":"List your strategies","note":"Newest first, specs omitted. Only ever your own.","command":"curl -H \"Authorization: Bearer $CRYPTO_COMP_API_KEY\" $CRYPTO_COMP_BASE_URL/api/v1/strategies"},{"tool":"get_strategy","title":"Read one strategy","note":"Including its full spec. Another account's id is 404, never 403.","command":"curl -H \"Authorization: Bearer $CRYPTO_COMP_API_KEY\" $CRYPTO_COMP_BASE_URL/api/v1/strategies/$STRATEGY_ID"},{"tool":"list_runs","title":"List your runs","note":"Optionally filtered to one of your strategies. A strategy id that is not yours yields an empty list, not an error.","command":"curl -H \"Authorization: Bearer $CRYPTO_COMP_API_KEY\" \"$CRYPTO_COMP_BASE_URL/api/v1/runs?strategy_id=$STRATEGY_ID\""}]}