View as Markdown

API changelog

Every API version, breaking change and deprecation, newest first.

Breaking changes to the native /v1 API ship only under a new dated version, and anything deprecated keeps working for at least six months. The versioning policy explains the rules. The compatible surfaces (/users/current/… and the terminal routes) are frozen to their existing shapes and are not listed here unless we fix a divergence from those shapes.

Unreleased (no version change)

  • New API key prefixes. New full keys start with tw_live_ and new read-only keys with tw_ro_. Keys issued before 2026-10-03 keep working unchanged, with the same rights. If your code checks a key’s prefix, accept both. No shape changes.

  • Idempotency-Key on calls that create a secret. POST /v1/agent-keys, POST /v1/pretrade-hooks, POST /v1/pretrade-hooks/{id}/rotate-secret, POST /users/current/tokens, POST /v1/readonly-keys, POST /v1/readonly-keys/{id}/rotate and POST /v1/sandbox/keys now honour it (a replayed rotate rotates nothing): a retry with the same key answers the first result with Idempotent-Replayed: true, the secret field set to null and a notice (key_notice, secret_notice, tokenNotice, apiKeyNotice) that it was already shown. Before, every retry created a second live credential. The key is scoped to the calling credential, and the protection is short-lived: a retry after one of our restarts or deploys may still create a second credential, so list and revoke duplicates. A call that fails after the credential exists answers 500, outcome: unknown and created_id. See Idempotency.

  • Full and sandbox keys are stored hashed. Full (tw_live_) and sandbox twsk_ keys are now stored as a SHA-256 digest with a short preview, like read-only keys, API tokens and agent keys. Their values were already shown only once (POST /dashboard/api-keys, POST /v1/sandbox/signup, POST /v1/sandbox/keys, the dashboard’s sandbox quickstart); now nothing on our side keeps them. Lists answer only key_preview (tw_live_ab12…, twsk_ab12…), and the dashboard’s reveal answers 400 not_revealable for every key. Existing keys keep working, with every header and query form, over REST and WebSocket. No shape changes.

  • Breaking (pre-launch): read-only keys are shown once. Read-only (tw_ro_) keys are now stored as a SHA-256 digest, like agent keys and API tokens. The value is in key only in the answer to POST /v1/readonly-keys and POST /v1/readonly-keys/{id}/rotate (with key_notice); every other read (GET /v1/readonly-keys, GET and PATCH /v1/readonly-keys/{id}) answers key: null and a key_preview (tw_ro_ab12…). Existing keys keep working. The dashboard can no longer reveal a read-only key; rotate it to get a new value.

  • Request logs. GET /v1/request-logs, GET /v1/request-logs/summary and GET /v1/request-logs/{id}: every authenticated call of the last 14 days, with redacted body excerpts for errors. See Request logs. New error code request_log_not_found. Additive; no version change.

  • Health of an account that isn’t running. GET /users/current/accounts/{id}/health and GET /mt4|mt5/{id}/status used to answer 404 account_not_deployed for an account whose deploy failed or that was undeployed. They now answer 200 with status: "down", deployed: false, the lifecycle state and the reason code from the account’s error (for example ERR_OTP_REQUIRED for sandbox login 1060); /healthz and /livez answer 503. Agent health (GET /v1/agent/accounts/{id}/health) says disconnected with that reason instead of unknown. Unknown accounts and other tenants’ accounts are still 404. See Health.

  • Fewer one-off 500s on listings. GET /v1/connected-apps and GET /v1/dedicated-ips (and their dashboard versions) could answer 500 internal_error about once in a hundred calls when a database connection had been dropped while idle. Connections are now checked before use, the read is retried once on a fresh connection, and if the database still can’t be reached the answer is 503 temporarily_unavailable with Retry-After. Nothing was changed by such a call; retry it.

2026-09-27: pagination, account snapshot, trade and account events

Additions and one pre-launch shape change to version 2026-09-25. No new version was cut: the API has no customers yet, so the change is made in place before launch rather than behind a new version.

  • Breaking (pre-launch): three lists changed from a bare JSON array to a page. GET /v1/agent-keys, GET /v1/pretrade-hooks and GET /v1/pretrade-holds now answer {"data": [...], "has_more": false, "next_cursor": null} instead of [...]. Read the items from data. The SDKs changed with them (list_agent_keys / listAgentKeys, list_pretrade_hooks / listPretradeHooks, list_holds / listHolds return the page).
  • Cursor pagination on native /v1 lists: limit (1 to 500, default 100) and cursor in; has_more and next_cursor out. Also on GET /v1/agent-kill-switches, GET /v1/agent-audit, GET /v1/agent/audit, GET /v1/connected-apps and GET /v1/agent/accounts, where the existing items field is unchanged and the two fields are added. The lists on the compatible routes are unchanged. See Pagination.
  • New error code: invalid_cursor.
  • Account snapshot: GET /users/current/accounts/{id}/snapshot and GET /v1/agent/accounts/{id}/snapshot: account information, positions, orders, health and server time in one read, with per-section freshness and an explicit broker UTC offset. MT4 and MT5. See Health.
  • New webhook events, each sent only on evidence from the terminal: position.opened, position.modified, position.closed, order.filled, order.cancelled, order.rejected, trade.outcome_unknown, account.connected, account.disconnected, account.login_failed, account.margin_call, account.stop_out, dedicated_ip.active, dedicated_ip.released. position.closed carries evidence: closing_deal (sent at once, with the deal) or unknown (the position was missing from two consecutive reads and no closing deal was found). See Webhooks.
  • New response header: Idempotent-Replayed: true on every answer served from the idempotency store; first executions never carry it. See Idempotency.

Deprecations: none.

2026-09-25: first version

  • Version 2026-09-25 published. This is the baseline for the native /v1 API. Send Tickwire-Version: 2026-09-25 to pin it per request; every /v1 response echoes the version that served it.
  • Rate limits published and enforced with per-endpoint weights: standard calls and trades cost 1 unit, history 5, batch orders 10, a WebSocket connect 5. Trades have their own per-account budget. See Rate limits.
  • New response headers: RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset and RateLimit-Policy (IETF draft) on every response, next to the existing X-RateLimit-*.
  • Organisation pin. Each organisation is pinned to a version (new organisations to the latest, existing ones to 2026-09-25), used by /v1 calls without Tickwire-Version. Read it with GET /v1/api-version and move it with POST /v1/api-version or in the dashboard settings; see Versioning.
  • New error codes: invalid_api_version and api_version_unsupported.
  • A 429 on a trade or batch order now says "sent": false, "outcome": "not_sent".

Deprecations: none.