# Health

> How Tickwire decides whether an account is working.

Source: https://tickwire.io/docs/health/

A running terminal isn't necessarily a working one. A terminal can be alive while logged out, stuck on a dialog, or
connected to a broker whose price feed has stopped. So Tickwire measures health from **work that
succeeded**, using three independent signals:

1. **Broker link:** the connection state the terminal itself reports.
2. **Account reads:** when we last read the account successfully. That shows the broker is
   answering, not just that a socket is open.
3. **Tick advance:** whether the broker's clock on a reference symbol is moving.

## The ladder

| Status | Meaning | What to do |
|---|---|---|
| `healthy` | Connected, account readable, ticks advancing | Nothing |
| `stale` | Connected and readable, but ticks haven't advanced within 30 s | Usually the broker or a closed market. Treat prices as frozen; don't page yourself |
| `disconnected` | Terminal up, no broker link | Usually the broker. We reconnect automatically and escalate if it persists |
| `down` | We can't read the terminal at all, or the account isn't running | A running account: ours to fix, and you'll get a webhook. An account that isn't running: `reason_code` says why (see [below](#accounts-that-arent-running)) |

## A health snapshot

```bash
curl -s https://api.tickwire.io/users/current/accounts/acc_7Qx/health -H "auth-token: $TW_KEY"
```

```json
{
  "status": "healthy",
  "connected": true,
  "read_only": false,
  "trading_enabled": true,
  "reasons": [],
  "last_account_read_age_s": 2.1,
  "last_tick_age_s": 0.4,
  "last_action_age_s": 812.0,
  "broker_time": "2026-09-23 14:02:11.480",
  "checked_at": "2026-09-23T11:02:13Z"
}
```

- `trading_enabled` is true only if an order could be accepted right now: connected, not an
  investor login, and trading switched on in the terminal.
- `reasons` explains any status other than `healthy` in plain words.
- The ages let you apply your own, stricter thresholds.
- A quote older than four days never counts as "ticks advancing". Some brokers serve a cached
  quote from years ago right after login.

## Accounts that aren't running

An account can exist without a terminal: its deploy failed (for example the broker asked for a
one-time password), or you undeployed it. There is nothing to measure, but the answer is still
known, so health says so instead of returning `404`:

```bash
curl -s https://api.tickwire.io/users/current/accounts/acc_7Qx/health -H "auth-token: $TW_KEY"
```

```json
{
  "status": "down",
  "connected": false,
  "read_only": null,
  "trading_enabled": false,
  "reasons": ["account is not running (state DEPLOY_FAILED)",
              "ERR_OTP_REQUIRED: the broker asks for a one-time password"],
  "last_account_read_age_s": null,
  "last_tick_age_s": null,
  "last_action_age_s": null,
  "broker_time": null,
  "checked_at": "2026-09-23T11:02:13Z",
  "fx_status": "error",
  "deployed": false,
  "state": "DEPLOY_FAILED",
  "reason_code": "ERR_OTP_REQUIRED",
  "error": "ERR_OTP_REQUIRED: the broker asks for a one-time password"
}
```

- The answer is `200`. `state` is the account's lifecycle state, and `reason_code` the code at the
  start of its error (`ERR_OTP_REQUIRED`, `E_AUTH`, …), or `null` when the error has no code or there
  is none. `error` is the same text as `health.error` on the account object.
- Anything we can't know without a terminal, such as `read_only` and the ages, is `null`.
- The terminal routes match: `GET /mt5/{id}/status` (and `/mt4/…`) is `200` with `status: "down"`
  plus `deployed`, `state`, `reasonCode` and `error`; `/healthz` and `/livez` are `503`.
- Agents: `GET /v1/agent/accounts/{id}/health` says `disconnected`, with `state` and `reason_code`
  in its `evidence`.
- An account that is still deploying answers `503` `account_deploying` with `Retry-After`: it may
  be up in a moment. An account that doesn't exist, or isn't yours, is `404` `account_not_found`.
- Other data calls on an account that isn't running are `404` `account_not_deployed`. Fix the cause
  and redeploy it.

## How it reaches you

- **Polling:** `GET …/health`, as above.
- **Streaming:** `health` and `brokerConnectionStatus` frames on the [account stream](https://tickwire.io/docs/streaming/).
- **Webhooks:** `account.health_changed` on every transition, within 60 seconds. See
  [Webhooks](https://tickwire.io/docs/webhooks/).
- **`/users/current/…` fields:** `connectionStatus` on the account is `CONNECTED` only when health is `healthy`
  or `stale` **and** the broker link is up; otherwise `DISCONNECTED_FROM_BROKER` or `DISCONNECTED`.
- **`/v1` and terminal-route fields:** `status` maps `healthy` and `stale` to `connected`, `disconnected` to
  `disconnected`, and `down` to `error`.

## Account snapshot

`GET /users/current/accounts/{id}/snapshot` (agents: `GET /v1/agent/accounts/{id}/snapshot`) returns
everything needed to rebuild your picture of an MT4 or MT5 account in one call: account information,
open positions, pending orders, health and the broker's server time.

```json
{
  "account_id": "acc_7Qx", "platform": "mt5",
  "as_of": "2026-09-23T11:02:13.412Z", "complete": true, "terminal_connected": true,
  "account_information": { "status": "ok", "read_at": "2026-09-23T11:02:13.380Z", "age_ms": 32, "data": { "balance": 10000, "equity": 10012.5 } },
  "positions": { "status": "ok", "read_at": "…", "age_ms": 25, "data": [] },
  "orders": { "status": "ok", "read_at": "…", "age_ms": 18, "data": [] },
  "health": { "status": "ok", "read_at": "…", "age_ms": 9, "data": { "status": "healthy" } },
  "server_time": { "status": "ok", "read_at": "…", "age_ms": 2,
                   "data": { "time": "2026-09-23T11:02:13.410Z", "brokerTime": "2026-09-23 14:02:13.410", "brokerUtcOffsetMinutes": 180 } }
}
```

- Each section has its own `status`: `ok` (read from a terminal connected to its broker), `stale`
  (read, but the terminal was not connected, so values may be out of date) or `unavailable` (the read
  failed; `data` is `null` and `error` says why). `complete` is `true` only when every section is `ok`.
- `read_at` and `age_ms` show how fresh each section is. Sections are read one after another, not
  atomically.
- `data` uses the same shapes as the individual routes. `brokerUtcOffsetMinutes` is explicit and is
  `null` until measured; broker time is never assumed to be UTC.
- The answer is `200` whenever anything could be read, and `503` only when no section could be.

## Missing data shows as unknown

If a measurement is missing, the answer is unknown. The status page works the same way: a
component without a recent check shows as unknown.
