# API changelog

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

Source: https://tickwire.io/docs/changelog-api/

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](https://tickwire.io/docs/versioning/) 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](https://tickwire.io/docs/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`](https://tickwire.io/docs/errors/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](https://tickwire.io/docs/request-logs/). New error code
  [`request_log_not_found`](https://tickwire.io/docs/errors/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](https://tickwire.io/docs/health/#accounts-that-arent-running).
- **Fewer one-off `500`s 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`](https://tickwire.io/docs/errors/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](https://tickwire.io/docs/pagination/).
- **New error code:** [`invalid_cursor`](https://tickwire.io/docs/errors/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](https://tickwire.io/docs/health/#account-snapshot).
- **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](https://tickwire.io/docs/webhooks/#events).
- **New response header:** `Idempotent-Replayed: true` on every answer served from the idempotency
  store; first executions never carry it. See [Idempotency](https://tickwire.io/docs/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](https://tickwire.io/docs/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](https://tickwire.io/docs/versioning/#your-organisations-pin).
- **New error codes:** [`invalid_api_version`](https://tickwire.io/docs/errors/invalid_api_version/) and
  [`api_version_unsupported`](https://tickwire.io/docs/errors/api_version_unsupported/).
- A `429` on a trade or batch order now says `"sent": false`, `"outcome": "not_sent"`.

Deprecations: none.
