# Platform Whisperer agent access

Read-only access is open to anyone who signs in: Claude and Poke connect through sign-in, other assistants use a key made on the account page.

Station facts, unofficial platform predictions and aggregate measured accuracy. No watches, callbacks or messaging in phase one.

## Discovery and tools

- MCP (safer, header key): POST /mcp with Authorization Bearer or x-pw-key. Stateless JSON-RPC 2.0, plain JSON, no sessions.
- MCP (key in address): POST /mcp/{key}, for clients with no header field. The whole address is a secret.
- MCP tools: list_stations, get_departures, find_train, get_train, get_track_record. Each result has a compact readable text digest (board status, data age, next check, then one line per train with times, destination, lifecycle, platform state, release time and confidence, up to about 2,000 characters) and the complete REST envelope in structuredContent; isError is true when ok is false.
- [OpenAPI schema](/api/public/agent/v1/openapi.json): Complete parameters, response fields, errors and limits. The schema is the data field of the common response envelope.
- list_stations(station=EUS): Station names, enablement, platform lists and announcement wording.
- get_departures(station=EUS, window_min=90, operator?, destination?): Trains with scheduled and expected times, delay and cancelled flag, sorted by expected time, in a maximum 90-minute stored window. Operator and destination use case-insensitive partial matching. Operator short forms include Avanti and LNR. Present trains are retained even after their scheduled time. Departed trains are hidden unless include_departed=true.
- find_train(station=EUS, destination?, after?, before?, headcode?): All matching candidates with exact (headcode, full destination name or station code such as MAN), partial or unfiltered match_quality; include_departed=true adds departed trains; no silent choice. Time bounds are inclusive.
- get_train(train_id, station?): Full train facts, plain basis and expected announcement wording, including departed trains. The room id determines the station when omitted.
- get_track_record(station=EUS, days=14): Accuracy at measured minutes before release, sample sizes and early markers, with days_requested and days_available; calendar days including the current day, maximum 365. Groups under 10 results are hidden, under 30 are early. Simulated crowd is excluded. days_available and coverage_note show when less history exists than requested.

## URLs and keys

JSON GET tools live at /api/public/agent/v1/<tool>. The /api/agent/v1/<tool> aliases may be intercepted by published site authentication. Authorization is a Bearer key or x-pw-key header issued by the service owner or made by a signed-in person on their account page (read only, 90 days, at most 5 active, shown once). Keys have the format pwk_ followed by 32 random URL-safe characters and are stored as SHA-256 hashes, never returned by these tools. Scopes are read or individual tool names; watch does not grant reads. Schema discovery accepts any valid, non-revoked key. Keys expire after 90 days by default. Owner-issued rotation keeps the old key valid for at most seven days, subject to its original expiry; rotating a self-made key on the account page revokes the old key at once. Revoked keys return unauthorized immediately. Owner labels are private and stable independently of key rotation.

## Connecting Claude

Claude (web, desktop, mobile): open Settings, then Connectors, find Platform Whisperer in the connector directory and add it, then sign in with your email address (or Google) and press Allow. No key or client ID is needed. Fallback: add a custom connector with the address https://platformwhisperer.com/mcp and leave the client ID blank. Claude Code: claude mcp add --transport http platform-whisperer https://platformwhisperer.com/mcp (not yet tested by us). After running it, start Claude Code, run /mcp and follow the sign-in prompt.

## Response contract

Every JSON response contains ok, station, as_of, data, notice and attribution. Board tools (get_departures, find_train, get_train) also contain board_status. Board tools also contain data_age_s (snapshot age in seconds) and, when board_status is not live, status_reason. Errors also contain error.code, message, what_to_do, retryable and retry_after_s. Unavailable timestamps are null. Every timestamp is Europe/London local time with its offset, for example 2026-10-26T14:53:00+00:00 in winter and +01:00 in summer.

board_status is live for snapshots up to 150 seconds old, stale for snapshots up to 360 seconds old, otherwise unavailable. list_stations, get_track_record and schema discovery omit board_status. Stale facts retain their original as_of, never a current timestamp. Unknown stations return unknown_station with the given station echoed (null when not plain text) and no board_status. Unusable boards return board_unavailable, not an empty success list. Healthy empty boards have state=no_departures; unmatched filters have state=no_matches.

Absent fields mean unknown: null and empty values are omitted from trains. Train fields: train_id, headcode, scheduled_departure, expected_departure, actual_departure, delay_min, cancelled, destination {crs, name}, operator {code, name}, platform_status, platform, confidence_pct, lifecycle, optional status_reason and basis. next_check_s is envelope level only; repeated train summaries are omitted. as_of is top level only. destination.crs is null when the station reference does not have the name or the destination names several stations (basis then says it divides en route). lifecycle is scheduled (no prediction yet), predicted, released, cancelled, departed (only from a recorded actual departure, never the clock) due_now (at or after a known expected departure with no recorded departure or cancellation: a currently released platform stays, otherwise no predicted platform and no confidence are shown from the first second; status_reason says expected time reached, no departure recorded; estimate may be rolling forward), or overdue (expected departure more than 2 minutes past with no released platform and no recorded departure; no prediction is shown), or platform_withdrawn (a platform was announced and has since been withdrawn from the board before the expected time; the number is never shown, release_announced_at gives the earlier time, and after the expected time it becomes due_now). When a platform was announced earlier and is no longer shown, platform_status is none, no number is given and release_announced_at carries the earlier announcement time. delay_min and expected_departure carry delay facts without a repeated train summary. The MCP text digest shows one terse line per train with train_id, headcode, scheduled and expected times, destination, lifecycle, platform state, release time, confidence and typical accuracy, covering the whole requested board up to 40 trains in about 4,500 characters and ending with a hint to narrow with window_min, operator or destination for anything omitted; it never shows a suppressed platform number. Uncancelled trains without recorded departure leave get_departures only when more than 15 minutes past a known expected departure; get_train and find_train still return them. due_now keeps a currently released platform and never shows a predicted one. Cancelled trains more than 15 minutes past their scheduled time leave get_departures; get_train still returns them.

Confidence: confidence_pct is the stored display value (the engine's calibrated estimate, limited by a support cap that is 75% until higher confidence bands have at least 30 measured results each, plus smaller caps for late inbound, history-only and disrupted cases) and, when the train's time-to-release group has 30 or more results, the lower of that and the group's measured accuracy. It can therefore be lower than its group's typical accuracy, and above 75% when the cap has been raised by measured results. confidence_capped is true only when the display cap actually lowered that train's figure; the explanation is given once as the envelope confidence_cap_note. The envelope typical_accuracy block is keyed by time-to-release group id and contains typical_accuracy_pct, minutes_before_release and sample_size per group; typical_accuracy_basis is stated once beside it. Predicted trains keep only typical_accuracy_group when a group qualifies; otherwise it is omitted. The stage is estimated from expected departure minus the station lead (Euston 10 minutes, others 0), using the nearest measured group, ties to the larger horizon. At Euston 12 to 14 minutes before expected departure estimates 2 to 4 minutes before release and uses the 5 minute group. Only one platform is scored with a calibrated probability, so no ranked alternatives are returned.

List tools add applied (station, window_min, operator {query, code, name}, destination, sort=expected_departure, include_departed), warnings (for example no operator matched 'Avanti') . find_train candidates add match_reasons. next_check_s is one envelope-level hint on every board tool, independent of train times: seconds until as_of plus 60 seconds, rounded up and clamped to a minimum of 5 and maximum of 60. When as_of plus 60 seconds has already passed (the refresh is late) or data_age_s is over 150, use 15. In practice 5 means a refresh is due within seconds, and 15 means the refresh is already overdue or the snapshot is stale, so clients do not poll a late board faster. It tracks the roughly minute-long refresh cadence, not a fixed second of the clock; watches do not yet exist. board_status appears only in the envelope. platform_status is released, predicted or none. Only released platforms are facts from station screens. Predictions use the display value described under Confidence. confidence_pct is null except for predicted; platform is null for none, otherwise belongs to the station platform list. basis contains generated reasons only, never passenger-written text. No prediction yet is a real gap. expected_announcement is wording, not a promised time. Lists sort by expected_departure, falling back to scheduled. Cancelled trains have cancelled=true, platform_status none, platform and confidence_pct null. get_train also accepts headcode plus time (HH:MM, today, scheduled or expected) instead of train_id; several matches return state ambiguous with candidates; a headcode alone returns train_needs_time. Missing trains are train_not_on_board (not retryable, retry_after_s null): no longer on the board, departed or removed; use find_train or get_departures for the next one. They are never presumed departed from the clock.

Test snapshots include sample_data=true and sample_notice; they exist only on recognized preview addresses, including the local preview, and never contribute to accuracy.

Notice: Unofficial predictions. Check station screens.
Attribution: Contains live train data. Predictions are unofficial and may be wrong. Check station screens.

## Errors

- agents_not_enabled (503): Agent access is not open yet. Access becomes available after the service owner opens it.
- unauthorized (401): A valid key with the required scope is required. Make a new key on the account page at https://platformwhisperer.com/sign-in, or check the key was copied in full.
- key_expired (401): This key has expired. Make a new key on the account page at https://platformwhisperer.com/sign-in, then update it in your assistant.
- rate_limited (429): The key has reached its request limit. The request can be retried after retry_after_s seconds.
- station_disabled (403): This station is not enabled. Enabled stations are listed by list_stations.
- unknown_station (404): This station code is unknown. Station codes are listed by list_stations.
- train_not_found (404): This train identifier is not valid. Train identifiers are returned by find_train and get_departures.
- train_not_on_board (404): This train is no longer on the board (departed or removed). Use find_train or get_departures for the next train.
- board_unavailable (503): The current board is unavailable. The request can be retried shortly; station screens remain authoritative.
- train_needs_time (400): A headcode alone does not identify one train. Use the train_id from get_departures or find_train, or add time as HH:MM (scheduled, today), for example headcode 1H16 and time 11:40.
- bad_request (400): The request parameters or tool are not valid. Accepted tools and parameters are described in openapi.json.

## Limits and availability

MCP initialize, initialized, ping and tools/list use a separate 600-per-key fixed-minute bucket and safe log names mcp_initialize, mcp_initialized, mcp_ping and mcp_tools_list. They do not consume tool slots. Per-key tool limits default to 60 calls per fixed minute; keys can have owner-selected limits from 1 to 1000. Recognized-key admitted calls consume a slot, including parameter errors and scope failures. Rate-limited rejections do not extend the reset. Headers are X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset (approximate Unix seconds; application and database clocks can differ), and Retry-After for retryable errors. Parallel requests are serialized per key. Read failure or the global switch closes access. When closed, every JSON tool and schema returns agents_not_enabled, before key validation. Keys, personal data, request text and passenger reports never appear in responses.

## Links

- Connect guide: https://platformwhisperer.com/docs/connect
- Support: https://platformwhisperer.com/support
- Privacy: https://platformwhisperer.com/privacy
- Terms: https://platformwhisperer.com/terms
