{"openapi":"3.1.0","info":{"title":"Verilo Developer API","description":"The public Verilo developer API. Every endpoint below is authenticated with your per-developer API key sent as the `x-api-key` header (obtain one from `POST /v1/developers`). This is a CURATED surface: it publishes the developer and provider self-service API \u2014 including a provider's own payout, payout-controls, and Stripe-account endpoints (`/payouts/*`, `/providers/{id}/payouts`, `/providers/{id}/controls`, `/providers/{id}/stripe-account`). Internal admin, operator, treasury, compliance, and fleet routes are deliberately not published here.\n\n## Authentication\n\nOne header, on every authenticated call:\n\n```\nx-api-key: vk_\u2026\n```\n\n**There is no `Authorization: Bearer` scheme.** A bearer header is ignored; the request is then a request with no key, and fails `401`. There is no OAuth flow and no token to refresh. The staff/session bearer tokens you may see elsewhere in the product are not part of this API.\n\n### 401 vs 403 \u2014 they mean different things\n\n| Status | Meaning | What to change |\n|---|---|---|\n| `401` | **Who you are was not established.** The key was missing, unknown, or revoked. All three answer the same 401 on purpose \u2014 telling them apart would tell an attacker whether a key they hold is real. | Send a valid `x-api-key`. Retrying the same key will not help. |\n| `403` | **You were identified, and are not allowed to do this.** The key resolved to a developer, but the key lacks the required scope (e.g. `webhooks.write`), or the operation is closed to you (e.g. `POST /v1/developers` when signups are closed, or any write under `/v1/sandbox/*`). | Issue a key with the scope, or ask for access. Retrying is pointless. |\n\n`404` is also used where a `403` would leak: an account, webhook or delivery owned by a different developer is reported as not existing rather than as forbidden.\n\n### Getting a key\n\n`POST /v1/developers` mints your first key and is the only unauthenticated endpoint here \u2014 but see **Registration is gated** below: on production it is currently closed. Once you have one key, `POST /v1/keys` issues more (optionally scoped), `POST /v1/keys/{key_id}/rotate` replaces a secret keeping the same scopes, and `DELETE /v1/keys/{key_id}` revokes. Every secret is shown exactly once, in the response that creates it: only a SHA-256 hash and the first 12 characters are stored, so it cannot be shown again.\n\n### The sandbox needs no key at all\n\n`/v1/sandbox/*` is unauthenticated. Send **no** auth header \u2014 there is no sandbox token and never was. It is read-only (every write answers `403 sandbox_read_only`) and limited to 60 requests per minute per source IP.\n\n## Path prefixes: `/v1` is not a general rule\n\n**Two prefixes are live at once, and which one a route uses is history, not meaning.** Newer surfaces were added under `/v1/\u2026`; the original ones \u2014 `/accounts/*`, `/copy/*`, `/providers/*`, `/me/*`, `/eas/*`, `/payouts/*`, `/support/*` \u2014 have no prefix and are equally current. There is no rule that maps one to the other, and adding or removing `/v1` from a path you already have does not give you a second, equivalent path: it gives you a 404.\n\nTwo paths in this document are deliberately *near*-identical and are different resources: `GET /accounts/{account_id}/trades` is the order command log, while `GET /v1/accounts/{account_id}/trades` is round trips derived from sealed legs. Assuming they are the same endpoint under two names will silently give you the wrong data.\n\nSo, the rule: **copy paths from this document, never by analogy.** The documented mistake this warning exists for was `GET /v1/accounts/{id}/trust` \u2014 invented by analogy from other `/v1` routes, repeated in six places, copy-pasted into a quickstart, and a 404 every time. The real path is `GET /accounts/{id}/trust`.\n\n(Aliasing the older paths under `/v1` is a possible future change. Renaming them is not: these routes are in production use, and a rename would trade a documentation problem for an outage.)\n\n## Registration is gated\n\n`POST /v1/developers` is the only unauthenticated endpoint here, and on the production deployment it currently answers **403 `{\"detail\":\"signups are closed\"}`** to any address that is not allow-listed (verified against api.verilo.trade on 2026-08-08). Public self-serve sign-up is not open yet, so a copy-pasted quickstart stops at step 1 until you have been given access.\n\nTwo things open that door, and neither is something you can set yourself: the deployment-wide `DEVELOPER_SIGNUP_OPEN` flag, or your email address being on the allow-list. To be added, ask at support@verilo.trade. Nothing else on this API is gated that way \u2014 once you hold a key, every endpoint here answers.\n\n**You do not need a key to start.** `/v1/sandbox/*` is unauthenticated and reads a real, connected demo account, so you can build and test against real sealed data while you wait.\n\n## Where this document lives, and how a machine finds it\n\nYou should not have to be told any of these URLs. From the bare domain `verilo.trade`, this document is reachable four ways, all serving the same schema:\n\n| URL | What it is |\n|---|---|\n| `/.well-known/api-catalog` | **RFC 9727** linkset (`application/linkset+json`) naming this document under the **RFC 8631** `service-desc` relation. The standards-track answer. |\n| `/.well-known/openapi.json` and `/.well-known/openapi` | The raw schema. Not a registered well-known URI \u2014 there is no such thing for OpenAPI \u2014 but the filenames agents actually probe, so both are served. |\n| `/openapi.json`, `/developers/openapi.json` | The raw schema. |\n| `Link:` header on `/`, and `<link rel=\"service-desc\">` in the site's HTML head | RFC 8631, pointing at the same file. |\n\n`GET /` with `Accept: application/json` returns a short service index naming the spec, the auth header, and the current signup status; a browser still gets the site. `/llms.txt` is the same map as plain text. `/developers/docs` (this page) and `/developers/redoc` are the human reading surfaces; `/docs`, `/docs/api` and `/redoc` redirect to them.\n\n**Base URL.** `servers` above says `https://api.verilo.trade`, and that is not decoration. `verilo.trade` serves a single-page app and routes only the documentation paths to this API \u2014 calling `/v1/...` there returns the HTML shell with a `200`, which will fail inside your JSON parser rather than at the request.\n\n## Live quote stream (WebSocket)\n\n**This does not appear in the endpoint list below, and cannot: OpenAPI has no way to describe a WebSocket.** It is documented here because a reference that silently omits a whole transport is a reference that misleads by completeness.\n\n```\nwss://api.verilo.trade/accounts/{account_id}/stream\n```\n\n**Authenticate with the `x-api-key` header**, exactly like every endpoint below. There is no query-parameter form: `?api_key=` is ignored and the handshake then fails. A rejected handshake appears as HTTP `403` (we close with `4401` before accepting, which renders as `403`) and means a bad, missing or non-owning key \u2014 not that streaming is unavailable. Browsers cannot set WebSocket headers, so this endpoint is server-side only.\n\nEvery frame is an ENVELOPE: `{\"kind\": \"...\", \"payload\": {...}}` \u2014 the fields are NOT at the top level, and `msg[\"symbol\"]` raises KeyError on the first message. `kind` is `tick`, `snapshot` (account state) or `status` (connection changes). For a tick, `payload` carries `symbol`, `bid`, `ask` and `time_msc` (the BROKER's server-clock stamp in UTC milliseconds, not our receipt time).\n\n**Ticks are off until you turn them on.** `PATCH /accounts/{id}` with `{\"tick_stream\": true}`, then re-create the terminal \u2014 the flag reaches it as container environment at start-up, so a running terminal will not pick it up. Four things are true and worth knowing before you do:\n\n* **It roughly doubles that terminal's CPU.** Measured on one pod: ~13% idle versus ~28% with quotes flowing. Leave it off where nobody is watching.\n* **One symbol per account** \u2014 whatever the terminal is charted on. You do not choose it.\n* **It is not a market-data feed.** There is no subscribe. If you need a quote for a symbol this account is not charted on, this will not give you one, and no combination of parameters will change that.\n* **MT5 only.** MT4 and cTrader have no quote producer, so the flag is refused there rather than accepted and silently ignored.\n\nFor prices you can poll instead, use `GET /accounts/{id}/candles` (closed bars, `n` up to 5000) \u2014 a different question with a different cost, and it needs no flag.","contact":{"name":"Verilo support","email":"support@verilo.trade"},"version":"1.0.0"},"servers":[{"url":"https://api.verilo.trade","description":"Production"}],"paths":{"/accounts/{account_id}/ctrader/authorize":{"get":{"summary":"Start the cTrader OAuth flow","description":"Returns the URL to send the user to. Unlike an OANDA token, a cTrader connection is approved for Verilo specifically and can be revoked for Verilo alone.\n\nThe account stays at `PROVISIONING` until the user completes the flow and the callback lands -- there is no pod to start for a cTrader account.","operationId":"ctrader_authorize_accounts__account_id__ctrader_authorize_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CtraderAuthorize"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"cTrader OAuth is not configured on this deployment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/accounts/{account_id}/oanda/disclosure":{"get":{"summary":"What an OANDA token actually grants","description":"**Read this before collecting a token.** OANDA issues ONE access token that can do anything on the account, including moving money between the user's own OANDA accounts -- there is no narrower scope to ask for. That is OANDA's design, not ours.\n\nServed as structured data rather than prose so a client cannot build the connect flow without also showing the disclosure.","operationId":"oanda_disclosure_accounts__account_id__oanda_disclosure_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenCustodyDisclosure"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/accounts/{account_id}/oanda/connect":{"post":{"summary":"Connect an OANDA account","description":"`v20_account_id` is the sub-account id (`001-001-1234567-001`), **not** the login -- one OANDA login owns several v20 accounts, and picking the wrong one trades a different balance.\n\nPractice by default; a live account must be asked for explicitly.\n\nThe token is stored encrypted BEFORE the adapter starts, so a failed start leaves the credential on file to correct rather than making the user paste it again. It is never shown back, logged, or put in an error message.\n\n**202, not 200:** the adapter start is asynchronous. Poll the account state.","operationId":"oanda_connect_accounts__account_id__oanda_connect_post","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectOanda"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OandaConnected"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"`v20_account_id` and `token` are both required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"502":{"description":"Could not connect to OANDA with those credentials. The broker's error text is deliberately NOT echoed -- an auth failure message is the most likely thing to end up in a bug report.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"OANDA support is not configured on this deployment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"delete":{"summary":"Disconnect OANDA and forget the token","description":"Stops the adapter and deletes our stored copy of the token.\n\n**Deleting our copy is not the same as revoking it at OANDA.** The token still exists and still works for anyone else holding it -- the user must delete it in their OANDA portal. Returns no body.","operationId":"oanda_disconnect_accounts__account_id__oanda_connect_delete","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"OANDA support is not configured on this deployment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/marketplace/providers":{"get":{"summary":"Discover providers","description":"Browse and filter publicly-listed providers, ordered by the same ranking score the leaderboard uses.\n\n`min_gross_return_pct` filters on the **gross** figure -- the same one the card shows and the ranking uses. Filtering on net while displaying gross would quietly make a developer's 'minimum return' mean a different number than the one they saw.\n\n`max_drawdown_pct` compares against the drawdown's MAGNITUDE, so pass an unsigned percentage.\n\nOnly `active` and `paused` listings appear; publication is the provider's own consent.","operationId":"discover_providers_marketplace_providers_get","parameters":[{"name":"tier","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"verification_tier exact match","title":"Tier"},"description":"verification_tier exact match"},{"name":"min_gross_return_pct","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"minimum GROSS return (percent) -- price-based, excluding broker-specific commission and swap, matching the displayed and ranked figure","title":"Min Gross Return Pct"},"description":"minimum GROSS return (percent) -- price-based, excluding broker-specific commission and swap, matching the displayed and ranked figure"},{"name":"max_drawdown_pct","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"max drawdown MAGNITUDE (percent, unsigned)","title":"Max Drawdown Pct"},"description":"max drawdown MAGNITUDE (percent, unsigned)"},{"name":"max_price_cents","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"title":"Max Price Cents"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"display-name substring","title":"Q"},"description":"display-name substring"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderDiscoveryPage"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Request body or query parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/marketplace/providers/{provider_id}":{"get":{"summary":"Get one provider's public stats","description":"The same public projection discovery returns. Tenant-internal fields -- the backing account id and the payout destination -- are never included.\n\nFor the full sealed record behind these numbers, call `GET /v1/providers/{provider_id}/track-record`.","operationId":"get_marketplace_provider_marketplace_providers__provider_id__get","parameters":[{"name":"provider_id","in":"path","required":true,"schema":{"type":"string","title":"Provider Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicProvider"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such provider, or its listing is not public (`draft` or `revoked`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/providers":{"post":{"summary":"Create a provider listing","description":"Turns one of your accounts into a copyable provider. The listing starts unpublished -- set it `active` with `POST /providers/{provider_id}/status` when you want it discoverable.","operationId":"create_provider_providers_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProvider"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OwnedProvider"}}}},"400":{"description":"`price_cents` is below the platform floor.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"The backing account is not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Request body or query parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/providers/{provider_id}/segments":{"post":{"summary":"Attach a new broker account (the honest broker-switch path)","description":"**This is what you do instead of re-pointing credentials.** A sealed account's login and server can never be changed, because that would merge two brokers' histories into one chain. A provider who genuinely changes brokers adds the new account here.\n\nThe old broker's sealed chain is left untouched under its own account, a fresh chain begins under the new one, and the provider's aggregate track record spans BOTH -- metrics roll up across segments while the chains stay separate. The active segment moves to the new account, ending the previous one.\n\nYou must own both the provider and the new account.","operationId":"add_provider_segment_providers__provider_id__segments_post","parameters":[{"name":"provider_id","in":"path","required":true,"schema":{"type":"string","title":"Provider Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddProviderSegment"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderSegment"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"The provider or the new account is not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"Well-formed, but conflicts with state that already exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Request body or query parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/providers/{provider_id}/status":{"post":{"summary":"Publish, pause or revoke a listing","description":"`active` and `paused` are publicly discoverable; `draft` and `revoked` are not. Publishing is the provider's own consent to being listed, so nothing appears in discovery until this is called.","operationId":"set_provider_status_providers__provider_id__status_post","parameters":[{"name":"provider_id","in":"path","required":true,"schema":{"type":"string","title":"Provider Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetProviderStatus"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OwnedProvider"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Request body or query parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/providers/{provider_id}/stripe-account":{"post":{"summary":"Set the Stripe payout destination","description":"**Requires a fresh step-up, not just your API key.** Re-pointing the settlement destination is a fund-redirection event, so a long-lived key alone can never do it.\n\nThis records the destination id only. The OAuth onboarding that produces a *verified* Stripe Connect account is a separate flow.","operationId":"set_provider_stripe_account_providers__provider_id__stripe_account_post","parameters":[{"name":"provider_id","in":"path","required":true,"schema":{"type":"string","title":"Provider Id"}},{"name":"X-StepUp-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Stepup-Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetProviderStripeAccount"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OwnedProvider"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"A fresh money-action step-up is required (`{\"code\": \"step_up_required\", \"action\": \"payout.method_setup\"}`). Complete it and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Request body or query parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/providers/{provider_id}/controls":{"post":{"summary":"Set copier cap and broker exclusions","description":"`max_copiers` caps how many followers may copy you; omit it (null) for uncapped. Once the cap is reached, new subscribers are **waitlisted with a 202** rather than rejected.\n\n`excluded_brokers` is normalized and de-duplicated on write, so it is stored exactly as the subscribe gate compares it.","operationId":"set_provider_controls_providers__provider_id__controls_post","parameters":[{"name":"provider_id","in":"path","required":true,"schema":{"type":"string","title":"Provider Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetProviderControls"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OwnedProvider"}}}},"400":{"description":"`max_copiers` must be zero or greater.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Request body or query parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/subscriptions":{"post":{"summary":"Subscribe to a provider","description":"Creates a PENDING subscription and a Stripe checkout session. **Nothing is copied until the follower completes checkout.**\n\n* **201** -- subscription created; send the follower to `checkout.url`.\n* **202** -- the provider is full; the follower was waitlisted instead. Check for `waitlisted` before assuming success.\n\nThe follower identity always comes from your API key, never from the request body, so a key holder cannot create subscriptions under someone else's identity.\n\nProvider broker exclusions are enforced fail-closed: if the provider requires a verified broker and no owned `follower_account_id` was supplied, the request is refused.","operationId":"create_subscription_subscriptions_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSubscriptionBody"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionCreated"}}}},"202":{"description":"The provider is at its copier cap, so the follower was WAITLISTED. No subscription and no checkout were created -- do not treat this as a success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Waitlisted"}}}},"400":{"description":"The provider is not connected to Stripe, or the plan is invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"This provider excludes your broker.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such provider.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Request body or query parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/providers/{provider_id}/payouts":{"get":{"summary":"Your provider payout ledger","description":"Individual entries plus their rollup, for a provider you own.","operationId":"provider_payouts_providers__provider_id__payouts_get","parameters":[{"name":"provider_id","in":"path","required":true,"schema":{"type":"string","title":"Provider Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderPayouts"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/alerts":{"get":{"summary":"Your marketplace alerts","description":"Alerts for the CALLING key's own follower identity. There is deliberately no `follower_ref` parameter -- it is taken from your key, so one key holder can never read another follower's alerts.\n\nAlert payloads vary by alert kind and are documented as open objects rather than given an invented fixed shape.","operationId":"list_alerts_alerts_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/OpenObject"},"title":"Response List Alerts Alerts Get"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/sandbox/symbols":{"get":{"summary":"Sandbox symbols","description":"The curated symbol set the sandbox serves.\n\n**No API key required.** The sandbox is a shared public surface: it is strictly read-only, exposes only one fixed demo account's observed public data, and every write under `/v1/sandbox/*` is refused with 403.\n\n**Rate limit: 60 requests per minute, per source IP** (not per key -- there is no key). Over the limit returns 429.\n\n`503 sandbox_coming_soon` means the demo account is not connected right now; it is a temporary state, not a client error.","operationId":"sandbox_symbols_v1_sandbox_symbols_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxSymbols"}}}},"429":{"description":"Over 60 requests/minute from this IP.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[]}},"/v1/sandbox/quotes/{symbol}":{"get":{"summary":"Latest sandbox quote for a symbol","description":"A real observed tick from the demo account's feed -- not simulated, and not a synthetic price.\n\n**No API key required.** The sandbox is a shared public surface: it is strictly read-only, exposes only one fixed demo account's observed public data, and every write under `/v1/sandbox/*` is refused with 403.\n\n**Rate limit: 60 requests per minute, per source IP** (not per key -- there is no key). Over the limit returns 429.\n\n`503 sandbox_coming_soon` means the demo account is not connected right now; it is a temporary state, not a client error.","operationId":"sandbox_quote_v1_sandbox_quotes__symbol__get","parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxQuote"}}}},"404":{"description":"Not a sandbox symbol -- see `GET /v1/sandbox/symbols`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Over 60 requests/minute from this IP.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"No quote has been observed for this symbol yet (`no_quote_yet`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[]}},"/v1/sandbox/history/{symbol}":{"get":{"summary":"Sandbox candle history","description":"`limit` is clamped to 1-500; out-of-range values are clamped, not rejected.\n\n**No API key required.** The sandbox is a shared public surface: it is strictly read-only, exposes only one fixed demo account's observed public data, and every write under `/v1/sandbox/*` is refused with 403.\n\n**Rate limit: 60 requests per minute, per source IP** (not per key -- there is no key). Over the limit returns 429.\n\n`503 sandbox_coming_soon` means the demo account is not connected right now; it is a temporary state, not a client error.","operationId":"sandbox_history_v1_sandbox_history__symbol__get","parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":100,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxHistory"}}}},"404":{"description":"Not a sandbox symbol.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Over 60 requests/minute from this IP.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[]}},"/v1/sandbox/account":{"get":{"summary":"Is the sandbox demo account live?","description":"Deliberately returns **200 with `available: false`** rather than an error when the demo account is not connected, so a portal can show a friendly state. Check `available` before calling the other live sandbox reads.\n\n**No API key required.** The sandbox is a shared public surface: it is strictly read-only, exposes only one fixed demo account's observed public data, and every write under `/v1/sandbox/*` is refused with 403.\n\n**Rate limit: 60 requests per minute, per source IP** (not per key -- there is no key). Over the limit returns 429.\n\n`503 sandbox_coming_soon` means the demo account is not connected right now; it is a temporary state, not a client error.","operationId":"sandbox_account_info_v1_sandbox_account_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxAccountInfo"}}}},"429":{"description":"Over 60 requests/minute from this IP.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[]}},"/v1/sandbox/positions":{"get":{"summary":"Open positions on the demo account","description":"Real open positions on a real connected demo account.\n\n**No API key required.** The sandbox is a shared public surface: it is strictly read-only, exposes only one fixed demo account's observed public data, and every write under `/v1/sandbox/*` is refused with 403.\n\n**Rate limit: 60 requests per minute, per source IP** (not per key -- there is no key). Over the limit returns 429.\n\n`503 sandbox_coming_soon` means the demo account is not connected right now; it is a temporary state, not a client error.","operationId":"sandbox_positions_v1_sandbox_positions_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxPositions"}}}},"429":{"description":"Over 60 requests/minute from this IP.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"The demo account is not connected yet (`sandbox_coming_soon`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[]}},"/v1/sandbox/deals":{"get":{"summary":"Executed deals on the demo account","description":"Real executed history with realized P&L. `limit` is clamped to 1-500; `total` is the count before clamping.\n\n**No API key required.** The sandbox is a shared public surface: it is strictly read-only, exposes only one fixed demo account's observed public data, and every write under `/v1/sandbox/*` is refused with 403.\n\n**Rate limit: 60 requests per minute, per source IP** (not per key -- there is no key). Over the limit returns 429.\n\n`503 sandbox_coming_soon` means the demo account is not connected right now; it is a temporary state, not a client error.","operationId":"sandbox_deals_v1_sandbox_deals_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":100,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxDeals"}}}},"429":{"description":"Over 60 requests/minute from this IP.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"The demo account is not connected yet (`sandbox_coming_soon`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[]}},"/v1/sandbox/equity":{"get":{"summary":"Equity curve of the demo account","description":"`limit` is clamped to 1-500.\n\n**No API key required.** The sandbox is a shared public surface: it is strictly read-only, exposes only one fixed demo account's observed public data, and every write under `/v1/sandbox/*` is refused with 403.\n\n**Rate limit: 60 requests per minute, per source IP** (not per key -- there is no key). Over the limit returns 429.\n\n`503 sandbox_coming_soon` means the demo account is not connected right now; it is a temporary state, not a client error.","operationId":"sandbox_equity_v1_sandbox_equity_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":200,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxEquity"}}}},"429":{"description":"Over 60 requests/minute from this IP.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"The demo account is not connected yet (`sandbox_coming_soon`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[]}},"/v1/sandbox/metrics":{"get":{"summary":"Fill-rate + equity summary for the demo account","description":"The same shape as `GET /v1/accounts/{account_id}/metrics`, so what you build against the sandbox works unchanged on your own account.\n\n**No API key required.** The sandbox is a shared public surface: it is strictly read-only, exposes only one fixed demo account's observed public data, and every write under `/v1/sandbox/*` is refused with 403.\n\n**Rate limit: 60 requests per minute, per source IP** (not per key -- there is no key). Over the limit returns 429.\n\n`503 sandbox_coming_soon` means the demo account is not connected right now; it is a temporary state, not a client error.","operationId":"sandbox_metrics_v1_sandbox_metrics_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountMetrics"}}}},"429":{"description":"Over 60 requests/minute from this IP.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"The demo account is not connected yet (`sandbox_coming_soon`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[]}},"/v1/sandbox/seal":{"get":{"summary":"The demo account's tamper-evident seal chain","description":"The core demonstration: a real account's hash-chained verified record, the recomputed `chain_intact` verdict, and a `verify_url` that re-checks the digest with **no login at all**. Follow it -- the point is that you do not have to trust this response.\n\n**No API key required.** The sandbox is a shared public surface: it is strictly read-only, exposes only one fixed demo account's observed public data, and every write under `/v1/sandbox/*` is refused with 403.\n\n**Rate limit: 60 requests per minute, per source IP** (not per key -- there is no key). Over the limit returns 429.\n\n`503 sandbox_coming_soon` means the demo account is not connected right now; it is a temporary state, not a client error.","operationId":"sandbox_seal_v1_sandbox_seal_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxSeal"}}}},"429":{"description":"Over 60 requests/minute from this IP.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"The demo account is not connected yet (`sandbox_coming_soon`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[]}},"/v1/sandbox/trust":{"get":{"summary":"Full verified record of the demo account","description":"The same document `GET /accounts/{account_id}/trust` returns for your own account, on the public demo account.\n\n**No API key required.** The sandbox is a shared public surface: it is strictly read-only, exposes only one fixed demo account's observed public data, and every write under `/v1/sandbox/*` is refused with 403.\n\n**Rate limit: 60 requests per minute, per source IP** (not per key -- there is no key). Over the limit returns 429.\n\n`503 sandbox_coming_soon` means the demo account is not connected right now; it is a temporary state, not a client error.","operationId":"sandbox_trust_v1_sandbox_trust_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrustReport"}}}},"429":{"description":"Over 60 requests/minute from this IP.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"The demo account is not connected yet (`sandbox_coming_soon`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[]}},"/v1/sandbox/{rest}":{"post":{"summary":"Any sandbox write (always refused)","description":"A catch-all that refuses **every** POST under `/v1/sandbox/*` with 403, including paths that do not otherwise exist. There is no sandbox order-placement endpoint and there is not going to be one: the demo account is real, and no request to this API can ever place an order on it.\n\nTo place trades, register for a key and use `POST /accounts/{account_id}/trades` against your own account.","operationId":"sandbox_write_forbidden_v1_sandbox__rest__post","parameters":[{"name":"rest","in":"path","required":true,"schema":{"type":"string","title":"Rest"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"403":{"description":"Always. The sandbox is read-only (`sandbox_read_only`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/eas":{"post":{"summary":"Create Ea","operationId":"create_ea_eas_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEA"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"List Eas","operationId":"list_eas_eas_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/eas/{ea_id}":{"get":{"summary":"Get Ea","operationId":"get_ea_eas__ea_id__get","parameters":[{"name":"ea_id","in":"path","required":true,"schema":{"type":"string","title":"Ea Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/eas/{ea_id}/source":{"get":{"summary":"Get Ea Source","operationId":"get_ea_source_eas__ea_id__source_get","parameters":[{"name":"ea_id","in":"path","required":true,"schema":{"type":"string","title":"Ea Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/eas/{ea_id}/host":{"post":{"summary":"Host Ea","operationId":"host_ea_eas__ea_id__host_post","parameters":[{"name":"ea_id","in":"path","required":true,"schema":{"type":"string","title":"Ea Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HostEA"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/eas/{ea_id}/stop":{"post":{"summary":"Stop Ea","operationId":"stop_ea_eas__ea_id__stop_post","parameters":[{"name":"ea_id","in":"path","required":true,"schema":{"type":"string","title":"Ea Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/eas/{ea_id}/backtests":{"post":{"summary":"Submit Backtest","operationId":"submit_backtest_eas__ea_id__backtests_post","parameters":[{"name":"ea_id","in":"path","required":true,"schema":{"type":"string","title":"Ea Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitBacktest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/backtests/{bt_id}":{"get":{"summary":"Get Backtest","operationId":"get_backtest_backtests__bt_id__get","parameters":[{"name":"bt_id","in":"path","required":true,"schema":{"type":"string","title":"Bt Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/eas/{ea_id}/list":{"post":{"summary":"List Ea","operationId":"list_ea_eas__ea_id__list_post","parameters":[{"name":"ea_id","in":"path","required":true,"schema":{"type":"string","title":"Ea Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/signals":{"post":{"summary":"Submit a sealed signal","description":"Publish a trade call as a **pre-commitment**: we stamp it with our own receipt time and seal it into a hash chain BEFORE looking at any market data, then snap it against a real reference venue.\n\nThat ordering is the whole point -- a sealed record cannot be back-dated, edited, or quietly dropped if it goes the wrong way.\n\n`fill_state: pending` means no reference tick was found in the window yet. The pre-commitment still stands.","operationId":"submit_signal_v1_signals_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitSignal"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmittedSignal"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"The signal failed validation. This is the LAST point at which anything can be refused -- past it the payload is sealed as submitted and can never be edited.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"The signal rail is not configured on this deployment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/signals/hooks":{"post":{"summary":"Mint a TradingView webhook URL","description":"Returns a URL to paste straight into TradingView's alert dialog, with the credential in the path.\n\n**Shown once.** It is safe to carry a secret in a URL here only because the key is minted with the `signals:submit` scope and nothing else -- it cannot reach accounts, keys, pods or money. Revoke it like any key, with `DELETE /v1/keys/{key_id}`.","operationId":"create_signal_hook_v1_signals_hooks_post","responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignalHook"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"The signal rail is not configured on this deployment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/signals/{signal_id}":{"get":{"summary":"Get one sealed signal","description":"The stored signal record, owner-scoped.\n\nThe body is the sealed record as it was written -- payload, digest and chain linkage -- so its exact keys follow the seal format rather than a fixed API shape. It is documented as an open object deliberately: pinning a schema here would be a promise the seal format does not make.","operationId":"get_signal_v1_signals__signal_id__get","parameters":[{"name":"signal_id","in":"path","required":true,"schema":{"type":"string","title":"Signal Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenObject"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"The signal rail is not configured on this deployment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/accounts/{account_id}/relay-key":{"post":{"summary":"Issue this account's provider relay key","description":"The credential a provider's expert advisor uses to publish signals.\n\n**`relay_key` is shown once, here.** The response also carries a paste-ready `preset` -- save it as `provider.set` into `MQL5/Presets` (or `MQL4/Presets`) rather than retyping four fields into a MetaTrader dialog, because a single typo makes the EA fail closed silently.\n\n**Re-issuing revokes the previous key**, so a provider who loses one gets a working replacement instead of a second live credential on a machine they have forgotten about.","operationId":"issue_relay_key_v1_accounts__account_id__relay_key_post","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IssuedRelayKey"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"The copy relay is not configured on this deployment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/accounts/{account_id}/relay-keys":{"get":{"summary":"List this account's relay keys","description":"Prefixes and lifecycle only -- the secret is not stored and cannot be returned.\n\n`last_seen_at` is the field that matters: it distinguishes a key that is actually in use from one you should revoke.","operationId":"list_relay_keys_v1_accounts__account_id__relay_keys_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/RelayKeyRow"},"title":"Response List Relay Keys V1 Accounts  Account Id  Relay Keys Get"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"The copy relay is not configured on this deployment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/relay-keys/{key_id}":{"delete":{"summary":"Revoke a relay key","description":"Revocation is immediate for new connections: the relay resolves keys per CONNECT, so a revoked key cannot reconnect.\n\n**An expert advisor already holding a live socket keeps it until that socket drops.** Stated plainly because pretending otherwise would be the more dangerous claim. Stop the EA if you need the session dead now.\n\nReturns no body.","operationId":"revoke_relay_key_v1_relay_keys__key_id__delete","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"The copy relay is not configured on this deployment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/accounts/{account_id}/copy-directive":{"put":{"summary":"Set a follower's risk directive","description":"The server half of set-once-then-auto-trade: the follower's expert advisor caches this and enforces every guardrail locally, so sizing and drawdown limits hold even if the control plane is unreachable.\n\nApplied to any connected EA immediately (`pushed: true`) as well as remembered for its next hello -- a risk change does not wait for a reconnect a stable follower never makes.\n\n**Directives are held in memory only for now.** After a control-plane restart the directive is gone and the EA fail-closes until it is PUT again. Safe, but worth knowing.","operationId":"put_copy_directive_v1_accounts__account_id__copy_directive_put","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CopyDirective"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CopyDirectiveResponse"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"`risk_pct` is missing or out of range. It must be greater than 0 -- a non-positive value is the EA's fail-closed sentinel and would create a follower that looks configured and silently copies nothing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"The copy relay is not enabled on this deployment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/brokers/exec/consent":{"post":{"summary":"Opt an account in or out of public execution aggregates","description":"Controls whether this account's fills contribute to the public broker-execution reports. Opt-in by default is never assumed -- consent is explicit.\n\n**Revoking applies to future aggregates only.** Reports already published are not recomputed: a published figure with a stated sample size is a historical statement, and silently rewriting it would make every past report unverifiable.","operationId":"set_consent_v1_brokers_exec_consent_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsentBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExecConsent"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Request body or query parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Broker execution reports are not configured on this deployment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/brokers/exec/consent/{account_id}":{"get":{"summary":"Read an account's execution-aggregate consent","description":"An account that never granted consent reports `granted: false`.","operationId":"get_consent_v1_brokers_exec_consent__account_id__get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExecConsent"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Broker execution reports are not configured on this deployment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/accounts/{account_id}/trades":{"get":{"summary":"Round trips derived from sealed legs","description":"Entry and exit legs paired into round trips.\n\nNote this is a **different resource** from `GET /accounts/{account_id}/trades` (no `/v1`), which is the command log. This one is derived on read and never stored -- a persisted round trip would be a mutable copy of an immutable fact.\n\n`unpairedLegs` is surfaced rather than hidden: a non-zero value means the pairing is incomplete.","operationId":"list_trades_v1_accounts__account_id__trades_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoundTripList"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/accounts/{account_id}/trades/{trade_id}/replay":{"get":{"summary":"Tick-by-tick replay of one trade","description":"The recorded tick series around a trade, with its overlays.\n\n**No capture is not an error.** `available: false` with a `capture.status` tells you whether we never attempted the capture or attempted it and found nothing -- two different facts that would otherwise look identical.\n\nLong series are downsampled by even selection, never by averaging: `reduced`, `pointsShown` and `pointsRecorded` say exactly what you are looking at. `hasGaps` marks a record with holes so it is not drawn as continuous.","operationId":"replay_v1_accounts__account_id__trades__trade_id__replay_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"trade_id","in":"path","required":true,"schema":{"type":"string","title":"Trade Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TradeReplay"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Tick replay is not configured on this deployment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/accounts/{account_id}/journal/analytics":{"get":{"summary":"Journal analytics over sealed trades","description":"Win rate, expectancy, R distribution, hold-time and time-of-day breakdowns, drawdown, and per-tag / per-playbook groupings.\n\n**Every figure is computed from sealed trades only.** Notes, tags and playbooks choose WHICH trades a breakdown covers; they can never change the numbers inside it. That rule is restated on the response itself, in `basis`.","operationId":"analytics_v1_accounts__account_id__journal_analytics_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JournalAnalytics"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/accounts/{account_id}/journal/{deal_id}":{"put":{"summary":"Write or replace a trade's annotation","description":"Notes, rating, playbook and tags for one sealed trade.\n\nThere is deliberately **no way to change a price, size or P&L, and no way to delete or hide a trade** -- those routes do not exist. An annotation that does not belong to a sealed trade is refused, because an annotation floating free of one is the beginning of a self-reported journal.","operationId":"annotate_v1_accounts__account_id__journal__deal_id__put","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"deal_id","in":"path","required":true,"schema":{"type":"string","title":"Deal Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Annotation"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JournalAnnotation"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such account, or that deal is not a sealed trade on it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Request body or query parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"get":{"summary":"Get a trade's annotation","description":"An un-annotated trade returns an empty stub with **200**, not a 404 -- the trade exists, it simply has no notes yet.","operationId":"get_annotation_v1_accounts__account_id__journal__deal_id__get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"deal_id","in":"path","required":true,"schema":{"type":"string","title":"Deal Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JournalAnnotation"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/accounts/{account_id}/journal":{"get":{"summary":"List sealed trades with their annotations","description":"Driven from the SEALED trade list, not from the annotation table: listing annotations would silently omit every trade you have not written about, which is omission by a different route.\n\nOptional `tag` and `playbook_id` filters narrow the selection.","operationId":"list_journal_v1_accounts__account_id__journal_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"tag","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tag"}},{"name":"playbook_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Playbook Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JournalList"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/accounts/{account_id}/journal/{deal_id}/excursions":{"get":{"summary":"MAE / MFE for one trade","description":"How far price went against and in favour of the position while it was open, computed from the ticks recorded for that trade.\n\n**`available: false` with a `reason` is a real answer.** When there is no tick capture we say so rather than falling back to entry/exit prices -- the move between two endpoints is the result, not an excursion, and labelling it MAE/MFE would be a fabricated number wearing a real name.","operationId":"trade_excursions_v1_accounts__account_id__journal__deal_id__excursions_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"deal_id","in":"path","required":true,"schema":{"type":"string","title":"Deal Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TradeExcursions"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/journal/playbooks":{"post":{"summary":"Create a playbook","description":"A named strategy you can assign trades to.","operationId":"create_playbook_v1_journal_playbooks_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaybookBody"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Playbook"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"`name` is required and cannot be blank.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"get":{"summary":"List your playbooks","description":"Your own playbooks, ordered by name. These are the ids accepted by the `playbook_id` filter on the journal list and by `PUT /v1/accounts/{account_id}/journal/{deal_id}`.","operationId":"list_playbooks_v1_journal_playbooks_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaybookList"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/journal/playbooks/{playbook_id}":{"delete":{"summary":"Delete a playbook","description":"Deletes the playbook only. **Trades assigned to it are untouched** -- deleting a strategy must not delete the record of having traded it. Returns no body.","operationId":"delete_playbook_v1_journal_playbooks__playbook_id__delete","parameters":[{"name":"playbook_id","in":"path","required":true,"schema":{"type":"string","title":"Playbook Id"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/accounts/{account_id}/journal/shares":{"post":{"summary":"Share a journal or a single trade","description":"**A journal is private until this is called.** The returned `token` IS the grant: anyone holding the link can read the share, with no account, no login and no key.\n\nRevoke it at any time -- revocation is immediate and is a delete, not a flag. Shared views never expose your account number, broker identity or credentials, and annotations are labelled as your own unverified words.","operationId":"create_share_v1_accounts__account_id__journal_shares_post","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareBody"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedJournalShare"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"`scope` must be `journal` or `trade`; a `trade` share needs a `deal_id`; `expires_at` must be ISO-8601.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/journal/shares":{"get":{"summary":"List everything you have shared","description":"Every live share, so each one can be revoked -- a share nobody can see is a share nobody can revoke.","operationId":"list_shares_v1_journal_shares_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JournalShareList"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/journal/shares/{token}":{"delete":{"summary":"Revoke a share","description":"Immediate, and a real delete rather than a flag flip -- a revoked share cannot be resurrected by a bug that clears the wrong boolean. Returns no body.","operationId":"revoke_share_v1_journal_shares__token__delete","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/me/push-subscriptions/vapid-public-key":{"get":{"summary":"Vapid Public Key","operationId":"vapid_public_key_me_push_subscriptions_vapid_public_key_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/me/push-subscriptions":{"post":{"summary":"Register Subscription","operationId":"register_subscription_me_push_subscriptions_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterSubscription"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"List Subscriptions","operationId":"list_subscriptions_me_push_subscriptions_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/me/push-subscriptions/{sub_id}":{"patch":{"summary":"Rekey Subscription","operationId":"rekey_subscription_me_push_subscriptions__sub_id__patch","parameters":[{"name":"sub_id","in":"path","required":true,"schema":{"type":"string","title":"Sub Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RekeySubscription"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Revoke Subscription","operationId":"revoke_subscription_me_push_subscriptions__sub_id__delete","parameters":[{"name":"sub_id","in":"path","required":true,"schema":{"type":"string","title":"Sub Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/me/push-subscriptions/{sub_id}/click":{"post":{"summary":"Record that a notification was clicked","description":"Delivery-health telemetry, called by the service worker from its `notificationclick` handler. It records that this device acted on a notification; it carries no message content. Owner-scoped like every other device route, so a beacon cannot touch another developer's counters. Returns no body.","operationId":"record_click_me_push_subscriptions__sub_id__click_post","parameters":[{"name":"sub_id","in":"path","required":true,"schema":{"type":"string","title":"Sub Id"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such device registration, or it is not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/me/push-subscriptions/{sub_id}/test":{"post":{"summary":"Send a test notification to one device","description":"Drives the real delivery pipeline \u2014 VAPID signature, payload encryption, POST to the browser's push service \u2014 against exactly this one registration, never the whole fan-out. Use it to prove a device is actually reachable.\n\nA push service answering `404`/`410` means the registration is dead, and it is deleted here rather than left to fail forever.","operationId":"send_test_notification_me_push_subscriptions__sub_id__test_post","parameters":[{"name":"sub_id","in":"path","required":true,"schema":{"type":"string","title":"Sub Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"400":{"description":"The stored endpoint now resolves to a private or non-public host; the registration is deleted rather than POSTed to.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such device registration, or it is not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"502":{"description":"The browser's push service rejected the send. `detail` carries what it said.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/me/notification-preferences/quiet-hours":{"get":{"summary":"Get Quiet Hours","operationId":"get_quiet_hours_me_notification_preferences_quiet_hours_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"summary":"Set Quiet Hours","operationId":"set_quiet_hours_me_notification_preferences_quiet_hours_put","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetQuietHours"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Turn quiet hours off","description":"Removes the quiet-hours window entirely, so notifications are delivered at any time of day. Idempotent: clearing when none is set is still a `204`, not a `404`. After this, `GET /me/notification-preferences/quiet-hours` reports `enabled: false`. Returns no body.","operationId":"clear_quiet_hours_me_notification_preferences_quiet_hours_delete","responses":{"204":{"description":"Successful Response"},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/developers/me/email-log":{"get":{"summary":"List Email Log","operationId":"list_email_log_developers_me_email_log_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/developers/me/email-log/{log_id}":{"get":{"summary":"One email delivery record","description":"The full stored record for a single message we sent on your behalf \u2014 including its current delivery status, which is updated from the provider's bounce/complaint/delivered callbacks after the send.\n\nThe row is looked up with your own developer id, so another developer's message is a `404` rather than a `403`. The response is the stored row as written and its exact keys follow the email log's own schema, so no fixed response shape is declared for it here.","operationId":"get_email_log_developers_me_email_log__log_id__get","parameters":[{"name":"log_id","in":"path","required":true,"schema":{"type":"string","title":"Log Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such log entry, or it belongs to another developer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/privacy/consent/me":{"post":{"summary":"Record your own consent decision","description":"Logs a consent decision per processing category, against a policy version.\n\nA `Sec-GPC` header is honoured: a Global Privacy Control signal is applied and recorded, not ignored. Consent is append-only -- each call adds a row, and the history stays readable.","operationId":"record_consent_self_v1_privacy_consent_me_post","parameters":[{"name":"sec-gpc","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sec-Gpc"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordConsentSelf"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsentState"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Request body or query parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"get":{"summary":"Your effective consent state","description":"**Check `has_recorded_consent` first.** When it is false you are looking at region-appropriate DEFAULTS, not a decision anyone made, and the banner should still be shown.","operationId":"get_consent_self_v1_privacy_consent_me_get","parameters":[{"name":"region","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Region"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsentState"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/privacy/consent/me/history":{"get":{"summary":"Your full consent history","description":"Every recorded decision, newest first. Consent is append-only: a later choice never erases the record of an earlier one.","operationId":"get_consent_self_history_v1_privacy_consent_me_history_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ConsentState"},"title":"Response Get Consent Self History V1 Privacy Consent Me History Get"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/privacy/dsar/export":{"post":{"summary":"Export your personal data (DSAR)","description":"Runs a subject access request immediately and returns the bundle along with the logged request record.","operationId":"dsar_export_v1_privacy_dsar_export_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DsarExport"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/privacy/dsar/delete":{"post":{"summary":"Erase your personal data (DSAR)","description":"Anonymises direct-identifier PII on your developer record and revokes every API key.\n\n**This is not full erasure, and the response says so.** Trading accounts, trade and deal history, and payout/subscription ledgers are RETAINED -- regulatory recordkeeping obligations outweigh an erasure request for that data. `result.retained_for_legal_reasons` lists exactly what is kept.\n\nIdempotent: calling twice is safe.","operationId":"dsar_delete_v1_privacy_dsar_delete_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DsarRequest"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/privacy/dsar":{"get":{"summary":"List your DSAR requests","operationId":"dsar_list_v1_privacy_dsar_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/DsarRequest"},"title":"Response Dsar List V1 Privacy Dsar Get"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/privacy/dsar/{request_id}":{"get":{"summary":"Get one DSAR request","operationId":"dsar_get_v1_privacy_dsar__request_id__get","parameters":[{"name":"request_id","in":"path","required":true,"schema":{"type":"string","title":"Request Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DsarRequest"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ai/translate":{"post":{"summary":"Ai Translate","operationId":"ai_translate_ai_translate_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranslateBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ai/support/reply":{"post":{"summary":"Ai Support Reply","operationId":"ai_support_reply_ai_support_reply_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupportReplyBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ai/studio/draft-spec":{"post":{"summary":"Ai Draft Spec","operationId":"ai_draft_spec_ai_studio_draft_spec_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftSpecBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/referral/code":{"post":{"summary":"Get Or Create Code","operationId":"get_or_create_code_referral_code_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/referral/dashboard":{"get":{"summary":"Dashboard","operationId":"dashboard_referral_dashboard_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/affiliate/me":{"get":{"summary":"Me","operationId":"me_affiliate_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/affiliate/dashboard":{"get":{"summary":"Dashboard","operationId":"dashboard_affiliate_dashboard_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/payouts/step-up/challenge":{"post":{"summary":"Step Up Challenge","operationId":"step_up_challenge_payouts_step_up_challenge_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StepUpChallengeBody"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/payouts/step-up/verify":{"post":{"summary":"Step Up Verify","operationId":"step_up_verify_payouts_step_up_verify_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StepUpVerifyBody"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/payouts/methods":{"post":{"summary":"Register Method","operationId":"register_method_payouts_methods_post","parameters":[{"name":"X-StepUp-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Stepup-Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterMethodBody"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"List Methods","operationId":"list_methods_payouts_methods_get","parameters":[{"name":"provider_id","in":"query","required":true,"schema":{"type":"string","title":"Provider Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/payouts/balance":{"get":{"summary":"Balance","operationId":"balance_payouts_balance_get","parameters":[{"name":"provider_id","in":"query","required":true,"schema":{"type":"string","title":"Provider Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/payouts/disbursements":{"post":{"summary":"Request Payout","operationId":"request_payout_payouts_disbursements_post","parameters":[{"name":"X-StepUp-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Stepup-Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestPayoutBody"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"List Disbursements","operationId":"list_disbursements_payouts_disbursements_get","parameters":[{"name":"provider_id","in":"query","required":true,"schema":{"type":"string","title":"Provider Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/flags/evaluate":{"get":{"summary":"Evaluate All","operationId":"evaluate_all_flags_evaluate_get","parameters":[{"name":"region","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Region"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/flags/{key}/evaluate":{"get":{"summary":"Evaluate One","operationId":"evaluate_one_flags__key__evaluate_get","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","title":"Key"}},{"name":"region","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Region"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/me/digests/{iso_week}":{"get":{"summary":"Me Digest","operationId":"me_digest_me_digests__iso_week__get","parameters":[{"name":"iso_week","in":"path","required":true,"schema":{"type":"string","title":"Iso Week"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/me/digests":{"get":{"summary":"Me Digests List","operationId":"me_digests_list_me_digests_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":26,"minimum":1,"default":8,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/support/tickets":{"post":{"summary":"Create Ticket","operationId":"create_ticket_support_tickets_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTicketBody"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"List Tickets","operationId":"list_tickets_support_tickets_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/support/tickets/{ticket_id}":{"get":{"summary":"Get Ticket","operationId":"get_ticket_support_tickets__ticket_id__get","parameters":[{"name":"ticket_id","in":"path","required":true,"schema":{"type":"string","title":"Ticket Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/support/tickets/{ticket_id}/messages":{"post":{"summary":"Reply Ticket","operationId":"reply_ticket_support_tickets__ticket_id__messages_post","parameters":[{"name":"ticket_id","in":"path","required":true,"schema":{"type":"string","title":"Ticket Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReplyBody"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/support/tickets/{ticket_id}/vera":{"post":{"summary":"Ask Vera In Ticket","operationId":"ask_vera_in_ticket_support_tickets__ticket_id__vera_post","parameters":[{"name":"ticket_id","in":"path","required":true,"schema":{"type":"string","title":"Ticket Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VeraAskBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/support/tickets/{ticket_id}/status":{"post":{"summary":"Set Ticket Status","operationId":"set_ticket_status_support_tickets__ticket_id__status_post","parameters":[{"name":"ticket_id","in":"path","required":true,"schema":{"type":"string","title":"Ticket Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetStatusBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/analytics/business-events":{"post":{"summary":"Record a server-side business event","description":"Consent-independent capture of critical business events.\n\nThe subject is always YOUR developer id, taken from the key -- it is never read from the body, so an event cannot be attributed to another developer.\n\nIdempotent on `event_id`: a retry returns `created: false` and records nothing new. Message and context values are scrubbed before storage.","operationId":"track_business_event_v1_analytics_business_events_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BusinessEvent"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BusinessEventRecorded"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Request body or query parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/brokers":{"get":{"summary":"List connectable broker servers","description":"Every MT4/MT5 server we can connect to, with its branded installer URL. `POST /accounts` validates `server` against this list EXACTLY and case-sensitively, so copy the string verbatim -- a mis-cased server name fails login silently inside the terminal.\n\nThis list is the whole of what we support: a broker that is not here cannot be connected by typing its server name. To ask for one, open a `POST /support/tickets` with category `connect_broker` (or email support@verilo.trade) and include the exact server name your MetaTrader login window shows.","operationId":"list_brokers_brokers_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/BrokerServer"},"title":"Response List Brokers Brokers Get"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/developers":{"post":{"summary":"Register and get your first API key","description":"The only unauthenticated endpoint on this API, and the only place a key's secret is ever shown -- store it immediately, it cannot be retrieved again.\n\n**Signup is gated, and on production it is currently closed.** Unless `DEVELOPER_SIGNUP_OPEN` is set on the deployment, or your address is allow-listed, this returns **403** with `{\"detail\":\"signups are closed\"}` \u2014 which is exactly what api.verilo.trade returned when this was last checked, on 2026-08-08. Public sign-up is not open yet; a copy-pasted quickstart stops at this call until you have been given access.","operationId":"register_developer_v1_developers_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterDeveloper"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterDeveloperResponse"}}}},"403":{"description":"Developer signup is currently closed to this email address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"That email is already registered.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Request body or query parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}},"security":[]}},"/v1/keys":{"post":{"summary":"Issue an additional API key","description":"Mint a named key, optionally scoped. The secret is shown ONCE in `api_key`.\n\nOmitting `scopes` produces a FULL-ACCESS key -- an empty scope list is the legacy 'everything' default, not 'nothing'.","operationId":"issue_key_v1_keys_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IssueKey"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IssuedKeyResponse"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Request body or query parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"get":{"summary":"List your API keys","description":"Metadata only -- key secrets are stored hashed and are never returned by any endpoint after issue. Revoked keys stay listed with `revoked_at` set.","operationId":"list_keys_v1_keys_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ApiKeyRow"},"title":"Response List Keys V1 Keys Get"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/keys/{key_id}":{"delete":{"summary":"Revoke an API key","description":"Takes effect immediately. Returns no body.","operationId":"revoke_key_v1_keys__key_id__delete","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/keys/{key_id}/rotate":{"post":{"summary":"Rotate an API key","description":"Issues a fresh secret under the SAME name and scopes, then revokes the old key. The old key stops resolving immediately, so swap the new secret in before you rotate. Shown once.","operationId":"rotate_key_v1_keys__key_id__rotate_post","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"}}],"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RotatedKeyResponse"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/accounts":{"post":{"summary":"Connect a broker account","description":"Stores encrypted credentials and, for MT4/MT5, starts a terminal for the account. Returns as soon as the row exists -- the account begins at `state: PROVISIONING` and reaches `CONNECTED` asynchronously. Poll `GET /accounts/{account_id}/provisioning` for progress and a measured ETA.\n\n`server` is validated EXACTLY and case-sensitively before anything is written, because a mis-cased name fails login silently in the terminal. cTrader accounts skip this (they authorize over OAuth).","operationId":"create_account_accounts_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAccount"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Account"}}}},"400":{"description":"Unknown or mis-cased broker `server` (copy it verbatim from `GET /brokers`), or `tick_stream` on a non-MT5 platform. An unknown `server` returns a STRUCTURED `detail`: `message` (plain language), the `server` we received, `did_you_mean` (near matches -- `confident: true` only when the string differs from one known server by case or spacing alone; everything else is a guess), and `request_a_broker` for a broker we do not carry yet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"Your plan does not allow another account with these settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Request body or query parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"502":{"description":"The terminal could not be provisioned; the account is left FAILED.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"get":{"summary":"List your connected accounts","description":"Every account owned by the calling key, oldest first.","operationId":"list_accounts_accounts_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Account"},"title":"Response List Accounts Accounts Get"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/accounts/{account_id}":{"get":{"summary":"Get one account","description":"Owner-scoped. An account belonging to another developer returns **404**, not 403 -- the API never confirms that an id it does not own exists.","operationId":"get_account_accounts__account_id__get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Account"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"summary":"Update account settings","description":"Owner-scoped partial update. Send only the fields you want to change; anything omitted is left alone.\n\nBroker credentials are NOT changed here -- use `PATCH /accounts/{account_id}/credentials`, which carries the sealed-history identity guard.\n\n**tick_stream** -- Stream live quotes for this account on `GET /accounts/{id}/stream` as `tick` frames. **Off by default.** Four things are true and worth knowing before you turn it on:\n\n* **It materially increases the terminal's CPU use.** A hosted terminal's CPU is tick-driven; we have measured the same pod at roughly 13% idle and 28% while quotes are flowing. Leave it off on accounts nobody is watching.\n* **Only the terminal's chart symbol streams.** One symbol per account, chosen by the terminal, not by you.\n* **This is not a multi-symbol market-data feed.** There is no way to subscribe to a symbol on this stream; if you need a quote for a symbol the account is not charted on, this will not give you one.\n* **MT5 only.** MT4's bridge expert has no quote producer, so the flag is refused on MT4 and cTrader accounts rather than accepted and ignored.\n\nChanging it takes effect when the terminal is next created, because the terminal receives it as container environment at start-up. Call `POST /accounts/{id}/reprovision` to apply it to a running terminal -- knowing that recreating a terminal is a data-integrity event.\n\n---\n\n**keep_alive_days** -- **Pin this account's terminal so we do not stop it for being idle.**\n\nBy default we suspend any terminal we are not being paid to keep running once it has been quiet for 15 minutes, and start it again when you ask (`POST /accounts/{account_id}/resume`). A **demo** account is never billable, so before this flag existed there was no way to keep a demo terminal up: fifteen minutes after you stopped working, it stopped. Pinning is the fix while you are building an integration.\n\nSend the number of days, 1 to 1825. **7 is the suggested value** -- about the length of an integration. Send `0` to unpin immediately. The deadline comes back as `keep_alive_until`, and re-sending extends it from now; it does not add to what is left.\n\n**A pin always expires.** Pinning does not make a terminal free. A running terminal costs a measured $10.02 a month of real infrastructure whether or not anyone is connected to it, and pinning means that keeps being spent for as long as the pin lasts. That is why a pin always expires, why 1825 days is the maximum, and why pinning does NOT exempt you from your plan's monthly connection-hours cap.\n\n**What it does not do.** It does not exempt you from your plan's monthly connection-hours cap -- if you run out of hours we still stop the terminal, and we clear the pin when we do. It does not restart a terminal that is already suspended; call `POST /accounts/{account_id}/resume` for that. It changes nothing about what you are billed: a pinned demo terminal costs you exactly what an unpinned one does. And on a live always-on terminal it does nothing at all, because we already never stop those.","operationId":"update_account_accounts__account_id__patch","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAccount"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Account"}}}},"400":{"description":"Nothing to update -- no settable field was sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"This account's platform cannot do what was asked (e.g. live quotes on an MT4 or cTrader account).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Request body or query parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"delete":{"summary":"Disconnect and stop an account","description":"Stops the terminal, closes the billing span, drops the account from the health view, and revokes any relay keys it issued. The row is moved to `STOPPED`; sealed history is retained. Returns no body.","operationId":"delete_account_accounts__account_id__delete","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/accounts/{account_id}/credentials":{"patch":{"summary":"Update stored broker credentials","description":"Two different operations share this shape:\n\n* **Password only** (login and server unchanged or omitted) -- a credential rotation. Always allowed.\n* **Login or server changed** -- a change of IDENTITY. Allowed while the account has sealed nothing; **409** once it has verified records, because re-pointing credentials would merge two brokers' histories into one track record. Add a NEW account instead.\n\nThe running terminal keeps the OLD credentials until its next connect -- nothing here restarts it, because an unplanned terminal exit is a data-integrity event.","operationId":"update_credentials_accounts__account_id__credentials_patch","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCredentials"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Account"}}}},"400":{"description":"Nothing to update, or an unknown/mis-cased broker `server` -- which returns the same structured `detail` as `POST /accounts` (`message`, `did_you_mean`, `request_a_broker`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"This account has sealed verified records, so its broker identity (login/server) can no longer be changed. Password-only rotation is still allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Request body or query parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/accounts/{account_id}/state":{"get":{"summary":"Account row plus its latest terminal snapshot","description":"`account` is the durable row; `snapshot` is the last frame the terminal pushed, or **null** if none has landed yet (pod offline, or just connected). The snapshot's shape is set by the terminal, not by this API.","operationId":"get_state_accounts__account_id__state_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountState"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/provisioning/eta":{"get":{"summary":"Measured setup-time ETA for a broker","description":"Real p50/p95 of created-to-CONNECTED durations for one broker server (or pooled across all when `server` is omitted).\n\n**`measured: false` means we do not have enough real runs yet** and the numbers are a conservative fallback, not an observation. Say so if you show them to a user.","operationId":"provisioning_eta_provisioning_eta_get","parameters":[{"name":"server","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Server"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProvisioningEta"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/accounts/{account_id}/provisioning":{"get":{"summary":"Provisioning progress for one account","description":"Drives an onboarding progress indicator: `state` is the authoritative live state, `timeline` is the timestamped transition history, and `eta` is the same measured estimate as `GET /provisioning/eta` for this account's broker.","operationId":"provisioning_timeline_accounts__account_id__provisioning_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProvisioningTimeline"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/accounts/{account_id}/trust":{"get":{"summary":"Verified track record + tamper-evident seal","description":"The account's verified record: performance measured on the **cash-flow-neutral** growth curve (a deposit or withdrawal is not a return and not a drawdown), net-of-cost figures, labeled cash flows, and the seal over all of it.\n\nKeys here are camelCase, unlike the rest of the API, because this document is sealed byte-for-byte -- renaming a key would invalidate every existing seal.\n\nNote the path: this endpoint is **`/accounts/...`**, not `/v1/accounts/...`.","operationId":"get_trust_accounts__account_id__trust_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrustReport"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/accounts/{account_id}/trades":{"get":{"summary":"List trade commands","description":"The command/intent log with each command's outcome. For executed broker deals with realized P&L (and labeled deposits/withdrawals) use `GET /accounts/{account_id}/deals` instead.","operationId":"list_trades_accounts__account_id__trades_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Trade"},"title":"Response List Trades Accounts  Account Id  Trades Get"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"summary":"Place an order \u2014 market, or pending (limit / stop)","description":"Sends a market order and waits briefly for a terminal outcome.\n\n* **200** -- terminal: `status` is `FILLED` or `REJECTED`.\n* **202** -- not terminal within the timeout; poll the command.\n\n`commandId` is the idempotency key: replaying one returns the original command and never dispatches a second order.\n\n**Stops are attached at entry.** `sl`, `tp`, `price` and `deviation` are honoured by the MT5 and MT4 expert advisors. A stop on the wrong side of the market, or closer than the broker's minimum stop distance, is **rejected** rather than dropped -- the error carries `stops_level`, `freeze_level`, `min_distance` and `digits` so you can correct it.\n\nA terminal still running an older EA build does not advertise stop support, and an order carrying `sl`/`tp` is refused rather than filled unprotected. Some MT4 brokers reject any order carrying stops outright (error 130); there the entry fails loudly and no position is opened.\n\n**Pending entries.** Set `type` to `LIMIT` or `STOP` with a `price` and the order RESTS with the broker until price reaches it. `FILLED` on a pending order means **placed, not filled** \u2014 `result.pending` is `true` and `result.order` is the order's ticket. List them with `GET /accounts/{account_id}/pending-orders` and remove one with `POST /accounts/{account_id}/pending-orders/{ticket}/cancel`. MT5 and MT4 only; cTrader and OANDA return `501`.\n\nStops can be MOVED after entry with `POST /accounts/{account_id}/positions/{ticket}/modify`, and a position closed (in whole or in part) with `POST /accounts/{account_id}/close`. Both are journalled money commands with the same `commandId` idempotency and reconcile-on-timeout contract as this route.","operationId":"place_trade_accounts__account_id__trades_post","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TradeBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Trade"}}}},"202":{"description":"Accepted but not yet terminal: the terminal did not answer within the timeout, so `status` is `UNKNOWN` (or still in-flight) and a reconcile has been requested. Poll `GET /accounts/{account_id}/trades/{command_id}`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Trade"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such account, or it is not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"The terminal or adapter for this account is offline.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Request body or query parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"501":{"description":"A `LIMIT` or `STOP` (pending) order on a platform that cannot serve one. **MT5 and MT4 both can**; cTrader and OANDA return this, because neither can cancel a resting order through this API and OANDA would not observe it filling until its next poll. Market orders are unaffected on every platform.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/accounts/{account_id}/metrics":{"get":{"summary":"Command fill-rate + live equity summary","description":"`trades` counts command outcomes (fill rate), not P&L. `equity` is the live snapshot summary; its `balance`/`equity` are null until the terminal's first snapshot arrives.","operationId":"get_metrics_v1_accounts__account_id__metrics_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountMetrics"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/accounts/{account_id}/reprovision":{"post":{"summary":"Recreate this account's terminal","description":"Deliberately explicit, never automatic. A pod receives its relay credentials as container environment at CREATE time, so an account that becomes a provider AFTER its terminal booted has no way to get them -- this is the fix.\n\n**Restarting a terminal is a data-integrity event**: the closed-order history push runs once per session. Call it knowing that.","operationId":"reprovision_account_accounts__account_id__reprovision_post","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReprovisionResponse"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"This account has no terminal pod (e.g. cTrader), or its terminal is paused -- resume it first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"502":{"description":"Re-provisioning failed; the account is left FAILED.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/accounts/{account_id}/resume":{"post":{"summary":"Restart a suspended terminal","description":"Your terminal is **paused, not deleted** -- `state` reads `SUSPENDED`, which is the one state that is never a fault. We stop the container for a non-billable terminal that has gone unused -- a free demo past its monthly connection hours, or one with no live session for a while -- because a running terminal costs us real money every hour whether or not anyone is connected to it.\n\nNothing is thrown away when we do that: the terminal and its state are kept, and this restarts them. Read `outcome` -- `resumed` means the same terminal woke up in seconds; `reprovisioned` means the container was gone and a fresh terminal had to be built, which takes minutes.","operationId":"resume_account_accounts__account_id__resume_post","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResumeResponse"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"This account has no terminal pod (e.g. cTrader).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"502":{"description":"The terminal could not be started.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/accounts/{account_id}/subscribe":{"post":{"summary":"Subscribe the terminal to market symbols","description":"Fire-and-forget: **202** means the instruction was handed to the terminal, not that quotes are flowing. Body is `{\"symbols\": [\"EURUSD\", ...]}`.","operationId":"subscribe_accounts__account_id__subscribe_post","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OkResponse"}}}},"400":{"description":"`symbols` missing or not a non-empty array.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"More symbols than your plan allows.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"The terminal is offline, so the subscription cannot be delivered.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/accounts/{account_id}/trades/{command_id}":{"get":{"summary":"Get one trade command","description":"Poll this after a **202** from `POST /accounts/{account_id}/trades` until `status` is `FILLED` or `REJECTED`.","operationId":"get_trade_accounts__account_id__trades__command_id__get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"command_id","in":"path","required":true,"schema":{"type":"string","title":"Command Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Trade"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/accounts/{account_id}/halt":{"post":{"summary":"Emergency stop: flatten everything and refuse new orders","description":"**The emergency brake.** Latches a persisted halt on the account, stops the low-latency copy rail at the expert advisor, cancels every queued order, and flattens every open position.\n\nTrading stays refused -- on every path, including copy fan-outs and orders queued while a terminal was offline -- until you call `POST /accounts/{account_id}/rearm`. Nothing re-arms implicitly: not a reconnect, not a restart of ours.\n\n**Read the `flatten` block in the response; do not assume success.** `DONE` means every symbol reported flat. `PARTIAL` means at least one position is still open. `UNKNOWN` means a terminal did not answer and we cannot say either way -- the halt is in force, but check the account. `PENDING` means the terminal was offline: nothing could be closed, and the flatten runs automatically when it reconnects.\n\nIdempotent on `commandId`. The halt itself latches: repeating it keeps the original time, actor and reason.","operationId":"halt_account_accounts__account_id__halt_post","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HaltBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such account, or it is not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Request body or query parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"get":{"summary":"Halt status, and whether it can be re-armed","description":"Whether the account is halted, since when, who did it and why, plus any `UNKNOWN` commands blocking a re-arm and the most recent flattens with their per-symbol outcomes.","operationId":"get_halt_accounts__account_id__halt_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such account, or it is not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/accounts/{account_id}/rearm":{"post":{"summary":"Re-arm a halted account","description":"Clears the halt and allows orders again. **Separate call, deliberately** -- nothing re-arms on its own, so an account that was stopped stays stopped until a human says otherwise.\n\nRefused while any trade command is `UNKNOWN`: that means an order went out and we never learned its fate, so there may be a position nobody has accounted for. There is no override.","operationId":"rearm_account_accounts__account_id__rearm_post","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such account, or it is not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"The account still has trade commands in `UNKNOWN` state. Their real-world outcome is unresolved, so the account's true exposure is unknown and re-arming would resume trading on top of it. Resolve them first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/accounts/{account_id}/flatten/{command_id}":{"get":{"summary":"Poll one flatten command","description":"The reconcile half of the halt. Poll until `status` leaves `SENT`. `PARTIAL` and `UNKNOWN` both mean the account may still hold open positions -- neither is a success.","operationId":"get_flatten_accounts__account_id__flatten__command_id__get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"command_id","in":"path","required":true,"schema":{"type":"string","title":"Command Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/accounts/{account_id}/positions":{"get":{"summary":"Open positions + equity summary","description":"Read from the live terminal snapshot. Before the first snapshot lands this is an empty list with null equity -- **never a 404**; the account exists, it just has no live view yet.\n\nPosition objects are passed through exactly as the terminal reports them, so their fields are broker/platform specific.","operationId":"list_positions_accounts__account_id__positions_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PositionsResponse"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/accounts/{account_id}/exposure":{"get":{"summary":"Durable open-exposure rollup","description":"Open exposure in **lots**, per symbol and in total, read from the persisted rollup rather than from live process memory \u2014 so it survives a control-plane restart and still answers for a pod that is currently offline.\n\n**Read `positions_known` and `fresh` before you read any number.** `positions_known: false` means we could not enumerate this account's positions, so the volumes are a floor and not a measurement; `unknown_reason` says why. `fresh: false` means we have not observed this account within `stale_after_seconds`, so the figures are last-known rather than current. `usable` is simply both of those being true.\n\nA never-observed account returns `positions_known: false` with null volumes \u2014 **not zeros.** Unknown exposure and zero exposure are different facts and this endpoint will not conflate them.\n\n`gross_volume` sums |volume| over every position; `net_volume` is longs minus shorts. They differ on a hedging account, where a hedged pair is real cost with no directional risk.","operationId":"get_account_exposure_accounts__account_id__exposure_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/accounts/{account_id}/candles":{"get":{"summary":"Closed historical bars from the terminal","description":"Closed bars for one symbol and timeframe, oldest first, read **live from this account's own terminal** rather than from a cache or a vendor feed. Intended for warming a strategy up: a runner on H4 would otherwise sit mute for weeks accumulating bars from live ticks.\n\n**MT5 only.** cTrader, OANDA and MT4 accounts return `501` \u2014 see that code below.\n\nOnly CLOSED bars are returned; the forming bar is never included. `time` is the bar's OPEN time as a UTC epoch in seconds, and `time_utc` is the same instant as a string. The offset used to convert from the broker's server clock is reported as `server_gmt_offset`.\n\n**How much history a terminal holds is not promised.** `n` is clamped to 5000 per request, but that is a bound on one response, not a depth guarantee: real depth depends on the terminal's own history settings and on what the broker serves. A short answer is therefore normal and honest \u2014 it comes back `200` with `truncated: true` and the real `count`, never padded and never an error. Read `count`, do not assume `requested`.\n\n### Deep history: page backward with `before`\n\n`before` is a UTC epoch in **seconds**; you get the `n` closed bars **strictly before** it. Page by feeding the **oldest bar's `time`** back in as the next `before` \u2014 it is exclusive, so pages neither overlap nor skip a bar.\n\nDo not reach for a bigger `n` instead. Four years of RTH M1 is ~400,000 bars, roughly **50MB in one response** across the terminal socket, and it times out \u2014 which is why `n` is capped. A cursor keeps every page bounded however far back you go.\n\n### Stop on `exhausted`, never on an error\n\n`exhausted: true` is the terminating condition for a backward pager: an empty page that really is the START of history, as opposed to one the terminal has not assembled yet. Otherwise those are identical payloads and every bar in a short page is valid, so guessing wrong is invisible.\n\n* `exhausted: true` \u2192 **stop.** Nothing older exists here.\n* `syncing: true`, or HTTP **202** \u2192 still assembling. Wait `retry_after` and ask again. Not the end, and not your mistake.\n* `first_date` \u2192 earliest bar THIS TERMINAL holds, per timeframe. Intraday depth is the terminal cache and is far shallower than daily \u2014 weeks for M1 against years for D1.\n\n### A cold symbol is not a client error\n\nA symbol the terminal has not synced answers **202** with `Retry-After`, not `422`. It used to surface as a permanent-looking refusal for something that succeeds a minute later, and callers hammer-retried it hard enough to degrade the host. **422 now means the REQUEST is wrong** \u2014 unknown symbol, bad `tf`. Asking one terminal for more concurrent work than it can do returns **429** with `Retry-After`: one caller must not starve the terminal that is also executing someone orders.\n\nHistory is **per-terminal state**: it downloads on demand and grows the terminal's local database, so a wide backfill is heavy and lands on the same thread that services your orders. Tell us before backfilling hundreds of symbols and we will give it its own terminal.\n\nTimeframes: `M1, M2, M3, M4, M5, M6, M10, M12, M15, M20, M30, H1, H2, H3, H4, H6, H8, H12, D1, W1, MN1`. Anything else is a `422` here rather than an empty answer from the terminal.","operationId":"list_candles_accounts__account_id__candles_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"symbol","in":"query","required":true,"schema":{"type":"string","description":"Broker symbol, e.g. EURUSD","title":"Symbol"},"description":"Broker symbol, e.g. EURUSD"},{"name":"tf","in":"query","required":false,"schema":{"type":"string","description":"One of: M1, M2, M3, M4, M5, M6, M10, M12, M15, M20, M30, H1, H2, H3, H4, H6, H8, H12, D1, W1, MN1","default":"H1","title":"Tf"},"description":"One of: M1, M2, M3, M4, M5, M6, M10, M12, M15, M20, M30, H1, H2, H3, H4, H6, H8, H12, D1, W1, MN1"},{"name":"n","in":"query","required":false,"schema":{"type":"integer","maximum":5000,"minimum":1,"description":"How many CLOSED bars, most recent last. The parameter is `n` -- `count`/`limit`/`bars` are refused with a 422 rather than silently ignored, because ignoring them returns fewer bars than you asked for.","default":100,"title":"N"},"description":"How many CLOSED bars, most recent last. The parameter is `n` -- `count`/`limit`/`bars` are refused with a 422 rather than silently ignored, because ignoring them returns fewer bars than you asked for."},{"name":"before","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"description":"Backward cursor: UTC epoch SECONDS. Returns the `n` closed bars STRICTLY before this time, most recent last. Page by passing the oldest bar's `time` back as the next `before` -- exclusive, so pages neither overlap nor skip a bar. Omit for the most recent bars.","title":"Before"},"description":"Backward cursor: UTC epoch SECONDS. Returns the `n` closed bars STRICTLY before this time, most recent last. Page by passing the oldest bar's `time` back as the next `before` -- exclusive, so pages neither overlap nor skip a bar. Omit for the most recent bars."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CandlesPage"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such account, or it is not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"The account has a terminal, but it is not connected right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Unknown `tf`, a blank `symbol`, `n` outside 1..5000, or a symbol/timeframe this broker does not serve.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"502":{"description":"The link to the terminal failed while the question was in flight.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"501":{"description":"This account's platform has no terminal to ask. Only MT5 serves bars today: cTrader and OANDA run in-process with no terminal at all, and MT4's bridge has no bars operation. Also returned when the terminal is running an expert-advisor build that does not advertise this query.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"504":{"description":"The terminal was connected and did not answer in time.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/accounts/{account_id}/storage":{"get":{"summary":"How much disk this terminal's history is using","description":"Downloaded price history is **per-terminal state**: it grows as you page backward and is the only part of a pod that grows without bound. One account reached **58GB in two days**.\n\nReturns total bytes, the biggest symbols first, and the quota if one is set. `over_quota` is stated rather than left to be derived, so a dashboard and a client cannot disagree about when 'full' starts.\n\nFree space with `DELETE /accounts/{account_id}/history`.","operationId":"account_storage_accounts__account_id__storage_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/accounts/{account_id}/history":{"delete":{"summary":"Delete downloaded price history to reclaim disk","description":"Deletes this terminal's cached history. `?symbols=A,B` targets specific instruments; omitting it clears everything.\n\n**This costs time later, not data.** MT5 re-downloads on demand, so anything you delete is re-fetched (slowly) the next time it is asked for. It is a space/time trade you are choosing -- we never run it for you.\n\nReturns the bytes actually freed, measured rather than assumed, and names any symbol that was not found so a typo does not read as a successful no-op.","operationId":"purge_account_history_accounts__account_id__history_delete","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"symbols","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated symbols. Omit to clear all history.","title":"Symbols"},"description":"Comma-separated symbols. Omit to clear all history."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/accounts/{account_id}/symbols/{symbol}":{"get":{"summary":"The broker's own contract spec for one symbol","description":"Digits, point, tick size and value, contract size, volume min/step/max, the minimum stop and freeze distances, the three per-symbol currencies and the trading calendar \u2014 read **live from this account's own terminal**, not from a table we keep.\n\nIt has to be asked, because these values differ per **broker**, not just per symbol: two brokers' `EURUSD` routinely disagree on contract size, minimum volume and minimum stop distance. Sizing against a guess produces an order the server rejects with a bare retcode carrying no numbers.\n\n**MT5 and MT4.** cTrader and OANDA return `501` \u2014 see that code below.\n\n**Nothing here is defaulted.** An unknown value comes back `null`, never a plausible-looking `0` or `0.01`. `trade_mode: null` means unknown; `0` would mean `disabled`, which is a different claim.\n\n**Sessions differ by platform, and the response says which.** On MT5, `sessions_supported` is `true` and `sessions` is seven days of trading windows; a day the symbol does not trade is present with an EMPTY list, which is a real market fact. **MT4 has no session API at all**, so it answers `sessions_supported: false` with `sessions: null` \u2014 \"we cannot see the calendar\", not \"the market is shut all week\". `filling_mode`, `trade_mode`, `expiration_mode` and the three currencies are null on MT4 for the same reason.\n\nSession times are seconds from midnight on the **broker's server clock**; `server_gmt_offset` rides along so the conversion stays auditable.\n\nDistinct from `GET /v1/accounts/{account_id}/symbols` (plural), which lists the symbols the account currently HOLDS from the in-memory snapshot and never touches the terminal.","operationId":"get_symbol_info_accounts__account_id__symbols__symbol__get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SymbolInfo"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such account, or it is not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"The account has a terminal, but it is not connected right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"A blank `symbol`, or one this broker does not offer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"502":{"description":"The link to the terminal failed while the question was in flight.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"501":{"description":"This account's platform has no terminal to ask. **MT5 and MT4 both answer**; cTrader and OANDA run in-process with no terminal at all. Also returned when the terminal is running an expert-advisor build that does not advertise `symbol_info`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"504":{"description":"The terminal was connected and did not answer in time.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/accounts/{account_id}/broker-symbols":{"get":{"summary":"Every symbol this broker actually offers","description":"The broker's own instrument list, read **live from this account's terminal** \u2014 every instrument it offers, whether or not the terminal has ever touched it.\n\n**This is the only honest answer to \"does my broker have SPX500?\"** Every other view of symbols in this API is an undercount by construction: `GET /v1/accounts/{account_id}/symbols` (plural, no hyphen) lists the symbols you currently HOLD, and the terminal's own quote list is deliberately trimmed to what is being traded because every entry costs CPU. Neither absence means the instrument is not sold.\n\n**Enumerating changes nothing.** Reading the list does not add anything to the terminal's quote window and does not affect what you can trade. `selected: false` is the normal state.\n\n**`search` filters on the terminal, not here** \u2014 a substring, case-insensitive \u2014 so asking \"do you have SPX\" costs a handful of rows instead of thousands.\n\n**Read `truncated` before concluding anything is missing.** At most 5000 rows come back per request; `matched` says how many really matched and `total` how many the broker offers. A short list that looked complete would say \"not offered\", which is the exact error this endpoint exists to prevent.\n\nRows carry the name and quote-window membership only. For contract size, volume step, digits, stop distance and trading hours, ask `GET /accounts/{account_id}/symbols/{symbol}`, which reads the same terminal. To find out **which of these symbols is the instrument you mean**, use `GET /accounts/{account_id}/broker-symbols/resolve`.","operationId":"list_broker_symbols_accounts__account_id__broker_symbols_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Case-insensitive substring, applied on the terminal.","title":"Search"},"description":"Case-insensitive substring, applied on the terminal."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":5000,"minimum":1,"description":"Cap on rows returned.","default":5000,"title":"Limit"},"description":"Cap on rows returned."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrokerSymbolsPage"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such account, or it is not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"The account has a terminal, but it is not connected right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"`limit` outside 1..5000.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"502":{"description":"The link to the terminal failed while the question was in flight.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"501":{"description":"This account's platform has no terminal to enumerate. **MT5 and MT4 both answer**; cTrader and OANDA run in-process with no terminal at all. Also returned when the terminal is running an expert-advisor build that does not advertise `broker_symbols`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"504":{"description":"The terminal was connected and did not answer in time.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/accounts/{account_id}/broker-symbols/resolve":{"get":{"summary":"Does this broker offer this symbol \u2014 and if not, what is it called here?","description":"Brokers name the same instrument differently. The S&P 500 CFD ships as `SPX500` at one broker, `US500` at the next, then `SP500`, `USA500`, `USA500.IDX`. The Nasdaq 100 is `NAS100`, `USTEC`, `US100` or `NDX`; gold is `XAUUSD` or `GOLD`. So \"does this broker have SPX500\" is the wrong question, and this endpoint answers the right one: **which of this broker's symbols is the instrument you mean.**\n\n`available` is `true` only on an **exact** match in the broker's live list. Otherwise you get `suggestions` \u2014 candidates that really exist at this broker \u2014 and you decide. **Nothing is ever substituted for you**; a quietly-swapped symbol is a trade in the wrong instrument.\n\n**`equivalent` is the field that matters.** It is `true` only for a name our instrument glossary records as the same instrument with a source we checked, and every such row carries that source in `provenance`. `reason: similar` is a fuzzy string match \u2014 a guess \u2014 and `reason: alias_unconfirmed` is naming we have seen widely but have not sourced. Those come back `equivalent: false` on purpose.\n\n**An equivalence is about the NAME, not the contract.** Two brokers' `US500` can differ in contract size, tick value and trading hours. Before sizing anything, read `GET /accounts/{account_id}/symbols/{symbol}`, which asks the same terminal.\n\nCandidates are always checked against the account's own live enumeration first \u2014 the glossary can propose a name, never assert that your broker has it. `GET /accounts/{account_id}/broker-symbols` is that enumeration if you want the whole list.","operationId":"resolve_broker_symbol_accounts__account_id__broker_symbols_resolve_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"symbol","in":"query","required":true,"schema":{"type":"string","description":"The symbol you want, e.g. SPX500","title":"Symbol"},"description":"The symbol you want, e.g. SPX500"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SymbolResolution"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such account, or it is not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"The account has a terminal, but it is not connected right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"A blank `symbol`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"502":{"description":"The link to the terminal failed while the question was in flight.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"501":{"description":"This account's platform has no terminal to enumerate. **MT5 and MT4 both answer**; cTrader and OANDA return this, as does a terminal whose expert advisor does not advertise `broker_symbols`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"504":{"description":"The terminal was connected and did not answer in time.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/accounts/{account_id}/positions/{ticket}/modify":{"post":{"summary":"Move the stop-loss / take-profit on an open position","description":"Changes `sl` and/or `tp` on one OPEN position, addressed by its `ticket` \u2014 without closing and re-opening it, which would pay the spread and realize P&L just to change a number.\n\n**A money op, and journalled as one**, on exactly the contract `POST /accounts/{account_id}/trades` publishes: `commandId` is the idempotency key and replaying it returns the original command without sending a second modify; **200** means the command reached `FILLED` or `REJECTED` within the timeout; **202** means it did not, so `status` is `UNKNOWN`, a reconcile has been requested, and you poll `GET /accounts/{account_id}/trades/{command_id}`.\n\nBody: `{\"commandId\": \"...\", \"sl\": 1.0800, \"tp\": 1.0950}`. At least one of `sl`/`tp` is required. **Omitting one leaves it unchanged** \u2014 there is deliberately no way to clear a stop here, because on the wire \"clear it\" and \"do not touch it\" are indistinguishable, and silently removing protection nobody asked to remove is the failure the stop handling exists to prevent.\n\n**MT5 and MT4.** cTrader and OANDA return `501`. A terminal running an older expert-advisor build that cannot modify does not hang: the command comes back `REJECTED`, terminal, rather than decaying to `UNKNOWN`.\n\nWorth knowing on MT4: some brokers (market-execution / \"ECN\") refuse any order that carries stops, so the entry fails with their error 130 \u2014 and this verb is how the stop is then attached.","operationId":"modify_position_accounts__account_id__positions__ticket__modify_post","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"ticket","in":"path","required":true,"schema":{"type":"integer","title":"Ticket"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Trade"}}}},"202":{"description":"Accepted but not yet terminal: the terminal did not answer within the timeout, so `status` is `UNKNOWN` and a reconcile has been requested. Poll `GET /accounts/{account_id}/trades/{command_id}`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Trade"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such account, or it is not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"The terminal for this account is offline.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Missing `commandId`, neither `sl` nor `tp` sent, or a value the shared request validator rejected.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"501":{"description":"This account's platform cannot modify a position. **MT5 and MT4 both can**; cTrader and OANDA have no terminal and return this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/accounts/{account_id}/close":{"post":{"summary":"Close a position, or part of one \u2014 journalled","description":"Closes positions and records the command, so the outcome is answerable rather than merely hoped for. Same contract as `POST /accounts/{account_id}/trades`: `commandId` is the idempotency key and a replay never realizes a second, opposite trade; **200** when the command reached `FILLED`/`REJECTED` inside the timeout; **202** when it did not, with `status` `UNKNOWN` and a reconcile requested.\n\nBody: `{\"commandId\": \"...\", \"symbol\": \"EURUSD\", \"ticket\": 123, \"volume\": 0.05}`.\n\n* `symbol` alone \u2014 flatten every position on it.\n* `+ ticket` \u2014 flatten exactly that one.\n* `+ volume` \u2014 close only part of that one.\n\n**`volume` without `ticket` is rejected, not interpreted.** \"Close 0.05 lots of EURUSD\" has no single meaning on a hedging account holding three EURUSD positions, and picking one would be inventing your intent.\n\nVolume is clamped **down** at the terminal \u2014 to the position's own volume and to the symbol's volume step \u2014 never up, and the volume actually sent comes back in the result so a clamp is visible rather than assumed.\n\n**MT5 and MT4.** cTrader and OANDA return `501`.\n\n**A partial close does NOT preserve the ticket on MT4.** MT4 is a hedging model: it closes the requested lots and re-opens the remainder under a **new ticket**, so the original ticket is gone. MT5's netting model keeps the position id. A caller holding a ticket across a partial close must re-read `GET /accounts/{account_id}/positions` on MT4 rather than assume it still exists.","operationId":"close_positions_accounts__account_id__close_post","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Trade"}}}},"202":{"description":"Accepted but not yet terminal: the terminal did not answer within the timeout, so `status` is `UNKNOWN` and a reconcile has been requested. Poll `GET /accounts/{account_id}/trades/{command_id}`. **`UNKNOWN` on a close means you may or may not still hold the position** \u2014 read it before acting.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Trade"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such account, or it is not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"The terminal for this account is offline.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Missing `commandId` or `symbol`, a non-integer `ticket`, a non-numeric `volume`, or `volume` sent without a `ticket`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"501":{"description":"This account's platform cannot serve the journalled close. **MT5 and MT4 both can**; cTrader and OANDA have no terminal and return this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/accounts/{account_id}/pending-orders":{"get":{"summary":"Pending orders that have not filled","description":"Resting `LIMIT` and `STOP` orders \u2014 entries the broker is holding that have **not** become positions.\n\n**This is how you find an order's ticket.** Without it the only record of one is the `result.order` you kept when you placed it, so an order outliving the process that created it would be impossible to cancel. Read from the live terminal snapshot, like `GET /accounts/{account_id}/positions`.\n\n**Distinct from positions, and never merged with them.** A resting order is not exposure: it commits no margin and moves no P&L. `/positions` excludes these and this excludes those, so every live ticket appears in exactly one of the two.\n\n**Also not `GET /v1/accounts/{account_id}/orders`**, despite the word. That one is the *command log* \u2014 every instruction you have sent us and what became of it, keyed by your `commandId`. This one is what the *broker* is holding right now, keyed by the broker's own ticket. The path says `pending-orders` precisely so the two cannot be reached for by accident.\n\n`type` is `BUY_LIMIT` / `SELL_LIMIT` / `BUY_STOP` / `SELL_STOP` as a string on both platforms \u2014 MT4 and MT5 number the same four differently, so the code is never put on the wire. `price_open` is where the order rests; `price_current` is the live quote it is measured against.\n\nBefore the first snapshot lands this is an empty list \u2014 **never a 404**; the account exists, it just has no live view yet. It is also empty for a platform that cannot report pending orders (cTrader, OANDA), and `supported` says which of the two you are looking at, so \"none pending\" and \"cannot tell you\" are never confused.","operationId":"list_pending_orders_accounts__account_id__pending_orders_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PendingOrdersResponse"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/accounts/{account_id}/pending-orders/{ticket}/cancel":{"post":{"summary":"Cancel a pending order","description":"Removes a resting `LIMIT` or `STOP` order that has **not** filled. Find the ticket with `GET /accounts/{account_id}/orders`.\n\nBody: `{\"commandId\": \"...\"}`. Same contract as `POST /accounts/{account_id}/trades`: `commandId` is the idempotency key and a replay returns the original command rather than sending a second cancel; **200** means terminal within the timeout; **202** means `UNKNOWN` with a reconcile requested.\n\n**Not a close.** This removes an order that never became a position, and costs nothing. To flatten a live position use `POST /accounts/{account_id}/close` \u2014 passing a position ticket here is rejected rather than acted on, because order tickets and position tickets are separate numbers.\n\nA ticket that has already filled, or already been cancelled, comes back **`REJECTED`** naming which \u2014 not a silent success. If it filled, you now hold a position; that is worth knowing.\n\n**MT5 and MT4.** A terminal running an older expert-advisor build that cannot cancel does not hang: the command comes back `REJECTED`, terminal, saying the order is still live.","operationId":"cancel_pending_order_accounts__account_id__pending_orders__ticket__cancel_post","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"ticket","in":"path","required":true,"schema":{"type":"integer","title":"Ticket"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Trade"}}}},"202":{"description":"Accepted but not yet terminal: the terminal did not answer within the timeout, so `status` is `UNKNOWN` and a reconcile has been requested. Poll `GET /accounts/{account_id}/trades/{command_id}`. **`UNKNOWN` on a cancel means the order may still be live** \u2014 read it before assuming you are un-armed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Trade"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such account, or it is not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"The terminal for this account is offline.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Missing `commandId`, or a `ticket` that is not a positive integer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"501":{"description":"This account's platform cannot cancel a pending order. **MT5 and MT4 both can**; cTrader and OANDA return this \u2014 cTrader has no cancel request in this adapter at all, so an order placed there could not be removed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/accounts/{account_id}/deals":{"get":{"summary":"Executed deals with realized P&L","description":"The truthful trade-history feed, distinct from `/trades` (which is the command log). Deposits and withdrawals are classified as cash-flow markers at write time, so they can never masquerade as P&L.\n\nPaging is `limit`/`offset`; `total` is the count before paging. `since`/`until` are inclusive ISO-8601 bounds.","operationId":"list_deals_accounts__account_id__deals_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO-8601 lower bound (inclusive)","title":"Since"},"description":"ISO-8601 lower bound (inclusive)"},{"name":"until","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO-8601 upper bound (inclusive)","title":"Until"},"description":"ISO-8601 upper bound (inclusive)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":500,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DealsPage"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"`since`/`until` is not a valid ISO-8601 timestamp, or `limit`/`offset` is out of range.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/accounts/{account_id}/equity":{"get":{"summary":"Recorded equity curve","description":"The stored balance / equity / free-margin series. Same `since`/`until` + `limit`/`offset` convention as `/deals`.\n\nThis is the RAW curve, including the steps caused by deposits and withdrawals. For the cash-flow-neutral curve used for performance, read `growthSeries` from `GET /accounts/{account_id}/trust`.","operationId":"list_equity_accounts__account_id__equity_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO-8601 lower bound (inclusive)","title":"Since"},"description":"ISO-8601 lower bound (inclusive)"},{"name":"until","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO-8601 upper bound (inclusive)","title":"Until"},"description":"ISO-8601 upper bound (inclusive)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":500,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EquityPage"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"`since`/`until` is not a valid ISO-8601 timestamp, or `limit`/`offset` is out of range.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/accounts/{account_id}/seal":{"get":{"summary":"Seal hash chain + recomputed verdict","description":"The owner-scoped twin of the public verify-by-digest surface: pull your own sealed record and re-verify the chain over the API.\n\n`chain_intact` is RECOMPUTED on every call, not stored. An account with no seals yet returns an empty chain that verifies intact -- there is nothing to tamper with.","operationId":"get_seal_accounts__account_id__seal_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SealResponse"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/copy/subscriptions":{"post":{"summary":"Follow a provider (copy trading)","description":"Enrolls one of YOUR accounts as a follower of a master account.\n\n`mode` decides how orders reach the follower -- automatic replication, or push-to-confirm, where each order waits in `GET /copy/pending` until the follower confirms it. `sizing_mode`/`sizing_value` scale the copied volume, and `max_drawdown_pct` stops the subscription automatically.\n\nMaster accounts cannot follow another master; that is what makes a copy cycle impossible to construct.","operationId":"create_subscription_copy_subscriptions_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSubscription"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CopySubscription"}}}},"400":{"description":"Copying self, an unknown `mode`/`sizing_mode`, a master account that is not `kind: master`, or a master trying to follow another master.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"The follower account is not yours, or the master does not exist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"This follower already has a subscription to that master.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Request body or query parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"get":{"summary":"List a follower's copy subscriptions","description":"`follower_account_id` is a REQUIRED query parameter and must be an account you own.","operationId":"list_subscriptions_copy_subscriptions_get","parameters":[{"name":"follower_account_id","in":"query","required":true,"schema":{"type":"string","title":"Follower Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CopySubscription"},"title":"Response List Subscriptions Copy Subscriptions Get"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Request body or query parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/copy/subscriptions/{subscription_id}":{"get":{"summary":"Get one copy subscription","description":"Owner-scoped through the subscription's follower account: a subscription you do not own returns 404, never leaking that it exists.","operationId":"get_copy_subscription_copy_subscriptions__subscription_id__get","parameters":[{"name":"subscription_id","in":"path","required":true,"schema":{"type":"string","title":"Subscription Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CopySubscription"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Stop copying","description":"Marks the subscription `stopped`. New master fills stop replicating; **positions already open on the follower are left open** -- close them yourself. Returns no body.","operationId":"stop_subscription_copy_subscriptions__subscription_id__delete","parameters":[{"name":"subscription_id","in":"path","required":true,"schema":{"type":"string","title":"Subscription Id"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/copy/pending":{"get":{"summary":"Copy orders awaiting confirmation","description":"Orders held by a push-to-confirm subscription. Each one waits here until you call confirm or reject -- **nothing executes on its own** in this mode, so an unattended queue simply does not trade.","operationId":"list_pending_copy_pending_get","parameters":[{"name":"follower_account_id","in":"query","required":true,"schema":{"type":"string","title":"Follower Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CopyOrder"},"title":"Response List Pending Copy Pending Get"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Request body or query parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/copy/orders/{order_id}/confirm":{"post":{"summary":"Confirm a pending copy order","description":"Approves a held order and dispatches it.\n\nSubscription state and drawdown are **re-checked at confirm time**, not at queue time, so a confirm can legitimately come back `SKIPPED` -- e.g. the subscription stopped, the drawdown limit was hit, or no equity snapshot is available. Check `status` and `reason` on the returned order; a 200 is not a fill.\n\nConfirming twice is safe: the follower command id is deterministic, so the second call is refused rather than placing a second order.","operationId":"confirm_order_copy_orders__order_id__confirm_post","parameters":[{"name":"order_id","in":"path","required":true,"schema":{"type":"string","title":"Order Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CopyOrder"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"The order is no longer pending (already confirmed, rejected or expired).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"The copy engine is not running on this deployment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/copy/orders/{order_id}/reject":{"post":{"summary":"Reject a pending copy order","description":"Declines a held order. Nothing is sent to the broker.","operationId":"reject_order_copy_orders__order_id__reject_post","parameters":[{"name":"order_id","in":"path","required":true,"schema":{"type":"string","title":"Order Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CopyOrder"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"The order is no longer pending.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"The copy engine is not running on this deployment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/copy/portfolio/warnings":{"get":{"summary":"Correlation warnings across copied exposure","description":"Looks at the follower's live copied exposure and reports concentration / correlation risks (e.g. several providers all long the same currency). An empty `warnings` array means none were found -- it is not a safety guarantee.","operationId":"portfolio_warnings_endpoint_copy_portfolio_warnings_get","parameters":[{"name":"follower_account_id","in":"query","required":true,"schema":{"type":"string","title":"Follower Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortfolioWarnings"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Request body or query parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/copy/strategies":{"post":{"summary":"Create a copy strategy","description":"The developer-side replication engine: you own both the master and the subscriber accounts, and replication happens between them.\n\nDistinct from the `/copy/*` marketplace surface, where the master is a third-party provider you do not own. Note that ownership failures here are **403**, unlike the 404 used on `/accounts/*`.","operationId":"create_copy_strategy_v1_copy_strategies_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCopyStrategy"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CopyStrategy"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The master account is not owned by your key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"A strategy with that name already exists for this master.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Request body or query parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/copy/subscriptions":{"post":{"summary":"Subscribe an account to a copy strategy","description":"Enrolls one of your accounts as a subscriber of one of your strategies. `volume_mode`/`volume_value` control how master volume is translated to the subscriber.","operationId":"create_copy_strategy_subscription_v1_copy_subscriptions_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCopyStrategySubscription"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CopyStrategySubscription"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The strategy or the subscriber account is not owned by your key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"That account is already subscribed to this strategy.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Request body or query parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/copy/strategies/{strategy_id}/capacity":{"put":{"summary":"Set a strategy's capacity limits","description":"The anti-crowding lever: cap how many accounts may subscribe and how much volume the book may commit. `null` on either field means uncapped; `0` is a real cap meaning closed.\n\n**Lowering a cap below current usage never evicts anyone.** Existing subscribers are grandfathered and only new subscribes are refused; the response's `over_by` tells you how far over the book currently is.\n\n**`max_total_volume` caps COMMITTED volume, not live market exposure** -- the sum of what your subscriptions are configured to replicate, which is deterministic and survives a restart. Because a `proportional` subscription's size depends on a live balance ratio we cannot compute deterministically, a strategy with a volume cap accepts only `fixed` and `multiplier` subscriptions.","operationId":"set_copy_strategy_capacity_v1_copy_strategies__strategy_id__capacity_put","parameters":[{"name":"strategy_id","in":"path","required":true,"schema":{"type":"string","title":"Strategy Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CopyStrategyCapacity"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The strategy is not owned by your key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Request body or query parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"get":{"summary":"A strategy's capacity limits and how much is used","operationId":"get_copy_strategy_capacity_v1_copy_strategies__strategy_id__capacity_get","parameters":[{"name":"strategy_id","in":"path","required":true,"schema":{"type":"string","title":"Strategy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The strategy is not owned by your key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/copy/strategies/{strategy_id}/exposure":{"get":{"summary":"Live open exposure across this book","description":"The LIVE counterpart of `/capacity`. `/capacity` reports **committed** volume \u2014 what your subscriptions are configured to replicate, which is deterministic and restart-safe. This reports what the book **actually holds right now**, in lots, aggregated from each subscriber account's persisted exposure rollup.\n\n**Nothing is capped on this.** It is measurement, deliberately shipped before any enforcement, because a limit imposed on data nobody has inspected refuses real business for reasons nobody can explain.\n\n**`complete` is the field that decides whether the totals mean anything.** It is `true` only when every active subscriber's exposure was both enumerable and freshly observed. When it is `false`, `unknown_accounts` names each account we could not account for and why, and the totals are a **floor** \u2014 they omit those accounts entirely rather than counting them as zero. Silently treating an account we cannot see as flat is the one error an exposure figure must never make.","operationId":"get_copy_strategy_exposure_v1_copy_strategies__strategy_id__exposure_get","parameters":[{"name":"strategy_id","in":"path","required":true,"schema":{"type":"string","title":"Strategy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The strategy is not owned by your key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/copy/strategies/{strategy_id}/subscriptions":{"get":{"summary":"List a strategy's subscribers","operationId":"list_copy_strategy_subscriptions_v1_copy_strategies__strategy_id__subscriptions_get","parameters":[{"name":"strategy_id","in":"path","required":true,"schema":{"type":"string","title":"Strategy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CopyStrategySubscription"},"title":"Response List Copy Strategy Subscriptions V1 Copy Strategies  Strategy Id  Subscriptions Get"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"The strategy is not owned by your key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/billing/usage":{"get":{"summary":"This period's running statement","description":"What the period has accrued so far. Defaults to the current UTC month; pass `year`/`month` for a past one.\n\n**The terminal line is time-weighted.** `billable_accounts` is an accrual in terminal-months, not a count of terminals: a terminal held for half the month contributes about half a terminal-month, and a terminal deleted mid-month keeps the time it really ran. It is read out of the same computation that prices the invoice, so this quote and that bill cannot drift apart.\n\nHosted terminals are **$20.00 per terminal per month, prorated by the time you hold one**, and **your first live terminal is free, forever** \u2014 the allowance is already subtracted here, so a developer running exactly one live terminal reads `0`. Demo and non-always-on terminals are never metered at all.\n\n**The other lines are not time-weighted.** `hosted_ea_pods` and `copy_subscriber_slots` are current counts priced at the full monthly rate, and `overage_hours` is always priced at 0 because no overage pricing is live.\n\n**Still not an invoice**: the period is open and the numbers move until it closes. The closed, stored bills are at `GET /v1/billing/invoices`.","operationId":"get_billing_usage_v1_billing_usage_get","parameters":[{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Year"}},{"name":"month","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Month"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingUsage"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Request body or query parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/billing/invoices":{"get":{"summary":"Your stored invoices","description":"Closed, stored bills \u2014 newest period first \u2014 as opposed to `GET /v1/billing/usage`, which is this period's running estimate. An empty list means nothing has been invoiced yet, which is the normal state for a developer whose only terminal is the free one.\n\n`subtotal_cents` is integer USD cents. `external_ref` is null until the invoice has been handed to the payment processor.\n\nScoped to your own developer id in the query itself, so an invoice belonging to someone else cannot be read here at all.","operationId":"list_billing_invoices_v1_billing_invoices_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/BillingInvoice"},"title":"Response List Billing Invoices V1 Billing Invoices Get"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"The billing datastore is not wired on this deployment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/webhooks/event-types":{"get":{"summary":"List deliverable event types","description":"Every type here is a real event the platform produces. `event_types` on a subscription also accepts a `prefix.*` glob; an empty list subscribes to all of them.\n\nThe envelope, headers, signature, retry schedule and the **exact `data` shape of every event** are documented below \u2014 a handler cannot be written against a list of names.\n## The envelope\n\nEvery delivery is a POST of this JSON body, whatever the event:\n\n```json\n{\n  \"id\": \"4f1c\u2026\",              // event id; the SAME id on every endpoint this event fans out to\n  \"type\": \"trade.filled\",     // one of the types below\n  \"created\": \"2026-08-10T09:12:00.123456+00:00\",\n  \"data\": { }                 // per-event, documented below\n}\n```\n\nThe body is serialized with **sorted keys and no whitespace**\n(`json.dumps(..., separators=(\",\", \":\"), sort_keys=True)`). Sign and verify\nagainst the **raw bytes you received** \u2014 re-serializing a parsed object will\nnot reproduce them.\n\n## Headers\n\n| Header | Value |\n|---|---|\n| `Content-Type` | `application/json` |\n| `User-Agent` | `Verilo-Webhooks/1.0` |\n| `X-Verilo-Event` | The event id (`data`-independent; same across endpoints and across retries) |\n| `X-Verilo-Event-Type` | The event type |\n| `X-Verilo-Delivery` | The delivery id \u2014 one per (event, endpoint); use it for idempotency |\n| `X-Verilo-Signature` | `t=<unix seconds>,v1=<hex hmac-sha256>` |\n\nThe signature is `HMAC-SHA256(secret, f\"{t}.{raw_body}\")`, hex, where `secret`\nis the `whsec_\u2026` returned once by `POST /v1/webhooks`. The timestamp is inside\nthe MAC, so bounding `now - t` (300 seconds is what our own verifier uses)\nrejects replays. Compare in constant time.\n\n## Retries\n\nUp to **6 attempts**. Backoff before retry *n* is `10 \u00d7 2^(n-1)` seconds \u2014\n10s, 20s, 40s, 80s, 160s \u2014 capped at 1 hour. Any 2xx is success; anything\nelse, including a network error, is retried until the 6th attempt, which marks\nthe delivery `failed`. We read at most 64 KB of your response body and do not\ninterpret it. Redirects are **not** followed. Two failures are terminal\nimmediately and are not retried: the endpoint was deleted between claim and\nsend (`endpoint removed`), and the delivery-time SSRF re-check refused the URL\n(`blocked: \u2026`). `GET /v1/webhooks/{webhook_id}/deliveries` is the record.\n\n## `data` by event type\n\n**Every event originating from an account carries `account_id`** (a UUID\nstring) in addition to the fields listed. `webhook.test` is the one exception \u2014\nit belongs to an endpoint, not an account.\n\n### `account.state_changed`\n| Field | Type | Notes |\n|---|---|---|\n| `account_id` | string | |\n| `state` | string | `PROVISIONING`, `CONNECTING`, `CONNECTED`, `DEGRADED`, `RECONNECTING`, `FAILED`, `SUSPENDED`, `STOPPED` |\n| `detail` | string \\| null | Free text from the pod, when it sent one |\n\nFires on a legal state transition only. An illegal transition is ignored and\nemits nothing. A pod's own EA status frames do not fire this.\n\n`SUSPENDED` means we deliberately stopped the terminal because it was not in\nuse \u2014 it is **not** a fault, and it is not something to page on. Nothing is\ndeleted; `POST /accounts/{id}/resume` starts it again, and\n`GET /accounts/{id}` carries `pod_suspend_reason`. If you alert on terminals\nthat need attention, alert on `RECONNECTING` and `FAILED`, not on this. A\nterminal only ever leaves `SUSPENDED` when someone asks it to.\n\n### `trade.filled` / `trade.rejected`\n| Field | Type | Notes |\n|---|---|---|\n| `account_id` | string | |\n| `command_id` | string | The `commandId` you supplied when placing the order |\n| `status` | string | `\"FILLED\"` or `\"REJECTED\"` \u2014 matches the event type |\n| `retcode` | integer | The broker/terminal return code |\n| `order` | integer \\| null | Order ticket |\n| `deal` | integer \\| null | Deal ticket. MT5 sets it; **MT4 always sends `0`** |\n| `comment` | string \\| null | Broker comment |\n\nFires **once** per command, on the transition into a terminal status. A\nduplicate result frame for a command already terminal is dropped and does not\nre-fire.\n\n### `deal.recorded`\n| Field | Type | Notes |\n|---|---|---|\n| `account_id` | string | |\n| `deal_id` | integer | MT5 deal ticket, or the order ticket on MT4 (which reports `deal: 0`) |\n| `symbol` | string \\| null | From the originating request |\n| `type` | string \\| null | `\"BUY\"` / `\"SELL\"`. **Null when the command was a modify/close/cancel**, which carry no action |\n| `volume` | number | Lots |\n\nFires when a fill is observed and ingested into the verified record. Pending\nplacements (`LIMIT` / `STOP`) do not produce one at placement time.\n\n### `seal.appended`\nTwo shapes, distinguished by which key is present. Both carry `seal`:\n\n*At fill time (a single deal was sealed):*\n| Field | Type |\n|---|---|\n| `account_id` | string |\n| `deal_id` | integer |\n| `seal` | object (below) |\n\n*On a session history push (a batch was sealed):*\n| Field | Type | Notes |\n|---|---|---|\n| `account_id` | string | |\n| `deals` | integer | How many deals were in the batch |\n| `source` | string | Always `\"history\"` |\n| `seal` | object (below) | |\n\nThe `seal` object:\n| Field | Type | Notes |\n|---|---|---|\n| `algo` | string | Always `\"sha256\"` |\n| `digest` | string | SHA-256 hex of the record's canonical JSON |\n| `prevDigest` | string \\| null | The previous link in the chain; `null` for the first seal on the account |\n| `hmac` | string | HMAC-SHA256 hex over `(prevDigest or \"\") + digest` |\n| `sealedAt` | string | ISO-8601 UTC |\n\n### `copy.master_fill`\n| Field | Type | Notes |\n|---|---|---|\n| `account_id` | string | The **master/provider** account |\n| `command_id` | string | |\n| `request` | object | The original order request (below) |\n| `result` | object | `{\"retcode\": int, \"order\": int\\|null, \"deal\": int\\|null, \"price\": number\\|null}` |\n\n`request` is what was submitted, with unset fields **absent** (not null):\n`action` (`\"BUY\"`/`\"SELL\"`), `symbol`, `volume`, and optionally `type`,\n`price`, `sl`, `tp`, `deviation`, `comment`.\n\nFires only for an entry fill on an account whose `kind` is `master`. Modifies,\ncloses, cancels and pending placements do not fan out and do not fire this.\nThe payload describes the master's fill; it does not enumerate followers.\n\n### `webhook.test`\n| Field | Type |\n|---|---|\n| `webhook_id` | string |\n| `message` | string (`\"This is a Verilo test event.\"`) |\n\nProduced only by `POST /v1/webhooks/{webhook_id}/test`, delivered only to that\nendpoint. It **bypasses the endpoint's `event_types` filter**, so a test event\narrives even at an endpoint subscribed to something else \u2014 and for the same\nreason `\"webhook.test\"` is not a subscribable type and is rejected in\n`event_types`.","operationId":"webhook_event_types_v1_webhooks_event_types_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEventTypes"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/webhooks":{"post":{"summary":"Register a webhook endpoint","description":"**`secret` is returned exactly once, here.** It is the HMAC key for verifying `X-Verilo-Signature` on every delivery; it is not retrievable afterwards.\n\nAn empty `event_types` subscribes to ALL types.\n\nURLs are SSRF-checked at registration and re-checked immediately before every single delivery, so DNS rebinding cannot turn a public URL into an internal one.\n\nHonors `Idempotency-Key`.","operationId":"create_webhook_v1_webhooks_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhook"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedWebhook"}}}},"400":{"description":"The URL resolves to a private, loopback, link-local or metadata address and was refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Your key lacks the `webhooks.write` scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Malformed URL, or an unknown event type.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"get":{"summary":"List your webhook endpoints","description":"Secrets are never included.","operationId":"list_webhooks_v1_webhooks_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookList"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/webhooks/{webhook_id}":{"get":{"summary":"Get one webhook endpoint","description":"If `active` is false, `disabled_reason` says why we stopped delivering.","operationId":"get_webhook_v1_webhooks__webhook_id__get","parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","title":"Webhook Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Delete a webhook endpoint","description":"Deliveries stop immediately. Returns no body.","operationId":"delete_webhook_v1_webhooks__webhook_id__delete","parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","title":"Webhook Id"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Your key lacks the `webhooks.write` scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/webhooks/{webhook_id}/test":{"post":{"summary":"Send a test event to one endpoint","description":"Enqueues a synthetic `webhook.test` event to THIS endpoint and attempts it immediately, so you can verify your receiver and your signature check end to end without waiting for a real trade.\n\n**202 means enqueued, not delivered.** Poll `GET /v1/webhooks/{webhook_id}/deliveries` for the outcome; the durable worker retries on failure.","operationId":"test_webhook_v1_webhooks__webhook_id__test_post","parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","title":"Webhook Id"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookTestResponse"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Your key lacks the `webhooks.write` scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"Webhook delivery is not running on this deployment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/webhooks/{webhook_id}/deliveries":{"get":{"summary":"Delivery log for one endpoint","description":"Every attempt, with your receiver's response status, latency, the last error, and when the next retry is due. This is the record to read when a webhook 'never arrived'.","operationId":"list_deliveries_v1_webhooks__webhook_id__deliveries_get","parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","title":"Webhook Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDeliveriesPage"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Request body or query parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/accounts/{account_id}/orders":{"get":{"summary":"List trade commands (orders)","description":"The command/intent log, newest first, with `limit`/`offset` paging and an optional `status` filter.\n\nThis is the same data as `GET /accounts/{account_id}/trades`, in a paged envelope. For executed fills with realized P&L use `GET /accounts/{account_id}/deals` instead.","operationId":"list_orders_v1_accounts__account_id__orders_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"filter: PENDING|SENT|FILLED|REJECTED|UNKNOWN","title":"Status"},"description":"filter: PENDING|SENT|FILLED|REJECTED|UNKNOWN"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrdersPage"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Unknown `status` filter, or `limit`/`offset` out of range.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/accounts/{account_id}/orders/{command_id}":{"get":{"summary":"Get one order by command id","operationId":"get_order_v1_accounts__account_id__orders__command_id__get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}},{"name":"command_id","in":"path","required":true,"schema":{"type":"string","title":"Command Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Trade"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/accounts/{account_id}/balance":{"get":{"summary":"Live balance, equity and margin","description":"**Always check `source`.** `live` means the figures came from the connected terminal; `recorded` means the terminal is offline and these are the last stored equity point; `null` means we have no data for this account at all.\n\nA figure we do not have is null or absent -- it is never reported as zero.","operationId":"balance_summary_v1_accounts__account_id__balance_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BalanceSummary"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/accounts/{account_id}/symbols":{"get":{"summary":"Symbols currently open on this account","description":"The symbols carrying an OPEN POSITION in the live snapshot -- **not the broker's tradable catalog**, and not a symbol search. An account with no open positions returns an empty list.\n\nA broker-wide symbol catalog and REST quote/candle snapshots are not built yet; live ticks stream on the account WebSocket.","operationId":"account_symbols_v1_accounts__account_id__symbols_get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountSymbols"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/reference/brokers":{"get":{"summary":"Broker catalog, grouped","description":"Brokers with their regulators, datacenters, asset classes and login servers. Optionally filtered to those offering one `asset_class`.\n\nFor the flat server list that `POST /accounts` validates against, use `GET /brokers`.","operationId":"reference_brokers_v1_reference_brokers_get","parameters":[{"name":"asset_class","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"filter brokers offering this asset class","title":"Asset Class"},"description":"filter brokers offering this asset class"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReferenceBrokers"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/reference/asset-classes":{"get":{"summary":"Asset classes offered across the catalog","description":"The real union over the broker catalog -- the discovery filter for multi-asset copy trading, not a fixed enum.","operationId":"reference_asset_classes_v1_reference_asset_classes_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReferenceAssetClasses"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/providers/{provider_id}/track-record":{"get":{"summary":"A provider's sealed verified track record","description":"The provider's own tamper-evident record: net-of-cost metrics, risk-normalized return, skin-in-the-game, the cash-flow-neutral growth curve, and the hash-chained seal over all of it.\n\nOnly providers who have published a listing (`active` or `paused`) are retrievable -- consent is required, so an unpublished listing is a 404.","operationId":"provider_track_record_v1_providers__provider_id__track_record_get","parameters":[{"name":"provider_id","in":"path","required":true,"schema":{"type":"string","title":"Provider Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderTrackRecord"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such provider, or its listing is not public (draft or revoked).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/copy/subscriptions/{subscription_id}/performance":{"get":{"summary":"Replication rollup for a copy subscription","description":"How faithfully this subscription replicated: how many master fills were mirrored, filled, skipped by a guardrail, or rejected.\n\n**This measures replication, not profit.** For P&L read the follower account's `/deals`.","operationId":"copy_performance_copy_subscriptions__subscription_id__performance_get","parameters":[{"name":"subscription_id","in":"path","required":true,"schema":{"type":"string","title":"Subscription Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CopyPerformance"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/copy/subscriptions/{subscription_id}/guardrails":{"patch":{"summary":"Update a subscription's risk guardrails","description":"Changes position sizing and the max-drawdown auto-stop in place. Omitted fields are left untouched.\n\nApplies to FUTURE copied orders only -- positions already open on the follower are not resized or closed.","operationId":"update_copy_guardrails_copy_subscriptions__subscription_id__guardrails_patch","parameters":[{"name":"subscription_id","in":"path","required":true,"schema":{"type":"string","title":"Subscription Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateGuardrails"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CopySubscription"}}}},"400":{"description":"Unknown `sizing_mode`, or no guardrail field supplied.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Missing, unknown, or revoked `x-api-key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No such resource -- or it exists but is not owned by your key. Ownership failures are deliberately 404, never 403, so the API never confirms that another developer's id exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Request body or query parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Limits are enforced per source IP, not per key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}}},"components":{"schemas":{"Account":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"server":{"type":"string","title":"Server","description":"Exact broker server display name."},"login":{"type":"integer","title":"Login"},"kind":{"type":"string","title":"Kind","description":"Copy-trading role: `master` (can be copied) or `investor`."},"state":{"type":"string","title":"State","description":"Lifecycle state: `PROVISIONING`, `CONNECTING`, `CONNECTED`, `DEGRADED`, `RECONNECTING`, `FAILED`, `SUSPENDED`, `STOPPED`.\n\n`SUSPENDED` is not an error and is never something that happened to your terminal by accident: we stopped it on purpose, because it was not in use and a running terminal costs real money every hour. Nothing is deleted -- the terminal and everything on it are kept, `POST /accounts/{id}/resume` starts it again, and `pod_suspend_reason` says why we parked it. Treat it as parked, not as broken: `RECONNECTING` means a terminal is trying to get back and may need attention, `SUSPENDED` means it is waiting for you to ask for it."},"container_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Container Id","description":"Terminal pod id when we host it; null otherwise."},"developer_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Developer Id"},"exec_mode":{"type":"string","title":"Exec Mode","description":"`demo` or `live`."},"always_on":{"type":"boolean","title":"Always On","description":"Whether we keep a terminal running for this account."},"platform":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Platform","description":"Execution path: MT5, MT4 or CTRADER. Distinct from `kind`."},"tick_stream":{"type":"boolean","title":"Tick Stream","description":"Whether this account streams live `tick` frames on `GET /accounts/{id}/stream`. Off by default: streaming quotes materially increases the terminal's CPU use, only the terminal's chart symbol streams, and it is not a multi-symbol market-data feed. Toggle with `PATCH /accounts/{id}`; it applies when the terminal is next created.","default":false},"pod_suspended_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pod Suspended At","description":"When we STOPPED this account's terminal container to stop it costing money, or null while it is running. Set exactly when `state` is `SUSPENDED`: `state` says the terminal is parked, this says since when. A suspended terminal is **not** deleted: the container and its state are kept and `POST /accounts/{id}/resume` starts it again. Only non-billable terminals are ever suspended -- a live always-on terminal you pay for is never stopped by us."},"pod_suspend_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pod Suspend Reason","description":"Why it was suspended: `over_cap` (free-tier monthly connection-hours exhausted), `idle` (no live session for the idle window), `never_connected`, or `manual`."},"last_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Error","description":"Why this account last entered `FAILED`, in plain language \u2014 for example a broker server we could not resolve, or credentials the broker rejected.\n\nRead this instead of guessing from `state`. `FAILED` on its own cannot tell you whether to fix a password, pick a different server, or wait and retry, and those need completely different responses from you.\n\n`null` means there is no failure on record \u2014 it never means \"failed for an unknown reason\". Cleared automatically when the account leaves `FAILED`, so it never describes a problem that is already over."},"last_error_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Error At","description":"When `last_error` was recorded. Deliberately separate from `updated_at`, which moves for unrelated writes and so cannot tell you whether the message you are reading describes the failure you are currently looking at."},"keep_alive_until":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Keep Alive Until","description":"When this account's keep-alive pin expires, or null if it is not pinned. While pinned, we will not stop this terminal for being idle -- which is how you keep a **demo** terminal up while you integrate against it, since a demo account is never billable and so is otherwise always a candidate for the idle sweep.\n\nA value in the past means the pin has expired and the terminal is an idle-sweep candidate again. Set it with `keep_alive_days` on `PATCH /accounts/{account_id}`.\n\n**It does not exempt you from your plan's monthly connection-hours cap**, and it does not restart a terminal that is already suspended -- use `POST /accounts/{account_id}/resume` for that.\n\nPinning does not make a terminal free. A running terminal costs a measured $10.02 a month of real infrastructure whether or not anyone is connected to it, and pinning means that keeps being spent for as long as the pin lasts. That is why a pin always expires, why 1825 days is the maximum, and why pinning does NOT exempt you from your plan's monthly connection-hours cap."},"created_at":{"type":"string","title":"Created At"},"updated_at":{"type":"string","title":"Updated At"}},"additionalProperties":true,"type":"object","required":["id","name","server","login","kind","state","exec_mode","always_on","created_at","updated_at"],"title":"Account","description":"A connected broker account. Mirrors ``app.repo.accounts._row``.\n\nThe stored broker password is never part of any response."},"AccountMetrics":{"properties":{"account_id":{"type":"string","title":"Account Id"},"trades":{"$ref":"#/components/schemas/TradeMetrics"},"equity":{"$ref":"#/components/schemas/EquitySummary"}},"additionalProperties":true,"type":"object","required":["account_id","trades","equity"],"title":"AccountMetrics"},"AccountState":{"properties":{"account":{"$ref":"#/components/schemas/Account"},"snapshot":{"anyOf":[{"$ref":"#/components/schemas/OpenObject"},{"type":"null"}],"description":"The most recent raw terminal snapshot, or null if none has landed yet. Shape is set by the terminal, not by this API."}},"additionalProperties":true,"type":"object","required":["account"],"title":"AccountState"},"AccountSymbols":{"properties":{"account_id":{"type":"string","title":"Account Id"},"symbols":{"items":{"type":"string"},"type":"array","title":"Symbols","description":"Sorted, de-duplicated symbol names."},"source":{"type":"string","title":"Source","description":"How the list was derived. Currently always `live_positions` -- these are the symbols with an OPEN POSITION, not the broker's full tradable catalog."}},"additionalProperties":true,"type":"object","required":["account_id","symbols","source"],"title":"AccountSymbols"},"AddProviderSegment":{"properties":{"account_id":{"type":"string","title":"Account Id"}},"type":"object","required":["account_id"],"title":"AddProviderSegment"},"Annotation":{"properties":{"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"rating":{"anyOf":[{"type":"integer","maximum":5.0,"minimum":1.0},{"type":"null"}],"title":"Rating"},"playbook_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Playbook Id"},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags"}},"type":"object","title":"Annotation","description":"Everything a trader may write. Note what cannot be set: no price, no\nsize, no P&L, no timestamp, no visibility."},"ApiError":{"properties":{"detail":{"anyOf":[{"type":"string"},{"additionalProperties":true,"type":"object"},{"items":{},"type":"array"}],"title":"Detail","description":"Human-readable reason, or a structured error object."}},"additionalProperties":true,"type":"object","required":["detail"],"title":"ApiError","description":"The error envelope every failure on this API uses.\n\n``detail`` is usually a human-readable string. A few step-up/entitlement\npaths return a small object instead (e.g.\n``{\"code\": \"step_up_required\", \"action\": \"...\"}``), so the type is declared\nas the union it actually is rather than the string it usually is."},"ApiKeyRow":{"properties":{"id":{"type":"string","title":"Id"},"developer_id":{"type":"string","title":"Developer Id"},"name":{"type":"string","title":"Name"},"key_prefix":{"type":"string","title":"Key Prefix","description":"First characters of the key, for identifying it in a list."},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes","description":"Least-privilege scopes. An EMPTY list means FULL access (the legacy default), not 'no access'."},"created_at":{"type":"string","title":"Created At"},"revoked_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Revoked At","description":"Null while the key is live."}},"additionalProperties":true,"type":"object","required":["id","developer_id","name","key_prefix","created_at"],"title":"ApiKeyRow","description":"A key as listed by ``GET /v1/keys``. Never contains the secret.\n\nMirrors ``app.repo.developers._key_row``."},"BalanceSummary":{"properties":{"account_id":{"type":"string","title":"Account Id"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source","description":"`live` (from the connected terminal), `recorded` (last stored equity point), or null when there is no data at all."},"balance":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Balance"},"equity":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Equity"},"margin_free":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Margin Free"},"margin":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Margin"},"currency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency"},"open_positions":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Open Positions","description":"Absent entirely when there is no snapshot to count."},"drawdown_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Drawdown Pct","description":"Cash-flow-adjusted. Absent when there is no snapshot."}},"additionalProperties":true,"type":"object","required":["account_id"],"title":"BalanceSummary","description":"Live (or last-recorded) balance and margin for one account.\n\nServed with ``response_model_exclude_unset=True``: when nothing has ever\nbeen recorded, `source` is null and the derived summary fields are absent\nrather than reported as zero. **A missing figure is never rendered as 0.**"},"BillingInvoice":{"properties":{"period":{"type":"string","title":"Period","description":"`YYYY-MM`, same format as the statement."},"period_year":{"type":"integer","title":"Period Year"},"period_month":{"type":"integer","title":"Period Month"},"subtotal_cents":{"type":"integer","title":"Subtotal Cents","description":"The amount billed for the period, in USD cents. Integer cents, never a float -- this is money that was actually charged."},"rate_card_version":{"type":"string","title":"Rate Card Version","description":"Which published rate card priced this invoice."},"external_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Ref","description":"The payment processor's reference, once one exists. Null on an invoice that has been computed and stored but not yet charged."},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"string"}],"title":"Created At"}},"additionalProperties":true,"type":"object","required":["period","period_year","period_month","subtotal_cents","rate_card_version","created_at"],"title":"BillingInvoice","description":"One stored invoice row, as `GET /v1/billing/invoices` returns it.\n\nEvery field below is a NOT NULL column of ``billing_invoices`` (migration\n056) except ``external_ref``, so only that one is optional. ``period`` is\nadded by the handler so an invoice lines up against the statement's\n``period`` without reformatting. ``line_items`` is deliberately not\nselected and is therefore absent."},"BillingUsage":{"properties":{"lines":{"items":{"$ref":"#/components/schemas/UsageLine"},"type":"array","title":"Lines"},"subtotal":{"type":"number","title":"Subtotal","description":"Sum of `lines[].amount`, USD."},"period":{"type":"string","title":"Period","description":"`YYYY-MM`."},"connection_hours":{"type":"number","title":"Connection Hours","description":"Terminal connection hours accrued so far this period."}},"additionalProperties":true,"type":"object","required":["lines","subtotal","period","connection_hours"],"title":"BillingUsage","description":"This period's statement so far.\n\n**Still not an invoice** -- it is a running total for a period that has not\nclosed -- but it is no longer a point-in-time count. The\n``billable_accounts`` line is a TIME-WEIGHTED accrual read out of the same\n``compute_invoice`` the invoicer prices from, so a terminal held for half a\nmonth contributes about half a terminal-month, a terminal deleted mid-month\nkeeps the time it actually ran, and the free-terminal allowance is already\nsubtracted. Because it is the same computation, the quote cannot drift from\nthe bill.\n\nThe remaining lines are NOT time-weighted: ``hosted_ea_pods`` and\n``copy_subscriber_slots`` are current counts priced at the full monthly\nrate, and ``overage_hours`` is always priced at 0 (no overage pricing is\nlive). Said plainly here because the difference between the lines is not\nvisible in the payload."},"BrokerServer":{"properties":{"broker":{"type":"string","title":"Broker"},"server":{"type":"string","title":"Server","description":"Pass this string VERBATIM as `server`."},"env":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Env","description":"`demo` or `live`."},"installer_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Installer Url"},"slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slug"},"region":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Region"},"reachability":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reachability"},"connectivity":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Connectivity","description":"Customer-facing connectivity wording derived from the last latency probe."},"connectivity_verified":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Connectivity Verified","description":"Whether `connectivity` reflects a real measurement."}},"additionalProperties":true,"type":"object","required":["broker","server"],"title":"BrokerServer","description":"One connectable broker server. Mirrors ``app.brokers.repo._server_row``.\n\n``server`` is the string `POST /accounts` validates EXACTLY and\ncase-sensitively -- a mis-cased name fails login silently inside the\nterminal, so it is rejected before an account row is written."},"BrokerSymbol":{"properties":{"symbol":{"type":"string","title":"Symbol","description":"Exactly as this broker names it."},"selected":{"type":"boolean","title":"Selected","description":"Whether the terminal currently has it in Market Watch. `false` is normal and is not a defect: our expert advisor trims the list to what is being traded, because every entry costs the terminal tick processing. It does not affect whether you can trade the symbol."}},"additionalProperties":true,"type":"object","required":["symbol","selected"],"title":"BrokerSymbol","description":"One instrument the broker offers.\n\nName and Market Watch membership only, on purpose: the full contract spec\nhas its own endpoint (`GET /accounts/{account_id}/symbols/{symbol}`), and\nreading it per row across a catalogue of thousands would put a per-symbol\nproperty read on the one socket that also carries every trade."},"BrokerSymbolsPage":{"properties":{"account_id":{"type":"string","title":"Account Id"},"source":{"type":"string","title":"Source","description":"Which platform answered: `MT5` or `MT4`."},"search":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Search","description":"The filter that was applied, echoed back. Null when the whole catalogue was requested."},"count":{"type":"integer","title":"Count","description":"How many rows are in `symbols`."},"matched":{"type":"integer","title":"Matched","description":"How many symbols matched before the cap. Greater than `count` means the answer was truncated."},"total":{"type":"integer","title":"Total","description":"How many instruments the broker offers in total, regardless of `search`."},"limit":{"type":"integer","title":"Limit","description":"The clamped cap that was applied."},"truncated":{"type":"boolean","title":"Truncated","description":"True when more symbols matched than were returned. Stated rather than left to be inferred: a truncated list that looked complete would read as \"this broker does not offer it\"."},"symbols":{"items":{"$ref":"#/components/schemas/BrokerSymbol"},"type":"array","title":"Symbols"}},"additionalProperties":true,"type":"object","required":["account_id","source","count","matched","total","limit","truncated","symbols"],"title":"BrokerSymbolsPage","description":"The broker's own instrument list, read live from the terminal."},"BusinessEvent":{"properties":{"event_id":{"type":"string","maxLength":128,"minLength":1,"title":"Event Id"},"event_name":{"type":"string","title":"Event Name"},"properties":{"additionalProperties":true,"type":"object","title":"Properties"}},"type":"object","required":["event_id","event_name"],"title":"BusinessEvent","description":"Body for POST /v1/analytics/business-events -- the dual-track,\nconsent-independent server-side capture for critical business events\n(checkout completed, subscription created, KYC passed, payout activated).\nNo subject_id field: the caller's own identity (Principal.developer_id)\nis always the subject, exactly like every other owner-scoped write in\nthis codebase -- never accepted from the request body."},"BusinessEventRecorded":{"properties":{"recorded":{"type":"boolean","title":"Recorded","description":"Whether the event was stored."},"created":{"type":"boolean","title":"Created","description":"False means this `event_id` had already been recorded and the call was a no-op. Ingest is idempotent on `event_id`, so a retry never double-counts."},"event":{"anyOf":[{"$ref":"#/components/schemas/OpenObject"},{"type":"null"}],"description":"The stored row, including the values after scrubbing."}},"additionalProperties":true,"type":"object","required":["recorded","created"],"title":"BusinessEventRecorded"},"Candle":{"properties":{"time":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"null"}],"title":"Time","description":"Bar OPEN time, UTC epoch seconds."},"time_utc":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Time Utc","description":"The same instant as an ISO-8601 UTC string."},"open":{"anyOf":[{"type":"number"},{"type":"integer"},{"type":"null"}],"title":"Open"},"high":{"anyOf":[{"type":"number"},{"type":"integer"},{"type":"null"}],"title":"High"},"low":{"anyOf":[{"type":"number"},{"type":"integer"},{"type":"null"}],"title":"Low"},"close":{"anyOf":[{"type":"number"},{"type":"integer"},{"type":"null"}],"title":"Close"},"tick_volume":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"null"}],"title":"Tick Volume"},"real_volume":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"null"}],"title":"Real Volume"},"spread":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"null"}],"title":"Spread"}},"additionalProperties":true,"type":"object","title":"Candle","description":"One CLOSED bar.\n\nEvery field is emitted by the builder for every bar, so nothing here is\nadded or removed by declaring it -- but each value is passed through from\nthe terminal and may be null if that terminal did not report it, so every\nfield is optional. Numeric types are unions (`int | float`) rather than\n`float`, which keeps an integral value on the wire as the integer the\nterminal sent instead of silently rewriting it as `1.0`."},"CandlesPage":{"properties":{"account_id":{"type":"string","title":"Account Id"},"symbol":{"type":"string","title":"Symbol"},"timeframe":{"type":"string","title":"Timeframe"},"count":{"type":"integer","title":"Count","description":"How many bars are in `candles`."},"requested":{"type":"integer","title":"Requested","description":"The clamped `n` that was asked for."},"truncated":{"type":"boolean","title":"Truncated","description":"True when the terminal returned fewer bars than requested. That is an honest answer, not an error -- but it is NOT by itself a retention limit: it means either the broker holds no more history here, or this terminal has not finished assembling it yet. Read `synchronized` to tell those apart. To find how far back a broker actually goes, page backward until you get an empty page WITH `synchronized: true`, or read `exhausted`, which states it directly."},"exhausted":{"type":"boolean","title":"Exhausted","description":"True when this empty page is genuinely the START of the history the terminal holds -- the terminating condition for a backward pager. Only ever set when `synchronized` is true; an empty page with `synchronized: false` is a cold cache, which is the opposite. Stop paging on `exhausted`, never on an error.","default":false},"syncing":{"type":"boolean","title":"Syncing","description":"True when the terminal is still assembling this series. The page is not a fact about the market yet -- retry. A request that is still syncing after our own retries comes back as HTTP 202 with Retry-After rather than a 4xx, because it is not the caller's mistake.","default":false},"synchronized":{"type":"boolean","title":"Synchronized","description":"False means the terminal is still assembling this timeseries from the server -- ask again shortly. A short or empty page with `synchronized: false` is a COLD CACHE, not the end of the broker's history.","default":true},"first_date":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"First Date","description":"UTC epoch seconds of the earliest bar THIS TERMINAL holds locally for this symbol/timeframe. This one is reliable and varies per timeframe -- measured on a live Darwinex terminal for EURUSD: M5 2026-08-07, H1 2025-08-20, D1 2006-12-31. Note history is PER-TERMINAL state that downloads on demand, so this grows as you page backward and resets if the terminal is rebuilt."},"before":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Before","description":"Echo of the backward cursor used for this page, or null when the most recent bars were requested."},"source":{"type":"string","title":"Source","description":"Which platform answered, e.g. `MT5`."},"server_gmt_offset":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"null"}],"title":"Server Gmt Offset","description":"The broker-server-to-UTC offset the terminal used when converting bar times, so the conversion is auditable."},"candles":{"items":{"$ref":"#/components/schemas/Candle"},"type":"array","title":"Candles"}},"additionalProperties":true,"type":"object","required":["account_id","symbol","timeframe","count","requested","truncated","source","candles"],"title":"CandlesPage","description":"Closed bars for one (symbol, timeframe), oldest first.\n\nEvery key is written unconditionally by `app.pods.candles`, so all are\nrequired except the nullable ones below, which reflect whatever the\nterminal reported and may be absent on an older EA binary."},"Checkout":{"properties":{"checkout_id":{"type":"string","title":"Checkout Id"},"url":{"type":"string","title":"Url","description":"Send the follower here to pay."},"follower_charge_cents":{"type":"integer","title":"Follower Charge Cents","description":"What the follower pays."},"provider_payout_cents":{"type":"integer","title":"Provider Payout Cents","description":"What the provider receives."},"plan":{"type":"string","title":"Plan"}},"additionalProperties":true,"type":"object","required":["checkout_id","url","follower_charge_cents","provider_payout_cents","plan"],"title":"Checkout"},"ConnectOanda":{"properties":{"v20_account_id":{"type":"string","title":"V20 Account Id"},"token":{"type":"string","title":"Token"},"is_live":{"type":"boolean","title":"Is Live","default":false}},"type":"object","required":["v20_account_id","token"],"title":"ConnectOanda"},"ConsentBody":{"properties":{"account_id":{"type":"string","title":"Account Id"},"granted":{"type":"boolean","title":"Granted"}},"type":"object","required":["account_id","granted"],"title":"ConsentBody"},"ConsentCategories":{"properties":{"functional":{"type":"boolean","title":"Functional","default":false},"analytics":{"type":"boolean","title":"Analytics","default":false},"marketing":{"type":"boolean","title":"Marketing","default":false}},"type":"object","title":"ConsentCategories","description":"Non-essential categories a subject can independently toggle.\nstrictly_necessary is deliberately absent -- it is always True and is\nnever accepted from a client (spec \u00a71: \"Strictly-necessary always-on\")."},"ConsentState":{"properties":{"subject_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject Type"},"subject_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Subject Id"},"region":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Region"},"regime":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Regime","description":"Which privacy regime applies -- it decides whether categories default ON or OFF."},"categories":{"anyOf":[{"additionalProperties":{"type":"boolean"},"type":"object"},{"type":"null"}],"title":"Categories","description":"Consent decision per processing category."},"gpc_honored":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Gpc Honored","description":"Whether a Global Privacy Control signal was applied."},"policy_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Policy Version"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"string"},{"type":"null"}],"title":"Created At"},"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id"},"has_recorded_consent":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Has Recorded Consent","description":"**False means these are defaults, not a decision the subject made** -- keep showing the banner. True means the values are a recorded choice.\n\nPresent on the effective-state read. ABSENT on history rows and on the record-consent receipt, where every row is by definition a real recorded decision."}},"additionalProperties":true,"type":"object","title":"ConsentState","description":"Effective consent for one subject.\n\nServed with ``response_model_exclude_unset=True``: a recorded consent row\nand a synthesised regional default are different objects, and this reports\nwhichever it actually is rather than padding one to look like the other."},"CopyDirective":{"properties":{"risk_pct":{"type":"number","maximum":0.05,"exclusiveMinimum":0.0,"title":"Risk Pct"},"dd_floor_pct":{"anyOf":[{"type":"number","maximum":0.9,"exclusiveMinimum":0.0},{"type":"null"}],"title":"Dd Floor Pct"},"atten_ceiling":{"anyOf":[{"type":"number","maximum":0.05,"exclusiveMinimum":0.0},{"type":"null"}],"title":"Atten Ceiling"},"band_normal":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Band Normal"},"band_hard":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Band Hard"},"margin_safety":{"anyOf":[{"type":"number","maximum":1.0,"exclusiveMinimum":0.0},{"type":"null"}],"title":"Margin Safety"},"minlot_cap_pct":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Minlot Cap Pct"},"trail_mode":{"anyOf":[{"type":"integer","maximum":2.0,"minimum":0.0},{"type":"null"}],"title":"Trail Mode"},"trail_step":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Trail Step"},"trail_trigger":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Trail Trigger"}},"type":"object","required":["risk_pct"],"title":"CopyDirective","description":"Per-follower risk directive, the server half of set-once-then-auto-\ntrade: the EA caches it and enforces every guardrail locally (sizing spec\ntests/test_copyengine_sizing.py). Field names are the API's; the wire\nuses the EA's compact keys (rp/dd/bn/bh/tm/ts/tt...).\n\nrisk_pct is REQUIRED and must be positive: rp <= 0 is the EA's\nfail-closed sentinel, so storing it would create a follower that looks\nconfigured and copies nothing, silently."},"CopyDirectiveResponse":{"properties":{"directive":{"$ref":"#/components/schemas/OpenObject","description":"The normalized directive as the relay stored it."},"pushed":{"type":"boolean","title":"Pushed","description":"True if a connected follower EA received it live. False means it is stored and will apply on the EA's next hello -- not that it failed."}},"additionalProperties":true,"type":"object","required":["directive","pushed"],"title":"CopyDirectiveResponse"},"CopyOrder":{"properties":{"id":{"type":"string","title":"Id"},"subscription_id":{"type":"string","title":"Subscription Id"},"master_command_id":{"type":"string","title":"Master Command Id"},"follower_command_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Follower Command Id"},"status":{"type":"string","title":"Status"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"},"request":{"anyOf":[{"$ref":"#/components/schemas/OpenObject"},{"type":"null"}]},"result":{"anyOf":[{"$ref":"#/components/schemas/OpenObject"},{"type":"null"}]},"created_at":{"type":"string","title":"Created At"}},"additionalProperties":true,"type":"object","required":["id","subscription_id","master_command_id","status","created_at"],"title":"CopyOrder","description":"One replicated order. Mirrors ``app.repo.copy._order_row``."},"CopyPerformance":{"properties":{"subscription_id":{"type":"string","title":"Subscription Id"},"status":{"type":"string","title":"Status"},"mode":{"type":"string","title":"Mode"},"sizing_mode":{"type":"string","title":"Sizing Mode"},"sizing_value":{"anyOf":[{"type":"number"},{"type":"integer"},{"type":"null"}],"title":"Sizing Value"},"max_drawdown_pct":{"anyOf":[{"type":"number"},{"type":"integer"},{"type":"null"}],"title":"Max Drawdown Pct"},"orders_total":{"type":"integer","title":"Orders Total"},"orders_filled":{"type":"integer","title":"Orders Filled","description":"FILLED plus PAPER_FILLED."},"orders_by_status":{"additionalProperties":{"type":"integer"},"type":"object","title":"Orders By Status","description":"Raw counts keyed by copy-order status."}},"additionalProperties":true,"type":"object","required":["subscription_id","status","mode","sizing_mode","orders_total","orders_filled","orders_by_status"],"title":"CopyPerformance","description":"Replication rollup for one subscription, computed from real copy orders.\n\nThese are REPLICATION counts, not profit: `orders_filled` means the copy\nreached the broker, and says nothing about whether it made money."},"CopyStrategy":{"properties":{"id":{"type":"string","title":"Id"},"developer_id":{"type":"string","title":"Developer Id"},"master_account_id":{"type":"string","title":"Master Account Id"},"name":{"type":"string","title":"Name"},"created_at":{"type":"string","title":"Created At"}},"additionalProperties":true,"type":"object","required":["id","developer_id","master_account_id","name","created_at"],"title":"CopyStrategy"},"CopyStrategyCapacity":{"properties":{"max_subscribers":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Max Subscribers","description":"Maximum accounts that may subscribe. null = uncapped. 0 closes the book to new subscribers."},"max_total_volume":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Max Total Volume","description":"Maximum COMMITTED volume across subscriptions -- the replica lots this book would place for one 1.00-lot master fill. Not live open exposure. null = uncapped."}},"type":"object","title":"CopyStrategyCapacity","description":"Both fields default to None, and None means UNCAPPED -- so a PUT that\nomits a field clears that cap rather than leaving it. That is the honest\nreading of a PUT (it replaces the resource), and the alternative -- treating\nomission as \"leave alone\" -- would make it impossible to remove a cap\nthrough this endpoint at all."},"CopyStrategySubscription":{"properties":{"id":{"type":"string","title":"Id"},"strategy_id":{"type":"string","title":"Strategy Id"},"subscriber_account_id":{"type":"string","title":"Subscriber Account Id"},"volume_mode":{"type":"string","title":"Volume Mode"},"volume_value":{"anyOf":[{"type":"number"},{"type":"integer"},{"type":"null"}],"title":"Volume Value"},"status":{"type":"string","title":"Status"},"created_at":{"type":"string","title":"Created At"}},"additionalProperties":true,"type":"object","required":["id","strategy_id","subscriber_account_id","volume_mode","status","created_at"],"title":"CopyStrategySubscription"},"CopySubscription":{"properties":{"id":{"type":"string","title":"Id"},"follower_account_id":{"type":"string","title":"Follower Account Id"},"master_account_id":{"type":"string","title":"Master Account Id"},"mode":{"type":"string","title":"Mode","description":"Execution mode -- e.g. auto-copy, or push-to-confirm where the follower approves each order."},"sizing_mode":{"type":"string","title":"Sizing Mode"},"sizing_value":{"anyOf":[{"type":"number"},{"type":"integer"},{"type":"null"}],"title":"Sizing Value"},"max_drawdown_pct":{"anyOf":[{"type":"number"},{"type":"integer"},{"type":"null"}],"title":"Max Drawdown Pct","description":"Auto-stop threshold; null means no drawdown stop."},"status":{"type":"string","title":"Status"},"stopped_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stopped Reason"},"created_at":{"type":"string","title":"Created At"},"updated_at":{"type":"string","title":"Updated At"}},"additionalProperties":true,"type":"object","required":["id","follower_account_id","master_account_id","mode","sizing_mode","status","created_at","updated_at"],"title":"CopySubscription","description":"A follower account copying a master. Mirrors ``app.repo.copy._sub_row``."},"CreateAccount":{"properties":{"name":{"type":"string","title":"Name"},"server":{"type":"string","title":"Server"},"login":{"type":"integer","title":"Login"},"password":{"type":"string","title":"Password"},"kind":{"type":"string","enum":["master","investor"],"title":"Kind"},"exec_mode":{"type":"string","enum":["demo","live"],"title":"Exec Mode","default":"demo"},"always_on":{"type":"boolean","title":"Always On","default":false},"platform":{"type":"string","enum":["MT5","MT4","CTRADER","OANDA"],"title":"Platform","default":"MT5"},"tick_stream":{"type":"boolean","title":"Tick Stream","description":"Stream live quotes for this account on `GET /accounts/{id}/stream` as `tick` frames. **Off by default.** Four things are true and worth knowing before you turn it on:\n\n* **It materially increases the terminal's CPU use.** A hosted terminal's CPU is tick-driven; we have measured the same pod at roughly 13% idle and 28% while quotes are flowing. Leave it off on accounts nobody is watching.\n* **Only the terminal's chart symbol streams.** One symbol per account, chosen by the terminal, not by you.\n* **This is not a multi-symbol market-data feed.** There is no way to subscribe to a symbol on this stream; if you need a quote for a symbol the account is not charted on, this will not give you one.\n* **MT5 only.** MT4's bridge expert has no quote producer, so the flag is refused on MT4 and cTrader accounts rather than accepted and ignored.\n\nChanging it takes effect when the terminal is next created, because the terminal receives it as container environment at start-up. Call `POST /accounts/{id}/reprovision` to apply it to a running terminal -- knowing that recreating a terminal is a data-integrity event.","default":false}},"type":"object","required":["name","server","login","password","kind"],"title":"CreateAccount","description":"Every enum here is CONSTRAINED IN THE DATABASE, so it must be\nconstrained here too.\n\nMEASURED 2026-08-12: `kind` was a bare `str`. An integrator sent the\nobvious-looking `kind=\"demo\"` -- it is a DEMO account, after all -- and got\na bare 500 in ~0.2s on every broker they tried. FastAPI validated nothing,\nthe value reached Postgres, and `accounts_kind_check` raised\nCheckViolationError out of the route. They reasonably concluded it was an\nentitlement crash and spent their debugging budget in the wrong place.\n\n`kind` is the MetaTrader PASSWORD type, not the account environment:\n`master` (trades) or `investor` (read-only). `exec_mode` is the environment\n(`demo`/`live`). Two different questions that both sound like \"is this a\ndemo?\", which is exactly why the wrong value was so easy to send.\n\nLiteral turns each into a 422 that NAMES the accepted values, before\nanything is written or any pod is started."},"CreateCopyStrategy":{"properties":{"master_account_id":{"type":"string","title":"Master Account Id"},"name":{"type":"string","title":"Name"}},"type":"object","required":["master_account_id","name"],"title":"CreateCopyStrategy"},"CreateCopyStrategySubscription":{"properties":{"strategy_id":{"type":"string","title":"Strategy Id"},"subscriber_account_id":{"type":"string","title":"Subscriber Account Id"},"volume_mode":{"type":"string","title":"Volume Mode","default":"fixed"},"volume_value":{"type":"number","exclusiveMinimum":0.0,"title":"Volume Value","default":1.0}},"type":"object","required":["strategy_id","subscriber_account_id"],"title":"CreateCopyStrategySubscription"},"CreateEA":{"properties":{"name":{"type":"string","title":"Name"},"spec":{"additionalProperties":true,"type":"object","title":"Spec"}},"type":"object","required":["name","spec"],"title":"CreateEA"},"CreateProvider":{"properties":{"account_id":{"type":"string","title":"Account Id"},"display_name":{"type":"string","title":"Display Name"},"price_cents":{"type":"integer","title":"Price Cents"}},"type":"object","required":["account_id","display_name","price_cents"],"title":"CreateProvider"},"CreateSubscription":{"properties":{"follower_account_id":{"type":"string","title":"Follower Account Id"},"master_account_id":{"type":"string","title":"Master Account Id"},"mode":{"type":"string","title":"Mode"},"sizing_mode":{"type":"string","title":"Sizing Mode"},"sizing_value":{"type":"number","exclusiveMinimum":0.0,"title":"Sizing Value"},"max_drawdown_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Max Drawdown Pct"}},"type":"object","required":["follower_account_id","master_account_id","mode","sizing_mode","sizing_value"],"title":"CreateSubscription"},"CreateSubscriptionBody":{"properties":{"provider_id":{"type":"string","title":"Provider Id"},"plan":{"type":"string","title":"Plan"},"follower_account_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Follower Account Id"}},"type":"object","required":["provider_id","plan"],"title":"CreateSubscriptionBody"},"CreateTicketBody":{"properties":{"category":{"type":"string","title":"Category"},"subject":{"type":"string","title":"Subject"},"body":{"type":"string","title":"Body"},"context":{"$ref":"#/components/schemas/TicketContext","default":{}},"client_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Ref"}},"type":"object","required":["category","subject","body"],"title":"CreateTicketBody"},"CreateWebhook":{"properties":{"url":{"type":"string","title":"Url"},"event_types":{"items":{"type":"string"},"type":"array","title":"Event Types","default":[]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"}},"type":"object","required":["url"],"title":"CreateWebhook"},"CreatedJournalShare":{"properties":{"token":{"type":"string","title":"Token","description":"The grant itself -- anyone holding it can read the share."},"scope":{"type":"string","title":"Scope"},"deal_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Deal Id"},"notice":{"type":"string","title":"Notice","description":"Plain-language warning shown to the sharer."}},"additionalProperties":true,"type":"object","required":["token","scope","notice"],"title":"CreatedJournalShare"},"CreatedWebhook":{"properties":{"id":{"type":"string","title":"Id"},"developer_id":{"type":"string","title":"Developer Id"},"url":{"type":"string","title":"Url"},"event_types":{"items":{"type":"string"},"type":"array","title":"Event Types","description":"Event types to deliver. An EMPTY list means ALL types."},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"active":{"type":"boolean","title":"Active"},"disabled_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disabled Reason","description":"Why we stopped delivering (e.g. repeated failures). Null while healthy."},"created_at":{"type":"string","title":"Created At"},"updated_at":{"type":"string","title":"Updated At"},"secret":{"type":"string","title":"Secret","description":"HMAC signing secret. Shown ONCE, here. Use it to verify the `X-Verilo-Signature` header on every delivery."}},"additionalProperties":true,"type":"object","required":["id","developer_id","url","active","created_at","updated_at","secret"],"title":"CreatedWebhook"},"CtraderAuthorize":{"properties":{"authorize_url":{"type":"string","title":"Authorize Url","description":"Send the user here to approve the connection. The URL carries a single-use state nonce; it expires and cannot be replayed."}},"additionalProperties":true,"type":"object","required":["authorize_url"],"title":"CtraderAuthorize"},"DealsPage":{"properties":{"count":{"type":"integer","title":"Count","description":"Rows in THIS page."},"total":{"type":"integer","title":"Total","description":"Rows matching the filter, before paging."},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"account_id":{"type":"string","title":"Account Id"},"deals":{"items":{"$ref":"#/components/schemas/OpenObject"},"type":"array","title":"Deals","description":"Executed deals with realized P&L, plus labeled deposit/withdrawal cash-flow markers (never counted as P&L)."}},"additionalProperties":true,"type":"object","required":["count","total","limit","offset","account_id"],"title":"DealsPage"},"DeveloperSummary":{"properties":{"id":{"type":"string","title":"Id"},"email":{"type":"string","title":"Email"},"tier":{"type":"string","title":"Tier","description":"Plan tier the key is entitled to."}},"additionalProperties":true,"type":"object","required":["id","email","tier"],"title":"DeveloperSummary"},"DraftSpecBody":{"properties":{"description":{"type":"string","minLength":1,"title":"Description"}},"type":"object","required":["description"],"title":"DraftSpecBody"},"DsarExport":{"properties":{"request":{"$ref":"#/components/schemas/DsarRequest"},"data":{"$ref":"#/components/schemas/OpenObject","description":"The exported personal data bundle."}},"additionalProperties":true,"type":"object","required":["request","data"],"title":"DsarExport"},"DsarRequest":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id"},"developer_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Developer Id"},"kind":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kind","description":"`EXPORT` or `DELETE`."},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"result":{"anyOf":[{"$ref":"#/components/schemas/OpenObject"},{"type":"null"}]},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"string"},{"type":"null"}],"title":"Created At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"string"},{"type":"null"}],"title":"Completed At"}},"additionalProperties":true,"type":"object","title":"DsarRequest","description":"A data subject access request and its outcome."},"EquityPage":{"properties":{"count":{"type":"integer","title":"Count","description":"Rows in THIS page."},"total":{"type":"integer","title":"Total","description":"Rows matching the filter, before paging."},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"account_id":{"type":"string","title":"Account Id"},"points":{"items":{"$ref":"#/components/schemas/OpenObject"},"type":"array","title":"Points","description":"Recorded balance/equity/free-margin points."}},"additionalProperties":true,"type":"object","required":["count","total","limit","offset","account_id"],"title":"EquityPage"},"EquitySummary":{"properties":{"balance":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Balance"},"equity":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Equity"},"open_positions":{"type":"integer","title":"Open Positions","default":0},"drawdown_pct":{"type":"number","title":"Drawdown Pct","description":"Cash-flow-adjusted drawdown -- a deposit or withdrawal does not register as a loss.","default":0.0}},"additionalProperties":true,"type":"object","title":"EquitySummary","description":"Live balance/equity summary. Mirrors ``compute_equity_metrics``.\n\nAll four keys are always present; `balance` and `equity` are null until the\naccount's terminal has sent its first snapshot."},"ExecConsent":{"properties":{"account_id":{"type":"string","title":"Account Id"},"granted":{"type":"boolean","title":"Granted","description":"Whether this account's fills feed public aggregates."},"notice":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notice","description":"Returned on every grant AND revoke, because it is the part of the bargain most likely to be misunderstood: revoking removes your fills from FUTURE aggregates only. Already-published reports are not recomputed."}},"additionalProperties":true,"type":"object","required":["account_id","granted"],"title":"ExecConsent"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HaltBody":{"properties":{"commandId":{"type":"string","title":"Commandid","description":"Caller-generated idempotency key for the flatten, as a **UUID**. Re-sending the same `commandId` returns the original flatten and never re-dispatches close orders.","examples":["3f2a7c18-9b4e-4a51-8d3c-1e6f0b5d29aa"]},"reason":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Reason","description":"Why trading was halted. Stored on the account and shown on the halt status, so whoever finds it later knows what happened."}},"type":"object","required":["commandId"],"title":"HaltBody","description":"Emergency stop. `commandId` is required for the same reason it is on\nPOST /trades: the flatten this triggers is a money op, and a retried or\nduplicated emergency request must land on the ORIGINAL flatten rather than\nfiring a second wave of close orders at an account someone may have since\nre-armed and re-entered."},"HostEA":{"properties":{"account_id":{"type":"string","title":"Account Id"}},"type":"object","required":["account_id"],"title":"HostEA"},"IssueKey":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"scopes":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Scopes"}},"type":"object","title":"IssueKey"},"IssuedKey":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"key_prefix":{"type":"string","title":"Key Prefix"},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes","description":"Least-privilege scopes. An EMPTY list means FULL access (the legacy default), not 'no access'."}},"additionalProperties":true,"type":"object","required":["id","name","key_prefix"],"title":"IssuedKey","description":"The key metadata returned alongside a freshly-issued secret.\n\nDeliberately narrower than `ApiKeyRow`: the issue handler echoes only these\nfields, and this schema says exactly that rather than promising the full row."},"IssuedKeyResponse":{"properties":{"api_key":{"type":"string","title":"Api Key","description":"The plaintext secret. Shown ONCE."},"key":{"$ref":"#/components/schemas/IssuedKey"}},"additionalProperties":true,"type":"object","required":["api_key","key"],"title":"IssuedKeyResponse"},"IssuedRelayKey":{"properties":{"relay_key":{"type":"string","title":"Relay Key","description":"The plaintext relay secret. Shown ONCE, here."},"key":{"$ref":"#/components/schemas/RelayKeyRow"},"endpoint":{"$ref":"#/components/schemas/RelayEndpoint"},"preset":{"type":"string","title":"Preset","description":"Paste-ready `.set` file contents for the expert advisor -- the same inputs a managed pod is given, so self-hosted and managed providers run identical configuration."},"preset_filename":{"type":"string","title":"Preset Filename"},"revoke_with":{"type":"string","title":"Revoke With","description":"The exact call that revokes this key."}},"additionalProperties":true,"type":"object","required":["relay_key","key","endpoint","preset","preset_filename","revoke_with"],"title":"IssuedRelayKey"},"JournalAnalytics":{"properties":{"summary":{"$ref":"#/components/schemas/OpenObject","description":"Win rate, expectancy, average win/loss, R distribution, hold time, hour/weekday breakdowns, max drawdown, gross profit/loss, and a `sample` size -- shown so four trades never render like four hundred."},"equityCurve":{"items":{"$ref":"#/components/schemas/OpenObject"},"type":"array","title":"Equitycurve","description":"Cumulative realized P&L in EXIT order, with running peak and drawdown."},"byTag":{"$ref":"#/components/schemas/OpenObject","description":"Breakdown keyed by tag name."},"byPlaybook":{"$ref":"#/components/schemas/OpenObject","description":"Breakdown keyed by playbook id."},"basis":{"type":"string","title":"Basis","description":"Plain-language statement of what was computed."}},"additionalProperties":true,"type":"object","required":["summary","equityCurve","byTag","byPlaybook","basis"],"title":"JournalAnalytics","description":"Journal analytics over SEALED trades only.\n\nNote `basis`: it is returned on the response deliberately, because it is\nthe claim that makes these figures worth more than a self-reported journal.\nTags and playbooks select WHICH trades a breakdown covers; they can never\nchange the numbers inside it."},"JournalAnnotation":{"properties":{"deal_id":{"type":"string","title":"Deal Id"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"rating":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rating"},"playbook_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Playbook Id"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"}},"additionalProperties":true,"type":"object","required":["deal_id"],"title":"JournalAnnotation","description":"A trader's own notes on one sealed trade.\n\nServed with ``response_model_exclude_unset=True``: an un-annotated trade\nreturns a stub rather than a 404 -- the trade exists, it simply has no notes\nyet, and a 404 would read as \"this trade is missing\"."},"JournalList":{"properties":{"trades":{"items":{"$ref":"#/components/schemas/JournalTrade"},"type":"array","title":"Trades"},"total":{"type":"integer","title":"Total"}},"additionalProperties":true,"type":"object","required":["trades","total"],"title":"JournalList"},"JournalShare":{"properties":{"token":{"type":"string","title":"Token"},"account_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Account Id"},"scope":{"type":"string","title":"Scope","description":"`journal` or `trade`."},"deal_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Deal Id"},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"string"},{"type":"null"}],"title":"Created At"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"string"},{"type":"null"}],"title":"Expires At","description":"Null means it never expires."}},"additionalProperties":true,"type":"object","required":["token","scope"],"title":"JournalShare"},"JournalShareList":{"properties":{"shares":{"items":{"$ref":"#/components/schemas/JournalShare"},"type":"array","title":"Shares","description":"EVERY share the owner has created -- a share nobody can see is a share nobody can revoke."}},"additionalProperties":true,"type":"object","required":["shares"],"title":"JournalShareList"},"JournalTrade":{"properties":{"side":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Side"},"volume":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Volume"},"profit":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Profit"},"open":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Open","description":"True while the trade is still open."},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"rating":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rating"},"playbookId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Playbookid"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"}},"additionalProperties":true,"type":"object","title":"JournalTrade","description":"One sealed trade with its annotation attached, if it has one."},"OandaConnected":{"properties":{"account_id":{"type":"string","title":"Account Id"},"state":{"type":"string","title":"State","description":"`CONNECTING` -- the adapter start is asynchronous."},"disclosure":{"$ref":"#/components/schemas/TokenCustodyDisclosure","description":"Repeated on the response, so it travels with the action."}},"additionalProperties":true,"type":"object","required":["account_id","state","disclosure"],"title":"OandaConnected"},"OkResponse":{"properties":{"ok":{"type":"boolean","title":"Ok","default":true}},"additionalProperties":true,"type":"object","title":"OkResponse"},"OpenObject":{"properties":{},"additionalProperties":true,"type":"object","title":"OpenObject","description":"An object whose keys are not fixed by the API.\n\nUsed where the payload is genuinely dynamic (a flag map keyed by flag name,\na broker-specific blob, a raw terminal snapshot). Declaring this says \"an\nobject, contents vary\" -- which is true -- instead of inventing fields."},"OrdersPage":{"properties":{"count":{"type":"integer","title":"Count","description":"Rows in THIS page."},"total":{"type":"integer","title":"Total","description":"Rows matching the filter, before paging."},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"account_id":{"type":"string","title":"Account Id"},"orders":{"items":{"$ref":"#/components/schemas/Trade"},"type":"array","title":"Orders","description":"Trade commands, newest first."}},"additionalProperties":true,"type":"object","required":["count","total","limit","offset","account_id"],"title":"OrdersPage"},"OwnedProvider":{"properties":{"id":{"type":"string","title":"Id"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"price_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Price Cents","description":"Monthly subscription price in cents."},"verification_tier":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Verification Tier","description":"How much of this record we can independently vouch for."},"track_record_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Track Record Days","description":"Length of the verified record. Short records are short."},"max_drawdown_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Max Drawdown Pct","description":"Signed drawdown as stored; discovery filters on its magnitude."},"net_return_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Net Return Pct","description":"Return AFTER broker commission and swap."},"gross_return_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Gross Return Pct","description":"Price-based return, excluding broker-specific commission and swap. **This is the figure cards display, the leaderboard ranks on, and `min_gross_return_pct` filters on** -- deliberately the same number in all three places."},"sharpe":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sharpe"},"verified_capital":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Verified Capital"},"score_cached":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Score Cached","description":"Cached ranking score -- the leaderboard's sort key."},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"Only `active` and `paused` listings are publicly discoverable; `draft` and `revoked` are not."},"max_copiers":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Copiers","description":"Copier cap, or null for uncapped."},"excluded_brokers":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Excluded Brokers","description":"Brokers this provider refuses to be copied on."},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"string"},{"type":"null"}],"title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"string"},{"type":"null"}],"title":"Updated At"},"account_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Account Id","description":"The backing broker account. Owner-only."},"stripe_account_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stripe Account Id","description":"Stripe Connect payout destination. Owner-only, and never part of any public projection."}},"additionalProperties":true,"type":"object","required":["id"],"title":"OwnedProvider","description":"A provider listing as returned to ITS OWNER.\n\nA superset of `PublicProvider`: the owner-facing writes return the full row\nfrom ``app.marketplace.providers._row``, which includes the two fields the\npublic projection deliberately strips. Modelled separately rather than\nreusing the public shape, so the reference does not claim these endpoints\nhide something they do not."},"PendingOrdersResponse":{"properties":{"account_id":{"type":"string","title":"Account Id"},"orders":{"items":{"$ref":"#/components/schemas/OpenObject"},"type":"array","title":"Orders","description":"Pending orders exactly as the terminal reports them. `type` is BUY_LIMIT / SELL_LIMIT / BUY_STOP / SELL_STOP as a string on both MetaTrader platforms."},"supported":{"type":"boolean","title":"Supported","description":"Whether this account's platform can report pending orders at all. **Read it before reading an empty list**: false means we cannot ask (cTrader, OANDA), which is a different fact from having none pending."},"platform":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Platform","description":"The platform that was asked, e.g. `MT5`."}},"additionalProperties":true,"type":"object","required":["account_id","supported"],"title":"PendingOrdersResponse","description":"Resting LIMIT/STOP orders -- entries that have NOT become positions.\n\nDeliberately does NOT inline the equity summary that ``PositionsResponse``\ncarries. A pending order commits no margin and moves no P&L, so putting\nbalance/equity/drawdown next to it would invite reading a resting order as\nexposure -- which is the one thing this list must never suggest."},"Playbook":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"owner_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owner Id"},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"string"},{"type":"null"}],"title":"Created At"}},"additionalProperties":true,"type":"object","required":["id","name"],"title":"Playbook"},"PlaybookBody":{"properties":{"name":{"type":"string","title":"Name"}},"type":"object","required":["name"],"title":"PlaybookBody"},"PlaybookList":{"properties":{"playbooks":{"items":{"$ref":"#/components/schemas/Playbook"},"type":"array","title":"Playbooks"}},"additionalProperties":true,"type":"object","required":["playbooks"],"title":"PlaybookList"},"PortfolioWarnings":{"properties":{"warnings":{"items":{"$ref":"#/components/schemas/OpenObject"},"type":"array","title":"Warnings","description":"Correlation/concentration warnings across the follower's live copied exposure. Empty list means none."}},"additionalProperties":true,"type":"object","title":"PortfolioWarnings"},"PositionsResponse":{"properties":{"balance":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Balance"},"equity":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Equity"},"open_positions":{"type":"integer","title":"Open Positions","default":0},"drawdown_pct":{"type":"number","title":"Drawdown Pct","description":"Cash-flow-adjusted drawdown -- a deposit or withdrawal does not register as a loss.","default":0.0},"account_id":{"type":"string","title":"Account Id"},"positions":{"items":{"$ref":"#/components/schemas/OpenObject"},"type":"array","title":"Positions","description":"Open positions exactly as the terminal reports them."},"connected":{"type":"boolean","title":"Connected","description":"Whether a terminal is connected **right now**. Read this before trusting anything else in this response.\n\n`false` means the numbers below are the last known values, not current ones \u2014 the terminal may have disconnected seconds or hours ago, and positions may have opened, closed or moved since. This is taken from the live socket, so unlike `state` on the account it cannot lag behind reality.","default":false},"as_of":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"As Of","description":"When we received the snapshot these numbers came from (UTC). `null` means no snapshot has ever arrived for this account."},"age_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Age Seconds","description":"How old that snapshot is, in seconds. A few seconds is normal \u2014 terminals report on a cadence, not per tick. Large values with `connected: false` mean you are reading stale data."}},"additionalProperties":true,"type":"object","required":["account_id"],"title":"PositionsResponse","description":"Open positions plus the same equity summary fields, inlined."},"ProviderDiscoveryPage":{"properties":{"count":{"type":"integer","title":"Count","description":"Rows in THIS page."},"total":{"type":"integer","title":"Total","description":"Rows matching the filter, before paging."},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"providers":{"items":{"$ref":"#/components/schemas/PublicProvider"},"type":"array","title":"Providers"}},"additionalProperties":true,"type":"object","required":["count","total","limit","offset","providers"],"title":"ProviderDiscoveryPage"},"ProviderPayouts":{"properties":{"entries":{"items":{"$ref":"#/components/schemas/OpenObject"},"type":"array","title":"Entries","description":"Individual ledger entries."},"totals":{"$ref":"#/components/schemas/OpenObject","description":"Rollup across the entries."}},"additionalProperties":true,"type":"object","required":["entries","totals"],"title":"ProviderPayouts"},"ProviderSegment":{"properties":{"id":{"type":"string","title":"Id"},"provider_id":{"type":"string","title":"Provider Id"},"account_id":{"type":"string","title":"Account Id"}},"additionalProperties":true,"type":"object","required":["id","provider_id","account_id"],"title":"ProviderSegment","description":"One broker-account identity segment of a provider."},"ProviderTrackRecord":{"properties":{"provider_id":{"type":"string","title":"Provider Id"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"track_record":{"$ref":"#/components/schemas/TrustReport"}},"additionalProperties":true,"type":"object","required":["provider_id","track_record"],"title":"ProviderTrackRecord"},"ProvisioningEta":{"properties":{"server":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Server","description":"Null means pooled across every broker server."},"measured":{"type":"boolean","title":"Measured"},"samples":{"type":"integer","title":"Samples"},"p50_seconds":{"type":"number","title":"P50 Seconds"},"p95_seconds":{"type":"number","title":"P95 Seconds"},"eta_minutes":{"anyOf":[{"type":"number"},{"type":"integer"}],"title":"Eta Minutes"}},"additionalProperties":true,"type":"object","required":["measured","samples","p50_seconds","p95_seconds","eta_minutes"],"title":"ProvisioningEta","description":"Measured per-broker setup-time estimate.\n\n``measured=false`` means there are not enough real runs for this server yet,\nso the numbers are a conservative fallback rather than an observation."},"ProvisioningTimeline":{"properties":{"account_id":{"type":"string","title":"Account Id"},"state":{"type":"string","title":"State","description":"Authoritative live account state."},"server":{"type":"string","title":"Server"},"timeline":{"items":{"$ref":"#/components/schemas/StateTransition"},"type":"array","title":"Timeline","description":"State transitions, oldest first."},"eta":{"anyOf":[{"$ref":"#/components/schemas/ProvisioningEta"},{"type":"null"}]}},"additionalProperties":true,"type":"object","required":["account_id","state","server"],"title":"ProvisioningTimeline"},"PublicProvider":{"properties":{"id":{"type":"string","title":"Id"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"price_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Price Cents","description":"Monthly subscription price in cents."},"verification_tier":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Verification Tier","description":"How much of this record we can independently vouch for."},"track_record_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Track Record Days","description":"Length of the verified record. Short records are short."},"max_drawdown_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Max Drawdown Pct","description":"Signed drawdown as stored; discovery filters on its magnitude."},"net_return_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Net Return Pct","description":"Return AFTER broker commission and swap."},"gross_return_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Gross Return Pct","description":"Price-based return, excluding broker-specific commission and swap. **This is the figure cards display, the leaderboard ranks on, and `min_gross_return_pct` filters on** -- deliberately the same number in all three places."},"sharpe":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sharpe"},"verified_capital":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Verified Capital"},"score_cached":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Score Cached","description":"Cached ranking score -- the leaderboard's sort key."},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"Only `active` and `paused` listings are publicly discoverable; `draft` and `revoked` are not."},"max_copiers":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Copiers","description":"Copier cap, or null for uncapped."},"excluded_brokers":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Excluded Brokers","description":"Brokers this provider refuses to be copied on."},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"string"},{"type":"null"}],"title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"string"},{"type":"null"}],"title":"Updated At"}},"additionalProperties":true,"type":"object","required":["id"],"title":"PublicProvider","description":"The public projection of a provider listing.\n\nDeliberately drops the backing `account_id` (another developer's internal\nid) and `stripe_account_id` (a payout destination). Served with\n``response_model_exclude_unset=True`` because the projection copies only\nthe keys the row actually has."},"RecordConsentSelf":{"properties":{"region":{"type":"string","maxLength":16,"minLength":1,"title":"Region"},"categories":{"$ref":"#/components/schemas/ConsentCategories"},"policy_version":{"type":"string","maxLength":40,"minLength":1,"title":"Policy Version"}},"type":"object","required":["region","categories","policy_version"],"title":"RecordConsentSelf","description":"Like RecordConsent but without subject_id -- the authenticated\n/consent/me route always derives the subject from the caller's own\nPrincipal, mirroring app.studio.routes' owner-never-from-body rule."},"ReferenceAssetClasses":{"properties":{"asset_classes":{"items":{"type":"string"},"type":"array","title":"Asset Classes","description":"Sorted union of asset classes across the whole catalog."}},"additionalProperties":true,"type":"object","required":["asset_classes"],"title":"ReferenceAssetClasses"},"ReferenceBrokers":{"properties":{"brokers":{"items":{"$ref":"#/components/schemas/OpenObject"},"type":"array","title":"Brokers","description":"Broker groups with their regulators, datacenters, asset classes and login servers."},"count":{"type":"integer","title":"Count"}},"additionalProperties":true,"type":"object","required":["brokers","count"],"title":"ReferenceBrokers"},"RegisterDeveloper":{"properties":{"email":{"type":"string","title":"Email"}},"type":"object","required":["email"],"title":"RegisterDeveloper"},"RegisterDeveloperResponse":{"properties":{"developer":{"$ref":"#/components/schemas/DeveloperSummary"},"api_key":{"type":"string","title":"Api Key","description":"The plaintext key. Shown ONCE, here. It is stored hashed and cannot be retrieved again -- rotate if you lose it."}},"additionalProperties":true,"type":"object","required":["developer","api_key"],"title":"RegisterDeveloperResponse","description":"The one and only time an API key's secret is returned."},"RegisterMethodBody":{"properties":{"provider_id":{"type":"string","title":"Provider Id"},"rail":{"type":"string","title":"Rail"},"display_label":{"type":"string","maxLength":120,"minLength":1,"title":"Display Label"},"destination":{"additionalProperties":true,"type":"object","title":"Destination"},"currency":{"type":"string","maxLength":8,"minLength":3,"title":"Currency","default":"USD"}},"type":"object","required":["provider_id","rail","display_label","destination"],"title":"RegisterMethodBody"},"RegisterSubscription":{"properties":{"endpoint":{"type":"string","title":"Endpoint"},"keys":{"$ref":"#/components/schemas/SubscriptionKeys"},"platform":{"type":"string","title":"Platform","default":"unknown"},"standalone":{"type":"boolean","title":"Standalone","default":false},"declarative_capable":{"type":"boolean","title":"Declarative Capable","default":false},"device_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Device Label"},"ua":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ua"}},"type":"object","required":["endpoint","keys"],"title":"RegisterSubscription"},"RekeySubscription":{"properties":{"endpoint":{"type":"string","title":"Endpoint"},"keys":{"$ref":"#/components/schemas/SubscriptionKeys"},"standalone":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Standalone"}},"type":"object","required":["endpoint","keys"],"title":"RekeySubscription"},"RelayEndpoint":{"properties":{"host":{"type":"string","title":"Host","description":"Host to dial (the ADVERTISED host)."},"port":{"type":"integer","title":"Port"}},"additionalProperties":true,"type":"object","required":["host","port"],"title":"RelayEndpoint"},"RelayKeyRow":{"properties":{"id":{"type":"string","title":"Id"},"account_id":{"type":"string","title":"Account Id"},"role":{"type":"string","title":"Role"},"provider_id":{"type":"string","title":"Provider Id"},"key_prefix":{"type":"string","title":"Key Prefix"},"created_at":{"type":"string","title":"Created At"},"revoked_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Revoked At"},"last_seen_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Seen At","description":"Last time this key actually connected. This is how you tell a key in use from one you should revoke. Null means never seen."}},"additionalProperties":true,"type":"object","required":["id","account_id","role","provider_id","key_prefix","created_at"],"title":"RelayKeyRow","description":"Relay credential metadata. Mirrors ``app.copyengine.relay_keys._row``.\n\nThe secret is never stored and can never be returned -- only its prefix."},"ReplayCapture":{"properties":{"status":{"type":"string","title":"Status","description":"`unattempted` (we never tried to capture this deal) is a DIFFERENT statement from an attempted capture that found nothing. Both produce an empty replay, and this field says which happened."},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"},"notice":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notice","description":"Plain-language wording for `status`, shipped with the payload."},"tickCount":{"type":"integer","title":"Tickcount","default":0}},"additionalProperties":true,"type":"object","required":["status"],"title":"ReplayCapture"},"ReplyBody":{"properties":{"body":{"type":"string","title":"Body"},"attachments":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Attachments","default":[]}},"type":"object","required":["body"],"title":"ReplyBody"},"ReprovisionResponse":{"properties":{"account_id":{"type":"string","title":"Account Id"},"container_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Container Id"},"note":{"type":"string","title":"Note"}},"additionalProperties":true,"type":"object","required":["account_id","note"],"title":"ReprovisionResponse"},"RequestPayoutBody":{"properties":{"provider_id":{"type":"string","title":"Provider Id"},"amount_cents":{"type":"integer","exclusiveMinimum":0.0,"title":"Amount Cents"},"idempotency_key":{"type":"string","maxLength":200,"minLength":8,"title":"Idempotency Key"}},"type":"object","required":["provider_id","amount_cents","idempotency_key"],"title":"RequestPayoutBody"},"ResumeResponse":{"properties":{"account_id":{"type":"string","title":"Account Id"},"outcome":{"type":"string","title":"Outcome"},"note":{"type":"string","title":"Note"}},"additionalProperties":true,"type":"object","required":["account_id","outcome","note"],"title":"ResumeResponse","description":"Result of restarting a suspended terminal.\n\n`outcome` is load-bearing and must be shown, not swallowed:\n\n* `resumed` -- the original container started back up. Its terminal state\n  and any trade history it had not yet reported survived, and it comes\n  back in seconds.\n* `reprovisioned` -- the container no longer existed, so a new terminal\n  was built from scratch. That takes minutes, and anything the old\n  terminal had not already reported to us is gone with it.\n* `not_suspended` -- it was already running; nothing changed."},"RotatedKey":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"key_prefix":{"type":"string","title":"Key Prefix"}},"additionalProperties":true,"type":"object","required":["id","name","key_prefix"],"title":"RotatedKey","description":"Key metadata echoed by rotation.\n\nCarries no `scopes`: rotation preserves the old key's scopes but the\nresponse does not restate them. Documented as it is, not as it \"should\" be."},"RotatedKeyResponse":{"properties":{"api_key":{"type":"string","title":"Api Key","description":"The new plaintext secret. Shown ONCE."},"key":{"$ref":"#/components/schemas/RotatedKey"},"revoked_key_id":{"type":"string","title":"Revoked Key Id","description":"The key that was revoked as part of this rotation."}},"additionalProperties":true,"type":"object","required":["api_key","key","revoked_key_id"],"title":"RotatedKeyResponse"},"RoundTrip":{"properties":{"side":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Side"},"volume":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Volume"},"profit":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Profit"},"open":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Open"},"unpairedLegs":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Unpairedlegs","description":"Legs that could not be paired into this round trip. Non-zero means the derivation is incomplete and is surfaced rather than hidden."}},"additionalProperties":true,"type":"object","title":"RoundTrip","description":"One round trip, derived on read from the sealed legs.\n\nNever stored: a persisted round trip would be a mutable copy of an\nimmutable fact."},"RoundTripList":{"properties":{"trades":{"items":{"$ref":"#/components/schemas/RoundTrip"},"type":"array","title":"Trades"}},"additionalProperties":true,"type":"object","required":["trades"],"title":"RoundTripList"},"SandboxAccountInfo":{"properties":{"available":{"type":"boolean","title":"Available","description":"False (with a 200, not an error) when the demo account is not connected yet."},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"},"account":{"anyOf":[{"$ref":"#/components/schemas/SandboxAccountPublic"},{"type":"null"}]}},"additionalProperties":true,"type":"object","required":["available"],"title":"SandboxAccountInfo"},"SandboxAccountPublic":{"properties":{"id":{"type":"string","title":"Id"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"kind":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kind"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"platform":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Platform"},"server":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Server"}},"additionalProperties":true,"type":"object","required":["id"],"title":"SandboxAccountPublic","description":"The deliberately narrow public projection of the demo account.\n\nLogin, credentials, container id and owning developer are never included."},"SandboxDeals":{"properties":{"account_id":{"type":"string","title":"Account Id"},"deals":{"items":{"$ref":"#/components/schemas/OpenObject"},"type":"array","title":"Deals"},"total":{"type":"integer","title":"Total","description":"Total rows before `limit` was applied."}},"additionalProperties":true,"type":"object","required":["account_id","deals","total"],"title":"SandboxDeals"},"SandboxEquity":{"properties":{"account_id":{"type":"string","title":"Account Id"},"points":{"items":{"$ref":"#/components/schemas/OpenObject"},"type":"array","title":"Points"},"total":{"type":"integer","title":"Total","description":"Total rows before `limit` was applied."}},"additionalProperties":true,"type":"object","required":["account_id","points","total"],"title":"SandboxEquity"},"SandboxHistory":{"properties":{"symbol":{"type":"string","title":"Symbol"},"candles":{"items":{"$ref":"#/components/schemas/OpenObject"},"type":"array","title":"Candles","description":"Most recent first, capped at 500 by `limit`."}},"additionalProperties":true,"type":"object","required":["symbol","candles"],"title":"SandboxHistory"},"SandboxPositions":{"properties":{"account_id":{"type":"string","title":"Account Id"},"positions":{"items":{"$ref":"#/components/schemas/OpenObject"},"type":"array","title":"Positions"},"balance":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Balance"},"equity":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Equity"},"open_positions":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Open Positions"},"drawdown_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Drawdown Pct"}},"additionalProperties":true,"type":"object","required":["account_id"],"title":"SandboxPositions","description":"Served with ``response_model_exclude_unset=True`` -- the equity summary\nfields are absent, not zero, when no snapshot exists."},"SandboxQuote":{"properties":{"symbol":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Symbol"},"bid":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Bid"},"ask":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ask"},"time":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"number"},{"type":"null"}],"title":"Time","description":"Broker timestamp for the tick, as the terminal sent it."}},"additionalProperties":true,"type":"object","title":"SandboxQuote","description":"One live sandbox tick, projected from the real demo account's feed.\n\nEvery field is optional and served with ``response_model_exclude_unset``:\nthe quote object is stored verbatim as the feed produced it, so a key the\nfeed did not set is absent rather than reported as null or zero."},"SandboxSeal":{"properties":{"account_id":{"type":"string","title":"Account Id"},"records":{"items":{"$ref":"#/components/schemas/OpenObject"},"type":"array","title":"Records"},"latest":{"anyOf":[{"$ref":"#/components/schemas/OpenObject"},{"type":"null"}]},"chain_intact":{"type":"boolean","title":"Chain Intact"},"verify_url":{"type":"string","title":"Verify Url","description":"Public, login-free URL that re-verifies the latest digest."}},"additionalProperties":true,"type":"object","required":["account_id","records","chain_intact","verify_url"],"title":"SandboxSeal"},"SandboxSymbols":{"properties":{"symbols":{"items":{"type":"string"},"type":"array","title":"Symbols","description":"The curated sandbox symbol set."}},"additionalProperties":true,"type":"object","required":["symbols"],"title":"SandboxSymbols"},"SealResponse":{"properties":{"account_id":{"type":"string","title":"Account Id"},"records":{"items":{"$ref":"#/components/schemas/OpenObject"},"type":"array","title":"Records","description":"The account's seal hash chain, oldest first."},"latest":{"anyOf":[{"$ref":"#/components/schemas/OpenObject"},{"type":"null"}],"description":"Last chain entry, or null."},"chain_intact":{"type":"boolean","title":"Chain Intact","description":"Recomputed verdict. False means the chain no longer verifies -- i.e. sealed history was altered."}},"additionalProperties":true,"type":"object","required":["account_id","chain_intact"],"title":"SealResponse"},"SetProviderControls":{"properties":{"max_copiers":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Copiers"},"excluded_brokers":{"items":{"type":"string"},"type":"array","title":"Excluded Brokers","default":[]}},"type":"object","title":"SetProviderControls"},"SetProviderStatus":{"properties":{"status":{"type":"string","title":"Status"}},"type":"object","required":["status"],"title":"SetProviderStatus"},"SetProviderStripeAccount":{"properties":{"stripe_account_id":{"type":"string","title":"Stripe Account Id"}},"type":"object","required":["stripe_account_id"],"title":"SetProviderStripeAccount"},"SetQuietHours":{"properties":{"start_minute":{"type":"integer","title":"Start Minute"},"end_minute":{"type":"integer","title":"End Minute"},"timezone":{"type":"string","title":"Timezone","default":"UTC"}},"type":"object","required":["start_minute","end_minute"],"title":"SetQuietHours"},"SetStatusBody":{"properties":{"status":{"type":"string","title":"Status"},"resolved_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolved By"}},"type":"object","required":["status"],"title":"SetStatusBody"},"ShareBody":{"properties":{"scope":{"type":"string","title":"Scope","default":"journal"},"deal_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Deal Id"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"}},"type":"object","title":"ShareBody"},"SignalHook":{"properties":{"webhook_url":{"type":"string","title":"Webhook Url","description":"Paste into TradingView's alert dialog. **Contains the secret and is shown once.** It is safe to carry in a URL only because the key is minted with the `signals:submit` scope and nothing else -- it cannot touch accounts, keys or pods."},"key":{"$ref":"#/components/schemas/IssuedKey"},"revoke_with":{"type":"string","title":"Revoke With","description":"The exact call that revokes it."}},"additionalProperties":true,"type":"object","required":["webhook_url","key","revoke_with"],"title":"SignalHook","description":"A TradingView-shaped webhook URL that carries its credential in the path."},"StateTransition":{"properties":{"from_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From State","description":"Null for the first transition (account creation)."},"to_state":{"type":"string","title":"To State"},"at":{"type":"string","title":"At"}},"additionalProperties":true,"type":"object","required":["to_state","at"],"title":"StateTransition"},"StepUpChallengeBody":{"properties":{"action":{"type":"string","title":"Action"}},"type":"object","required":["action"],"title":"StepUpChallengeBody"},"StepUpVerifyBody":{"properties":{"challenge_id":{"type":"string","title":"Challenge Id"},"code":{"type":"string","maxLength":32,"minLength":1,"title":"Code"}},"type":"object","required":["challenge_id","code"],"title":"StepUpVerifyBody"},"SubmitBacktest":{"properties":{"symbol":{"type":"string","title":"Symbol"},"timeframe":{"type":"string","title":"Timeframe"},"from_date":{"type":"string","title":"From Date"},"to_date":{"type":"string","title":"To Date"},"params":{"additionalProperties":true,"type":"object","title":"Params","default":{}}},"type":"object","required":["symbol","timeframe","from_date","to_date"],"title":"SubmitBacktest"},"SubmitSignal":{"properties":{"symbol":{"type":"string","title":"Symbol"},"side":{"type":"string","title":"Side"},"entry":{"type":"number","title":"Entry"},"stop":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Stop"},"target":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Target"},"declared_window_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Declared Window Ms"}},"type":"object","required":["symbol","side","entry"],"title":"SubmitSignal"},"SubmittedSignal":{"properties":{"signal_id":{"type":"string","title":"Signal Id"},"sealed_msc":{"type":"integer","title":"Sealed Msc","description":"OUR receipt timestamp, in epoch milliseconds, taken BEFORE any market data is looked at. This is what makes the record a pre-commitment rather than a claim made after the fact."},"digest":{"type":"string","title":"Digest","description":"Seal digest over the submitted payload."},"fill_state":{"type":"string","title":"Fill State","description":"`filled` once a real reference tick was found and sealed; `pending` otherwise. **Pending is an honest state, not a failure** -- the pre-commitment stands and the fill happens at the next real tick."},"filled":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Filled","description":"Observed fill price. Present only when `filled`."},"flags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Flags","description":"Qualifiers on the observed fill. Present only when `filled`."},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Why the snap is pending. Present only when `pending`."}},"additionalProperties":true,"type":"object","required":["signal_id","sealed_msc","digest","fill_state"],"title":"SubmittedSignal","description":"Receipt for a sealed signal submission.\n\nThe three steps are validate, seal, snap. Validation is the LAST point at\nwhich anything can be refused; past it the payload is sealed as submitted\nand can never be edited.\n\nServed with ``response_model_exclude_unset=True`` because the snap half is\nbranch-dependent: a filled snap carries `filled`/`flags`, a pending one\ncarries `reason`."},"SubscriptionCreated":{"properties":{"subscription":{"$ref":"#/components/schemas/OpenObject"},"checkout":{"$ref":"#/components/schemas/Checkout"}},"additionalProperties":true,"type":"object","required":["subscription","checkout"],"title":"SubscriptionCreated","description":"A subscription plus the checkout the follower must complete.\n\n**The subscription is PENDING until checkout completes.** Nothing is copied\nuntil Stripe confirms payment."},"SubscriptionKeys":{"properties":{"p256dh":{"type":"string","title":"P256Dh"},"auth":{"type":"string","title":"Auth"}},"type":"object","required":["p256dh","auth"],"title":"SubscriptionKeys"},"SupportReplyBody":{"properties":{"question":{"type":"string","minLength":1,"title":"Question"},"account_context":{"additionalProperties":true,"type":"object","title":"Account Context"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language"}},"type":"object","required":["question"],"title":"SupportReplyBody"},"SymbolInfo":{"properties":{"account_id":{"type":"string","title":"Account Id"},"symbol":{"type":"string","title":"Symbol","description":"As this broker names it."},"source":{"type":"string","title":"Source","description":"Which platform answered: `MT5` or `MT4`."},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"The broker's own label."},"path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Path","description":"Market Watch tree path."},"digits":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"null"}],"title":"Digits","description":"Price decimals. Do not assume 5 -- it is 3 on JPY crosses and varies again on indices and crypto."},"point":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Point","description":"The value of one point."},"tick_size":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Tick Size","description":"Smallest price change."},"tick_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Tick Value","description":"Account-currency value of one tick on one lot."},"contract_size":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Contract Size","description":"Units per lot."},"volume_min":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Volume Min"},"volume_max":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Volume Max"},"volume_step":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Volume Step","description":"The lot increment. Guessing this is not a rounding error: the server rejects the order and names no field."},"currency_base":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency Base","description":"What a lot is denominated in. Null on MT4, which does not expose it."},"currency_profit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency Profit","description":"What P&L accrues in. Null on MT4."},"currency_margin":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency Margin","description":"What margin is posted in. Null on MT4."},"stops_level":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"null"}],"title":"Stops Level","description":"Minimum stop distance from price, in points. A closer `sl`/`tp` is rejected, not clamped."},"freeze_level":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"null"}],"title":"Freeze Level","description":"Distance from price inside which an order can no longer be modified or closed, in points."},"trade_mode":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"null"}],"title":"Trade Mode","description":"MQL5 ENUM_SYMBOL_TRADE_MODE. Null means UNKNOWN, never 0 -- 0 is the real value `disabled`. Null on MT4."},"trade_mode_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Trade Mode Name","description":"`disabled` | `long_only` | `short_only` | `close_only` | `full`, or null when `trade_mode` is unknown."},"filling_mode":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"null"}],"title":"Filling Mode","description":"The raw MQL5 SYMBOL_FILLING_MODE bitmask. Null on MT4."},"filling_modes":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Filling Modes","description":"That bitmask decoded: any of `fok`, `ioc`, `boc`. `[\"return\"]` when the symbol advertises none of the flags, which is the same fallback MT5 itself uses."},"expiration_mode":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"null"}],"title":"Expiration Mode","description":"Raw MQL5 SYMBOL_EXPIRATION_MODE bitmask. Null on MT4."},"server_gmt_offset":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"null"}],"title":"Server Gmt Offset","description":"Broker-server-clock to UTC offset in seconds, so session times stay convertible."},"sessions_supported":{"type":"boolean","title":"Sessions Supported","description":"Whether this PLATFORM has a session calendar at all. `true` on MT5. **`false` on MT4**, which has no `SymbolInfoSessionTrade` equivalent -- there `sessions`, `session_count` and `session_count_reported` are all null. That is deliberately distinct from an MT5 symbol answering with seven EMPTY days, which means the market really is shut."},"sessions":{"anyOf":[{"items":{"$ref":"#/components/schemas/SymbolSessionDay"},"type":"array"},{"type":"null"}],"title":"Sessions","description":"Seven days, Sunday first, each with its windows. **Null** -- not an empty seven-day skeleton -- when `sessions_supported` is false."},"session_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Session Count","description":"How many session frames were actually folded in. Null when the platform has no calendar."},"session_count_reported":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"null"}],"title":"Session Count Reported","description":"How many the terminal SAID it sent. Reported next to `session_count` on purpose: a caller who can see the two disagree can decline to act on a half-list. Null when there is no calendar."}},"additionalProperties":true,"type":"object","required":["account_id","symbol","source","sessions_supported"],"title":"SymbolInfo","description":"The BROKER'S OWN contract spec for one symbol, read live.\n\nMirrors the literal dict built by `app.pods.symbols.fetch_symbol_info`,\nwhich writes EVERY key on every answer -- so declaring these adds and\nremoves nothing on the wire. Only `account_id`, `symbol`, `source` and\n`sessions_supported` are required; those four are constructed here rather\nthan passed through. Everything else is whatever the terminal reported and\nis optional AND nullable, because a plausible default is the dangerous\nanswer: a caller handed `volume_step: 0.01` when the truth is unknown\nplaces an order the broker rejects with a bare retcode, while a caller\nhanded `null` can decline to size the position at all."},"SymbolResolution":{"properties":{"account_id":{"type":"string","title":"Account Id"},"source":{"type":"string","title":"Source","description":"Which platform answered: `MT5` or `MT4`."},"requested":{"type":"string","title":"Requested","description":"The symbol you asked about."},"available":{"type":"boolean","title":"Available","description":"True only on an EXACT match in the broker's live list. Nothing is ever substituted for you."},"match":{"anyOf":[{"$ref":"#/components/schemas/BrokerSymbol"},{"type":"null"}],"description":"The exact match, when there is one."},"instrument":{"anyOf":[{"$ref":"#/components/schemas/OpenObject"},{"type":"null"}],"description":"Our canonical identity for the name you asked about (`id`, `name`, `asset_class`), when the glossary knows it. Null means we have no entry for that string \u2014 not that it is unreal."},"suggestions":{"items":{"$ref":"#/components/schemas/SymbolSuggestion"},"type":"array","title":"Suggestions","description":"Candidates that DO exist at this broker, best first. Empty when nothing plausible was found."},"searched":{"type":"integer","title":"Searched","description":"How many of the broker's symbols were compared."},"broker_symbol_count":{"type":"integer","title":"Broker Symbol Count","description":"How many instruments this broker offers in total."},"truncated":{"type":"boolean","title":"Truncated","description":"True when the enumeration hit its cap, so the candidate list may be incomplete."}},"additionalProperties":true,"type":"object","required":["account_id","source","requested","available","searched","broker_symbol_count","truncated"],"title":"SymbolResolution","description":"Whether this broker offers a symbol, and what it has if it does not."},"SymbolSession":{"properties":{"index":{"type":"integer","title":"Index","description":"0-based window number within the day."},"from":{"type":"integer","title":"From","description":"Session start, seconds from server-clock midnight."},"to":{"type":"integer","title":"To","description":"Session end, seconds from server-clock midnight."},"from_hhmm":{"type":"string","title":"From Hhmm","description":"`from` as HH:MM, for a human reader."},"to_hhmm":{"type":"string","title":"To Hhmm","description":"`to` as HH:MM; may exceed 24:00."}},"additionalProperties":true,"type":"object","required":["index","from","to","from_hhmm","to_hhmm"],"title":"SymbolSession","description":"One trading window inside one day.\n\n`from`/`to` are SECONDS FROM MIDNIGHT ON THE BROKER'S SERVER CLOCK, not\nUTC -- a session is a broker-calendar fact, and shifting it into UTC would\nmove it across a day boundary for any broker more than a few hours off.\nThe parent's `server_gmt_offset` is what keeps the conversion possible and\nauditable. `to` may exceed 24:00 for a window running past midnight; it is\ndeliberately not wrapped.\n\nEvery key is written unconditionally by `app.pods.symbols`, so all are\nrequired."},"SymbolSessionDay":{"properties":{"day":{"type":"integer","title":"Day","description":"MQL5 ENUM_DAY_OF_WEEK; Sunday is 0."},"name":{"type":"string","title":"Name","description":"`sunday` .. `saturday`."},"sessions":{"items":{"$ref":"#/components/schemas/SymbolSession"},"type":"array","title":"Sessions"}},"additionalProperties":true,"type":"object","required":["day","name","sessions"],"title":"SymbolSessionDay","description":"One day of the broker's trading calendar.\n\nA day with NO windows is present with an EMPTY list, never absent: absent\nwould read as \"unknown\", empty is the real and actionable answer -- the\nmarket is shut that day. Whether the platform has a calendar AT ALL is a\nseparate fact; see `sessions_supported`."},"SymbolSuggestion":{"properties":{"symbol":{"type":"string","title":"Symbol","description":"A symbol this broker really has."},"selected":{"type":"boolean","title":"Selected","description":"Whether it is in Market Watch."},"reason":{"type":"string","title":"Reason","description":"Why it is suggested. `alias` \u2014 a known equivalent name from our instrument glossary, with a source we checked. `case` \u2014 the same name in different letter case. `decorated` \u2014 your name plus a broker decoration, e.g. `EURUSD.m`. `alias_unconfirmed` \u2014 naming commonly used for this instrument that we have NOT sourced. `similar` \u2014 a fuzzy string match, i.e. a guess."},"equivalent":{"type":"boolean","title":"Equivalent","description":"**True only when this is a confirmed equivalent** of what you asked for. False on every fuzzy, decorated or unsourced candidate. Even when true it is a fact about NAMING, not about the contract \u2014 check `GET /accounts/{account_id}/symbols/{symbol}` before you size a position."},"canonical_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Canonical Id","description":"Our canonical instrument id, e.g. `SPX500`."},"instrument_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Instrument Name","description":"Plain-English instrument, e.g. `S&P 500 index`."},"confidence":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Confidence","description":"`confirmed` (sourced) or `reported` (unsourced)."},"provenance":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provenance","description":"How we know this mapping \u2014 the source we checked. Every glossary alias carries one; an unsourced mapping is a claim we cannot defend."},"score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Score","description":"String similarity, 0..1. Only on `similar` rows."}},"additionalProperties":true,"type":"object","required":["symbol","selected","reason","equivalent"],"title":"SymbolSuggestion","description":"A candidate for a symbol this broker does not offer under that name."},"TicketContext":{"properties":{"account_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Account Id"},"ea_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ea Id"},"copy_subscription_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Copy Subscription Id"},"payment_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Payment Ref"},"payout_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Payout Id"},"error_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Ref"},"article_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Article Slug"}},"type":"object","title":"TicketContext"},"TokenCustodyDisclosure":{"properties":{"summary":{"type":"string","title":"Summary"},"points":{"items":{"type":"string"},"type":"array","title":"Points","description":"The specific consequences, in plain language."},"compare":{"type":"string","title":"Compare","description":"How a narrower alternative differs, named so a user can compare rather than take our word for it."}},"additionalProperties":true,"type":"object","required":["summary","points","compare"],"title":"TokenCustodyDisclosure","description":"What a user is agreeing to BEFORE they paste an OANDA token.\n\nServed as structured data, not prose, so a client cannot render the\nconnection flow without also rendering the disclosure."},"Trade":{"properties":{"id":{"type":"string","title":"Id","description":"Your `commandId`; the idempotency key."},"account_id":{"type":"string","title":"Account Id"},"request":{"anyOf":[{"$ref":"#/components/schemas/OpenObject"},{"type":"null"}]},"status":{"type":"string","title":"Status","description":"PENDING -> SENT -> FILLED / REJECTED, or UNKNOWN if the terminal did not answer before the timeout (a reconcile is requested)."},"result":{"anyOf":[{"$ref":"#/components/schemas/OpenObject"},{"type":"null"}],"description":"Broker result once terminal; null until then."},"created_at":{"type":"string","title":"Created At"},"updated_at":{"type":"string","title":"Updated At"}},"additionalProperties":true,"type":"object","required":["id","account_id","status","created_at","updated_at"],"title":"Trade","description":"A trade COMMAND and its outcome. Mirrors ``app.repo.trades._row``.\n\nThis is the intent/command log. For executed broker deals with realized\nP&L use ``GET /accounts/{account_id}/deals`` instead."},"TradeBody":{"properties":{"commandId":{"type":"string","title":"Commandid","description":"Caller-generated idempotency key, as a **UUID**. Re-sending the same `commandId` returns the original command and never re-dispatches. It must be a UUID because it is stored as the money command's primary key and correlated all the way to the terminal; a non-UUID is refused with 422 (it used to reach the database and fail as a 500).","examples":["3f2a7c18-9b4e-4a51-8d3c-1e6f0b5d29aa"]},"request":{"$ref":"#/components/schemas/TradeRequest"}},"type":"object","required":["commandId","request"],"title":"TradeBody"},"TradeExcursions":{"properties":{"dealId":{"type":"string","title":"Dealid"},"available":{"type":"boolean","title":"Available"},"mae":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Mae","description":"Maximum adverse excursion."},"mfe":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Mfe","description":"Maximum favourable excursion."},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Why the figures are unavailable. Absent when they are."}},"additionalProperties":true,"type":"object","required":["dealId","available"],"title":"TradeExcursions","description":"MAE / MFE for one trade, computed from its recorded ticks.\n\n**`available: false` is an honest empty state, not an error.** It does NOT\nfall back to entry/exit prices: the move between two endpoints is the\nresult, not an excursion, and presenting it as MAE/MFE would be a\nfabricated number wearing a real name."},"TradeMetrics":{"properties":{"total_commands":{"type":"integer","title":"Total Commands"},"filled":{"type":"integer","title":"Filled"},"rejected":{"type":"integer","title":"Rejected"},"unknown":{"type":"integer","title":"Unknown"},"fill_rate":{"type":"number","title":"Fill Rate"}},"additionalProperties":true,"type":"object","required":["total_commands","filled","rejected","unknown","fill_rate"],"title":"TradeMetrics"},"TradeReplay":{"properties":{"dealId":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dealid"},"symbol":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Symbol"},"recordClass":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recordclass"},"simulated":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Simulated","description":"True when the position was marked to market, not really traded."},"simulatedNotice":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Simulatednotice","description":"Plain-language notice. Null when the trade was real money."},"overlays":{"anyOf":[{"items":{"$ref":"#/components/schemas/OpenObject"},"type":"array"},{"$ref":"#/components/schemas/OpenObject"},{"type":"null"}],"title":"Overlays"},"capture":{"anyOf":[{"$ref":"#/components/schemas/ReplayCapture"},{"type":"null"}]},"available":{"type":"boolean","title":"Available","description":"False means there is nothing to replay."},"segments":{"items":{},"type":"array","title":"Segments","description":"Contiguous runs of ticks. More than one means the record has holes."},"reduced":{"type":"boolean","title":"Reduced","description":"True when the series was downsampled. Stated because a reduced series that claims to be the whole record is a quiet lie about resolution.","default":false},"reductionNotice":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reductionnotice","description":"How many of how many ticks are shown. **No prices are ever averaged or invented** -- points are evenly selected."},"pointsShown":{"type":"integer","title":"Pointsshown","default":0},"pointsRecorded":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Pointsrecorded"},"hasGaps":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Hasgaps","description":"True when the record has holes, so a client cannot present a broken series as continuous."}},"additionalProperties":true,"type":"object","required":["available"],"title":"TradeReplay","description":"Tick-by-tick replay of one trade.\n\nServed with ``response_model_exclude_unset=True``: the no-ticks branch\ngenuinely omits `pointsRecorded` and `hasGaps` rather than reporting zero\nand false, and inventing them would misdescribe an empty record.\n\nEvery honesty label travels ON the payload rather than being left to the\nrenderer, because a claim about provenance must move with the data it\nqualifies."},"TradeRequest":{"properties":{"action":{"type":"string","title":"Action","description":"`BUY` or `SELL`."},"symbol":{"type":"string","title":"Symbol","description":"Broker symbol name, e.g. `EURUSD`."},"volume":{"type":"number","exclusiveMinimum":0.0,"title":"Volume","description":"Lots. Must be greater than zero."},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type","description":"`MARKET` (the default), `LIMIT` or `STOP`.\n\n`LIMIT` and `STOP` place a **pending order**: it rests with the broker and becomes a position only when price reaches `price`, which is then required. A `BUY LIMIT` rests below the market and a `BUY STOP` above it (mirrored for `SELL`); the wrong side is rejected before the order is sent, naming the side rather than returning a bare broker code.\n\n**Say which you mean rather than relying on `price`.** On a MetaTrader account a `price` on a market order is a fill band, not a resting price -- so the same body without `type` is an immediate order there and, historically, a resting one on cTrader. This field means the same thing on every platform.\n\n**MT5 and MT4 only.** cTrader and OANDA accounts return `501` for `LIMIT` and `STOP` -- see that code below.\n\nA pending order that is accepted comes back `FILLED`, which here means **placed, not filled** -- `result.pending` is `true` and `result.order` is the order's ticket. Track it with `GET /accounts/{account_id}/orders` and remove it with `POST /accounts/{account_id}/orders/{ticket}/cancel`."},"price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Price","description":"Requested price. **Currently ignored on MT5 execution** -- the terminal-side expert advisor does not read this field, so sending it does NOT place a protective order. Do not rely on it. Orders fill at the live tick.\n\n**Required for a `LIMIT` or `STOP` order**, where it is the price the order rests at rather than a fill band. It never defaults to the market for a pending order: an order resting at the market fills instantly, which is the opposite of what was asked."},"sl":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sl","description":"Stop loss, attached AT ENTRY on MT5 \u2014 the broker holds it, so it survives your process dying, and there is no unprotected window between the fill and a follow-up call. Send it inside `request` alongside `action`/`symbol`/`volume`. Omit for no stop.","examples":[1.085]},"tp":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Tp","description":"Take profit, attached at entry on MT5, same as `sl`.","examples":[1.125]},"deviation":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Deviation","description":"Max slippage in points. **Currently ignored on MT5 execution** -- the terminal-side expert advisor does not read this field, so sending it does NOT place a protective order. Do not rely on it. A fixed deviation of 20 is used."},"comment":{"anyOf":[{"type":"string","maxLength":31},{"type":"null"}],"title":"Comment","description":"Free-text order comment. MT5 truncates at 31 characters."}},"type":"object","required":["action","symbol","volume"],"title":"TradeRequest"},"TranslateBody":{"properties":{"text":{"type":"string","minLength":1,"title":"Text"},"target_locale":{"type":"string","minLength":1,"title":"Target Locale"}},"type":"object","required":["text","target_locale"],"title":"TranslateBody"},"TrustReport":{"properties":{"accountId":{"type":"string","title":"Accountid"},"metrics":{"$ref":"#/components/schemas/OpenObject","description":"totalReturn, maxDrawdown, sharpe, sortino, var95, expectedDrawdown."},"netOfCost":{"$ref":"#/components/schemas/OpenObject","description":"Gross vs net of commission/swap/charges."},"riskNormalized":{"$ref":"#/components/schemas/OpenObject"},"skinInTheGame":{"$ref":"#/components/schemas/OpenObject"},"tradePnl":{"anyOf":[{"$ref":"#/components/schemas/OpenObject"},{"items":{},"type":"array"},{"type":"number"},{"type":"null"}],"title":"Tradepnl"},"cashFlows":{"items":{"$ref":"#/components/schemas/OpenObject"},"type":"array","title":"Cashflows","description":"Labeled external capital movements -- excluded from return and drawdown."},"equityCurve":{"anyOf":[{"items":{"$ref":"#/components/schemas/OpenObject"},"type":"array"},{"type":"null"}],"title":"Equitycurve","description":"Raw balance/equity line."},"growthSeries":{"anyOf":[{"items":{"$ref":"#/components/schemas/OpenObject"},"type":"array"},{"type":"null"}],"title":"Growthseries","description":"Cash-flow-neutral (time-weighted) curve."},"safety":{"anyOf":[{"$ref":"#/components/schemas/OpenObject"},{"items":{},"type":"array"},{"type":"null"}],"title":"Safety"},"seal":{"$ref":"#/components/schemas/OpenObject","description":"The tamper-evident seal over the record above."}},"additionalProperties":true,"type":"object","required":["accountId","metrics","netOfCost","riskNormalized","skinInTheGame","seal"],"title":"TrustReport","description":"The account's verified record plus its tamper-evident seal.\n\nKeys are camelCase here (unlike the rest of the API) because this document\nis sealed byte-for-byte -- renaming a key would invalidate every existing\nseal. Sub-objects are left open rather than pinned: they are analytics\nrollups that gain fields, and the seal covers their content."},"UpdateAccount":{"properties":{"tick_stream":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Tick Stream","description":"Stream live quotes for this account on `GET /accounts/{id}/stream` as `tick` frames. **Off by default.** Four things are true and worth knowing before you turn it on:\n\n* **It materially increases the terminal's CPU use.** A hosted terminal's CPU is tick-driven; we have measured the same pod at roughly 13% idle and 28% while quotes are flowing. Leave it off on accounts nobody is watching.\n* **Only the terminal's chart symbol streams.** One symbol per account, chosen by the terminal, not by you.\n* **This is not a multi-symbol market-data feed.** There is no way to subscribe to a symbol on this stream; if you need a quote for a symbol the account is not charted on, this will not give you one.\n* **MT5 only.** MT4's bridge expert has no quote producer, so the flag is refused on MT4 and cTrader accounts rather than accepted and ignored.\n\nChanging it takes effect when the terminal is next created, because the terminal receives it as container environment at start-up. Call `POST /accounts/{id}/reprovision` to apply it to a running terminal -- knowing that recreating a terminal is a data-integrity event."},"keep_alive_days":{"anyOf":[{"type":"integer","maximum":1825.0,"minimum":0.0},{"type":"null"}],"title":"Keep Alive Days","description":"**Pin this account's terminal so we do not stop it for being idle.**\n\nBy default we suspend any terminal we are not being paid to keep running once it has been quiet for 15 minutes, and start it again when you ask (`POST /accounts/{account_id}/resume`). A **demo** account is never billable, so before this flag existed there was no way to keep a demo terminal up: fifteen minutes after you stopped working, it stopped. Pinning is the fix while you are building an integration.\n\nSend the number of days, 1 to 1825. **7 is the suggested value** -- about the length of an integration. Send `0` to unpin immediately. The deadline comes back as `keep_alive_until`, and re-sending extends it from now; it does not add to what is left.\n\n**A pin always expires.** Pinning does not make a terminal free. A running terminal costs a measured $10.02 a month of real infrastructure whether or not anyone is connected to it, and pinning means that keeps being spent for as long as the pin lasts. That is why a pin always expires, why 1825 days is the maximum, and why pinning does NOT exempt you from your plan's monthly connection-hours cap.\n\n**What it does not do.** It does not exempt you from your plan's monthly connection-hours cap -- if you run out of hours we still stop the terminal, and we clear the pin when we do. It does not restart a terminal that is already suspended; call `POST /accounts/{account_id}/resume` for that. It changes nothing about what you are billed: a pinned demo terminal costs you exactly what an unpinned one does. And on a live always-on terminal it does nothing at all, because we already never stop those."}},"type":"object","title":"UpdateAccount","description":"Partial update of an account's settings.\n\nField-optional like UpdateCredentials: absent means \"leave it alone\", and\nthat has to stay distinguishable from an explicit false or an explicit 0.\n\nDeliberately does NOT carry exec_mode or always_on. Those two decide\nwhether a terminal is billable (see is_billable), and adding them here is\na billing change -- the writer would have to call sync_billing_span or we\nwould go on charging for an account the customer made free. The\ncharacterization test in tests/test_billing_hooks.py\n(test_exec_mode_and_always_on_have_no_mutation_path) exists to catch\nexactly that and is left standing on purpose: tick_stream costs US CPU,\nit never changes what the customer is charged, so it is not a meter\nevent.\n\n`keep_alive_days` is in the same category and for the same reason. It\nwrites ONE column, `accounts.keep_alive_until`, which appears in no\nbillability predicate anywhere (`is_billable` reads exec_mode, always_on\nand state; `count_billable_accounts` reads the same three). A pinned demo\nterminal is charged exactly what an unpinned demo terminal is charged --\nnothing -- so there is no span to open or close and no meter to sync. What\nit does change is OUR cost, and the API description says so in words."},"UpdateCredentials":{"properties":{"login":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Login"},"password":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Password"},"server":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Server"}},"type":"object","title":"UpdateCredentials","description":"Partial credential update. Every field optional: which fields are\nPRESENT is exactly what distinguishes a password rotation from a change of\nbroker account identity, so absent must stay distinguishable from\nunchanged."},"UpdateGuardrails":{"properties":{"sizing_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sizing Mode"},"sizing_value":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"title":"Sizing Value"},"max_drawdown_pct":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"title":"Max Drawdown Pct"}},"type":"object","title":"UpdateGuardrails"},"UsageLine":{"properties":{"name":{"type":"string","title":"Name"},"qty":{"anyOf":[{"type":"number"},{"type":"integer"}],"title":"Qty"},"unit":{"type":"number","title":"Unit","description":"Price per unit in USD."},"amount":{"type":"number","title":"Amount","description":"`qty * unit`, USD."}},"additionalProperties":true,"type":"object","required":["name","qty","unit","amount"],"title":"UsageLine"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VeraAskBody":{"properties":{"message":{"type":"string","title":"Message"},"context":{"additionalProperties":true,"type":"object","title":"Context","default":{}}},"type":"object","required":["message"],"title":"VeraAskBody"},"Waitlisted":{"properties":{"waitlisted":{"type":"boolean","title":"Waitlisted","default":true},"waitlist":{"$ref":"#/components/schemas/OpenObject","description":"The queue entry."}},"additionalProperties":true,"type":"object","required":["waitlist"],"title":"Waitlisted","description":"Returned with **202**, not 201: no subscription and no checkout were\ncreated. The provider is at its copier cap and the follower was queued."},"WebhookDeliveriesPage":{"properties":{"count":{"type":"integer","title":"Count","description":"Rows in THIS page."},"total":{"type":"integer","title":"Total","description":"Rows matching the filter, before paging."},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"deliveries":{"items":{"$ref":"#/components/schemas/WebhookDelivery"},"type":"array","title":"Deliveries"}},"additionalProperties":true,"type":"object","required":["count","total","limit","offset","deliveries"],"title":"WebhookDeliveriesPage"},"WebhookDelivery":{"properties":{"id":{"type":"string","title":"Id"},"endpoint_id":{"type":"string","title":"Endpoint Id"},"event_id":{"type":"string","title":"Event Id"},"event_type":{"type":"string","title":"Event Type"},"payload":{"anyOf":[{"$ref":"#/components/schemas/OpenObject"},{"type":"null"}],"description":"The exact envelope that was (or will be) POSTed."},"status":{"type":"string","title":"Status"},"attempts":{"type":"integer","title":"Attempts"},"response_status":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Response Status","description":"HTTP status your receiver returned, if it answered."},"response_ms":{"anyOf":[{"type":"integer"},{"type":"number"},{"type":"null"}],"title":"Response Ms"},"last_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Error"},"next_attempt_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Attempt At","description":"When the next retry is due; null if none is scheduled."},"delivered_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Delivered At"},"created_at":{"type":"string","title":"Created At"}},"additionalProperties":true,"type":"object","required":["id","endpoint_id","event_id","event_type","status","attempts","created_at"],"title":"WebhookDelivery","description":"One delivery attempt record. Mirrors ``app.webhooks.repo._delivery_row``."},"WebhookEndpoint":{"properties":{"id":{"type":"string","title":"Id"},"developer_id":{"type":"string","title":"Developer Id"},"url":{"type":"string","title":"Url"},"event_types":{"items":{"type":"string"},"type":"array","title":"Event Types","description":"Event types to deliver. An EMPTY list means ALL types."},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"active":{"type":"boolean","title":"Active"},"disabled_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disabled Reason","description":"Why we stopped delivering (e.g. repeated failures). Null while healthy."},"created_at":{"type":"string","title":"Created At"},"updated_at":{"type":"string","title":"Updated At"}},"additionalProperties":true,"type":"object","required":["id","developer_id","url","active","created_at","updated_at"],"title":"WebhookEndpoint","description":"A registered delivery endpoint. Mirrors ``app.webhooks.repo._endpoint_row``.\n\nThe signing secret is NOT here -- it is returned once, at creation, and is\nnot retrievable afterwards."},"WebhookEventTypes":{"properties":{"event_types":{"items":{"type":"string"},"type":"array","title":"Event Types","description":"Every deliverable event type. All of them are real events the platform produces."}},"additionalProperties":true,"type":"object","required":["event_types"],"title":"WebhookEventTypes"},"WebhookList":{"properties":{"webhooks":{"items":{"$ref":"#/components/schemas/WebhookEndpoint"},"type":"array","title":"Webhooks"}},"additionalProperties":true,"type":"object","required":["webhooks"],"title":"WebhookList"},"WebhookTestResponse":{"properties":{"delivered_event_id":{"type":"string","title":"Delivered Event Id"},"delivery_id":{"type":"string","title":"Delivery Id"}},"additionalProperties":true,"type":"object","required":["delivered_event_id","delivery_id"],"title":"WebhookTestResponse"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"x-api-key","description":"Per-developer API key, sent as the `x-api-key` header. There is no Bearer scheme -- an Authorization header is ignored and the call then fails 401. Keys come from POST /v1/developers, which is CURRENTLY CLOSED to self-service signup on production (403 `signups are closed`); ask support@verilo.trade for access, or build against the keyless /v1/sandbox/* surface meanwhile."}}},"security":[{"ApiKeyAuth":[]}]}