AMQP quick start

Keep a clock within 0.5 seconds of UTC. Support supplies the broker address, pinned certificate authority, broker user and password, API key with AMQP access, and queue set number. Allowlist the client address for both the broker and HTTPS API. Connect using AMQPS on port 5671, trust only the supplied authority, verify the broker address, and use a 10 second AMQP connection heartbeat. Never disable TLS verification.

- Queues are already created: `c_<client>.prices.<n>` and `c_<client>.results.<n>`. Open a separate channel for results. Do not declare or purge queues.
- Bind the price queue to `feed.prices`; also bind it to `feed.events` if event objects are wanted. Supported keys are `#`, `<sport>.#`, `*.<competition_id>.*`, and `*.*.<event_id>`. The price queue already receives `feed.heartbeat`; the results queue already receives every result.
- Consume prices with automatic acknowledgement. Hold messages until joined. A standby consumer receives nothing until it takes over; all its prices remain suspended.
- Wait for a non-final heartbeat received within 3 seconds of its `sent_at`. Record its `session` and `seq`, and discard messages received before it. Keep receiving while the HTTPS request runs.
- Send `POST /v1/prices/join` with `Authorization: Bearer <API key>`, `Accept-Encoding: gzip`, and the union of binding filters: for example `{"filters":[{"sports":["football"]},{"competitions":["cp_example"]}]}`. Use `{"filters":[{}]}` for `#`. Group IDs by shape into at most 4 filters and at most 500 IDs. The response is gzip JSON with `version` and `data`. Read every page of `GET /v1/events` if wanted, keeping events that match the bindings.
- Replace the held rows with the answer. Apply buffered messages in order, then expose prices. Compare integer versions, never their apparent time. A row or event absent from the snapshot counts as having the join version. Ignore older or equal versions. An event that moves outside the bindings is dropped with its rows; remember its removal version so old messages cannot bring it back.
- Keep results by `market_id` and `version`, even outside the price bindings. Use manual acknowledgements and prefetch at most 1,000 on the results channel. Acknowledge only after durable storage; duplicate or older versions do not change stored results. Results expire after 7 days, so reconcile a longer outage through REST.

Suspend every price and rejoin whenever no heartbeat arrives for 3 seconds; the session changes, the sequence is not the previous one plus one, or a heartbeat is final; a heartbeat arrives more than 3 seconds after `sent_at`; or a connection/channel closes or the broker cancels a consumer. The first three conditions keep the consumer and return to waiting for a fresh heartbeat. A closed connection/channel, cancellation, or binding change reconnects and binds again. Remove obsolete bindings from both AMQP exchanges for prices and events first. Never purge the queue. Treat stored prices as suspended with no usable probabilities until joining completes again.

For a join response of 429 or 503, wait `Retry-After`. For a refused connection, a request longer than 30 seconds, or another 5xx, retry with jittered backoff between 1 and 30 seconds. After 60 seconds without a join answer, discard the buffer and wait for a fresh heartbeat. An account gets two join answers per 10 seconds, independently of its other API requests. The examples bound their local join buffers and rejoin on overflow.

Refusals and closes

- Login 403 `ACCESS_REFUSED`: check the broker user and password.
- Vhost 530 `NOT_ALLOWED`: contact support about disabled access. Connection/channel limit: close unused connections or channels (4 connections and 8 channels per user).
- Channel 403: correct the queue name, AMQP exchange name, or unsupported operation. Channel 404: check the assigned queue set.
- `basic.cancel`: reconnect and rejoin. No deliveries on an open connection may mean standby; keep prices suspended.
- Connection 320 `CONNECTION_FORCED`, account disabled: contact support. Too many unacknowledged messages: acknowledge results after storing them and use automatic acknowledgement for prices. Other forced closes: reconnect with backoff.

The Python and Java examples in `examples/amqp/` implement this procedure, including pinned TLS, durable result deduplication, and the suspension gate. The AMQP contract is available at `/docs/asyncapi-amqp.yaml`.
