Vector Market Data API
Real-time Brazilian market data (B3) delivered over a plaintext JSON WebSocket, plus REST snapshots and native AI interfaces (MCP & A2A). One API key governs every surface, with per-plan entitlements, limits and auditing.
Base URL
https://md.vectrading.com
How you connect
- WebSocket — live order books, the primary streaming surface. Authenticated with a short-lived connection token.
- REST — point-in-time snapshots, catalog and history. Same connection token.
- MCP / A2A — for LLMs and agents. Accept the API key directly (the gateway exchanges it server-side).
Data profiles
Every symbol belongs to one profile; your plan is entitled to a subset.
| Profile | Contents |
|---|---|
fut | Outright futures and spreads (WDO, DOL, WIN, IND, DI1, …) |
events | Event contracts and special listings (Copom, etc.) |
equities | Curated liquid B3 equities |
options | At-the-money equity, index & dollar options — front expiration only, ~6 strikes each side of spot (WDO/WIN option series included). Tickers follow the B3 pattern (e.g. BB3#BWD… for dollar, BB3#BWI… for index). |
Symbol format
Symbols use the BB3#<TICKER> form, e.g. BB3#PETR4,
BB3#WDON26. A bare ticker is auto-prefixed with BB3# and
upper-cased server-side.
Authentication
All access is keyed to an API key (vmd_live_…) created in
your dashboard. There are two credential models depending on the surface:
| Surface | Credential |
|---|---|
WebSocket, REST (/mdgateway/*) | A short-lived connection token (JWT) obtained from the API key — see below. |
MCP (/mcp), A2A (/a2a/*) | The API key sent directly as Authorization: Bearer vmd_live_…; the gateway exchanges it for you. |
Connection tokens
/api/connection-tokensExchange your API key for a short-lived connection token (JWT, HS256).
Request
Send the API key in the Authorization header (or as an api_key body field). No body is required.
curl -X POST https://md.vectrading.com/api/connection-tokens \ -H "Authorization: Bearer vmd_live_YOUR_KEY"
Response 200
{
"token_type": "Bearer",
"access_token": "eyJhbGciOiJIUzI1Ni␣...", // the connection token
"expires_at": "2026-06-30T19:40:00Z",
"expires_in": 300,
"account": { "id": "12", "name": "Acme Quant", "status": "active" },
"plan": {
"slug": "beta",
"name": "Beta",
"max_concurrent_connections": 3,
"max_symbols": 50,
"min_interval_ms": 250,
"history_days": 30,
"profiles": ["fut", "equities"],
"ai_interfaces": ["mcp", "a2a"]
}
}
Use access_token as the token for the WebSocket and REST surfaces. Refresh
before expires_in elapses to open new connections.
WebSocket streaming
/ws/v1?token=<access_token>Connect with the connection token either as the token query parameter or an
Authorization: Bearer <access_token> header. On connect the server sends a
hello frame with your effective limits; then you subscribe and
receive book frames.
Minimal browser client
// 1) Get a connection token from your backend (POST /api/connection-tokens)
const token = "YOUR_CONNECTION_TOKEN";
const ws = new WebSocket("wss://md.vectrading.com/ws/v1?token=" + token);
ws.onopen = () => {
ws.send(JSON.stringify({ type: "subscribe", symbols: ["BB3#PETR4", "BB3#WDON26"] }));
};
ws.onmessage = (ev) => {
const msg = JSON.parse(ev.data);
if (msg.type === "book") {
const bestBid = msg.book.bids[0]; // { price, size }
const bestAsk = msg.book.asks[0];
console.log(msg.symbol, bestBid, bestAsk);
}
};
Conflation & keepalive
- Book updates are conflated per symbol and flushed every
min_interval_ms(from your plan) — you get the latest book per symbol per interval, not every raw tick. - The server sends a WebSocket ping every 20s; standard clients auto-pong. You may also
send
{"type":"ping"}and receive{"type":"pong"}. Idle connections are closed after ~70s without traffic.
Message reference
Client → server
| Command | Shape |
|---|---|
| subscribe | {"type":"subscribe","symbols":["BB3#PETR4"]} |
| unsubscribe | {"type":"unsubscribe","symbols":["BB3#PETR4"]} |
| ping | {"type":"ping"} |
Server → client
hello — sent once on connect:
{
"type": "hello", "seq": 1,
"stats": {
"account_id": "12", "plan": "beta",
"max_symbols": 50, "max_concurrent_connections": 3, "min_interval_ms": 250
}
}
subscribed / unsubscribed — acknowledge accepted symbols:
{ "type": "subscribed", "seq": 2, "symbols": ["BB3#PETR4", "BB3#WDON26"] }
book — the full market-data snapshot for a symbol: order book depth
and trade data (last/high/low, the trade tape, and a volume-by-price profile).
Emitted at most once per min_interval_ms per symbol (conflated).
{
"type": "book", "seq": 25,
"profile": "equities", "symbol": "BB3#PETR4",
"time": 1782848877.916,
"book": {
"code": "BB3#PETR4",
"time": 1782848877.916,
"status": "O",
"ticksize": 0.01,
"mid": 37.805,
"settle": null,
"lasttrade": 37.81,
"hightrade": 37.85,
"lowtrade": 37.40,
"theoprice": null,
"expiry": 0,
"bids": [ { "price": 37.79, "size": 3400 }, { "price": 37.78, "size": 21000 } ],
"asks": [ { "price": 37.80, "size": 19000 }, { "price": 37.81, "size": 14300 } ],
"implieds": { "bids": [], "asks": [] },
"icebergs": [ { "rowprice": 37.75, "side": 1 } ],
"trades": [
{ "price": 37.81, "size": 200, "side": 1, "cond": "@", "time": 1782848877.910 }
],
"trademap": { "prices": [3780, 3781, 3785], "vols": [431700, 122600, 186700] }
}
}
book fields
Order book
| Field | Type | Meaning |
|---|---|---|
bids / asks | array | { price, size } levels, best price first. |
implieds | object | Implied levels for spreads/options: { bids, asks }, each { price, size }. |
icebergs | array | Detected iceberg levels: { rowprice, side }. |
Trades & prices
| Field | Type | Meaning |
|---|---|---|
lasttrade | float | Last traded price (null if none yet). |
hightrade / lowtrade | float | Highest / lowest traded price of the session. |
mid | float | Mid price (best bid/ask). |
settle | float | Settlement price (null intraday). |
theoprice | float | Theoretical price during auction (TC_THEO). |
trades | array | The trade tape — executions since the previous message for this symbol. [] when none occurred in the window. Element shape below. |
sending_time | float | Exchange-side packet clock (B3 SendingTime, epoch seconds) of the latest feed event — use it to measure end-to-end latency against time. |
trademap | object | Cumulative volume-by-price profile: { prices: [...], vols: [...] } (parallel arrays). prices[i] is an integer tick index — actual price = prices[i] × ticksize; vols[i] is total traded volume there (buy + sell summed, not split by side). |
Metadata
| Field | Type | Meaning |
|---|---|---|
code | string | Symbol (same as the envelope symbol). |
time | float | Book timestamp (epoch seconds). |
status | string | Instrument status: O open, P pre/post/paused/auction. |
ticksize | float | Minimum price increment. |
expiry | float | Contract expiration (epoch seconds; 0 for spot equities). |
Trade element (each item of trades):
{
"price": 37.81, // executed price
"size": 200, // executed quantity
"side": 1, // aggressor: +1 buy, -1 sell, 0 unknown
"cond": "0", // trade condition (see table below)
"time": 1782848877.91,// epoch seconds (gateway-side event clock)
"id": "388231", // exchange fill id — STRING-encoded uint64;
// monotonic per symbol within the session;
// "0" for summary trades (cond "S")
"buyer": 3, // buyer participant/broker code (0 = unknown)
"seller": 120 // seller participant/broker code (0 = unknown)
}
Trade conditions (cond)
| cond | Meaning |
|---|---|
0 | Regular trade |
R | RLP trade (Retail Liquidity Provider) |
S | Trade summary — aggregate mirror of the classified prints. Per window, vol(S) = vol(0) + vol(R). Deduplicate by keeping only 0/R. |
H | Theoretical price print (auction / pre-open; feeds theoprice) |
X | Cross trade |
W | Sweep |
1 | Spread leg |
2 | Marketplace entered |
3 | Multi-asset |
I | Implied |
E | Iceberg |
trademap accumulates only
cond "0" volume (RLP excluded). status values observed:
O open, P pre/post/auction/paused, C closed.status: "P") or when no trade occurred in the
refresh window, trades is [] and lasttrade/trademap
may be empty/null — the book (bids/asks) is still delivered.error — rejected commands or symbols:
{
"type": "error", "seq": 7,
"code": "subscribe_rejected",
"message": "one or more symbols were rejected",
"details": [
{ "symbol": "BB3#XYZ", "code": "unknown_symbol", "message": "symbol not found in gateway cache" }
]
}
REST — snapshots & catalog
Point-in-time reads of the same cache the stream uses. Authenticate with the
connection token (Authorization: Bearer <access_token>
or ?token=). All paths are under the /mdgateway/ prefix.
/mdgateway/book?symbol=BB3#PETR4TOKEN=$(curl -s -X POST https://md.vectrading.com/api/connection-tokens \ -H "Authorization: Bearer vmd_live_YOUR_KEY" | jq -r .access_token) curl "https://md.vectrading.com/mdgateway/book?symbol=BB3#PETR4" \ -H "Authorization: Bearer $TOKEN"
/mdgateway/snapshot?symbols=BB3#PETR4,BB3#VALE3Returns { "snapshots": [...], "rejected": [...] } for one or more symbols.
/mdgateway/catalog?q=PETR&profile=equities&limit=100Searches currently-cached symbols visible to your plan. Returns
{ "symbols": [{symbol, profile, time, raw_at}], "count", "limit" }.
History
/mdgateway/history, MCP
get_history and A2A getHistory return an error until it is
re-enabled. Live snapshots and streaming are unaffected.MCP
/mcpJSON-RPC 2.0 Model Context Protocol endpoint (protocol 2025-06-18).
Authenticate with Authorization: Bearer vmd_live_… (the API key directly).
Methods: initialize, tools/list, tools/call,
ping.
Tools
| Tool | Arguments |
|---|---|
get_gateway_status | — |
get_coverage | — |
search_symbols | query, profile, limit |
get_snapshot | symbol or symbols[] |
get_book | symbol (req), depth, include_raw |
get_history | symbol, start, end (currently disabled) |
curl -X POST https://md.vectrading.com/mcp \
-H "Authorization: Bearer vmd_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"get_book","arguments":{"symbol":"BB3#PETR4","depth":5}}}'
Returns a normalized book (summary with best bid/ask/mid/spread plus trimmed
levels) — MCP does not stream the raw feed; use the WebSocket for that.
A2A
Agent-to-Agent interface. The agent card is public; capability calls require the API key.
/.well-known/agent-card.json public/a2a/agents/marketdataJSON-RPC message/send; the capability name goes in the message text.
Capabilities: getGatewayStatus, getCoverage,
listSymbols, getSnapshot, getBook,
getHistory.
curl https://md.vectrading.com/a2a/agents/marketdata \
-H "Authorization: Bearer vmd_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"message/send",
"params":{"message":{"role":"user","parts":[{"kind":"text","text":"listSymbols"}]}}}'
Errors & limits
Connection-token errors HTTP 401
| code | Meaning |
|---|---|
missing_api_key | No Authorization: Bearer / api_key provided. |
invalid_api_key | Key not recognized. |
key_inactive | Key revoked or not active. |
key_expired | Key past its expiry. |
account_or_plan_inactive | Account disabled or plan not active. |
WebSocket errors
| Where | code | Meaning |
|---|---|---|
| Upgrade | HTTP 401 | Missing/invalid connection token. |
| Upgrade | HTTP 429 | Concurrent-connection limit for your account reached. |
| Command | bad_json | Command was not valid JSON. |
| Command | unknown_command | Type is not subscribe/unsubscribe/ping. |
| Subscribe | unknown_symbol | Symbol not in the gateway cache. |
| Subscribe | profile_not_entitled | Your plan is not entitled to the symbol's profile. |
| Subscribe | symbol_limit_exceeded | Per-connection symbol limit reached. |
Plan limits
| Limit | Effect |
|---|---|
max_symbols | Max symbols subscribed per connection. |
max_concurrent_connections | Max simultaneous WebSocket connections per account. |
min_interval_ms | Conflation interval — minimum gap between book frames per symbol. |
history_days | Maximum history look-back window (when history is enabled). |
profiles | Which data profiles your plan may access. |