Skip to main content
The stream pushes event changes, live offers, your private betslip quotes, and your order and bet updates as they happen. You normally hold one socket open for the life of your program and call REST alongside it.
For a snapshot, REST is simpler. GET /v2/events/ lists the events that have prices, and GET /v2/offers/ returns the offers on up to 20 events per call. Both return the same objects the stream sends. A REST reply can be about 4 seconds behind live prices, so use the stream when you need each change as it happens. The stream is the only place pmm quotes and order updates are pushed. Over REST, you poll the betslip and the order.

Connection

Authentication is enforced at the HTTP handshake. A refused upgrade returns a non-101 status with a short plain-text body: 400 missing_credentials or invalid_lang, 401 auth_rejected, or 503 unavailable. Many client libraries report this only as a handshake error. Verify the key over REST first. See Errors and rate limits. The server does not restrict the Origin header, so a page in your own browser can connect directly. Any page that holds your key can trade your balance. Never put a key in a page that other people load.
The retired /magic-cpricefeed/v2 endpoint returns 502. Any code still pointing at it, or at pro.magicmarkets.com, is stale.

Wire format

Every frame the server sends is a batch envelope:
Each data[] entry is an array whose first element is a type tag:
Batching boundaries carry no meaning. A frame may hold a register snapshot and its response together, or offers alongside account updates. Never rely on ordering, grouping, or a type appearing exactly once per frame. Iterate data[] and dispatch on entry[0]. Ignore tags you do not recognise rather than failing.

Initial sync

On connect the server sends every currently priced event as ["event", ...] entries, possibly across several frames, then ["sync", {"session_id": ...}]. Account messages such as balance and xrate can arrive in the same frames, before sync. GET /v2/events/ returns the same event objects over REST.
Dispatch on event_type. A normal event carries home and away. A multirunner event carries a teams array and an end_time. The snapshot contains only events that currently have prices, and it can hold thousands of them across many frames. After sync, changed events re-arrive as event and events that lose their prices arrive as remove_event. Keep the session_id: it helps when correlating with REST errors or talking to support.

Commands

Commands are JSON arrays sent by the client. Registering an event with no prices is not an error. You get an empty snapshot. There is no unknown-event error. The registered-event cap is counted across all your connections. Exceeding it returns customer_event_limit_exceeded. Registrations do not survive a reconnect; re-register after any drop.

Message types

Two of these matter for correctness. On clear_events, drop every event, offer and in-play state you hold and wait for the new snapshot and sync. On betslip_closed, stop waiting for quotes on that betslip and create a new one to re-quote.

Offers

price_list is sorted by decimal price descending, one entry per price, so price_list[0] is the highest advertised price. Offer prices are indicative. A book can show prices that cross, and not every level can be traded. The price you can trade is the pmm quote for your betslip. min is null when there is no minimum. max is the stake available at that price. An empty price_list means no liquidity. An offer is a full replacement for its (sport, event_id, bet_type), not a delta. Overwrite, do not merge. for and against on the same selection are two separate offers.

Your quotes and orders

After POST /v2/betslips/, the quote arrives here:
Match it to your betslip by betslip_id and wait for a non-empty price_list. After POST /v2/orders/, watch order messages for your order_id until status is done or failed, and bet messages for the matched pieces.

Errors

Command errors arrive in-band and leave the socket open:
Codes: bad_json, invalid_input, already_registered, customer_event_limit_exceeded, invalid_customer, system_error. invalid_customer is transient: retry after a short backoff. Treat anything else as opaque: log it and retry with backoff. See Errors and rate limits for the full table. Backpressure. If you read too slowly, the server closes with code 1008. A client that has stopped reading sees only a TCP close. Silent drops. Two failures close an established connection with a raw TCP close, no close frame and no error: an I/O error and an internal error. A quiet socket is not proof of a healthy one.

Reconnect discipline

Treat any exit from the pump as a reconnect trigger. Read fast: if you do heavy work per message, hand frames to a queue and process them off the read loop, or backpressure will close the connection. After a 1008 close, fix the read speed or register fewer events before you reconnect, because an immediate reconnect overflows again. On reconnect you get a fresh snapshot, so rebuild your state from it rather than assuming your cache survived.

Next