MagicMarkets
Mercados de previsão esportiva para agentes de IA — preços ao vivo, cotações, ordens e posições.
Documentação
magicmarkets-cli
Uma interface de linha de comando para a API Magic Markets v2 — transmita preços esportivos ao vivo, selecione cotações, coloque e gerencie ordens e inspecione sua posição. A mesma API também está disponível como ferramentas MCP, via stdio (um cliente inicia o magicmarkets mcp como subprocesso) ou pelo transporte HTTP transmissível em localhost.
Binário estático único, autenticado com uma chave de API. Sem assinatura de requisições, sem chaves privadas.
magicmarkets markets --sport fb # find events with live prices
magicmarkets offers fb 2026-06-15,1001,2002 # list priced bet types
magicmarkets betslip create fb 2026-06-15,1001,2002 for,h --wait 5s
magicmarkets order place --betslip bs-123 --price 2.10 --stake 50
- Configuração — instalação, autenticação, primeiros comandos
- Como usar — fluxo de apostas, referência de comandos, MCP, preços, erros
- Desenvolvimento — estrutura, geração de código, convenções, contribuição
Configuração
1. Instalação
A partir de um clone:
git clone https://github.com/magicmarkets/magicmarkets-cli
cd magicmarkets-cli
make install # installs `magicmarkets` into your Go bin directory
make where mostra exatamente onde foi instalado. Se magicmarkets não for encontrado depois, esse diretório não está no seu PATH:
export PATH="$PATH:$(go env GOPATH)/bin" # add to ~/.zshrc or ~/.bashrc
Prefere não instalar? make build produz ./build/magicmarkets e não altera seu PATH.
go install diretamente
Observe o caminho /cmd/magicmarkets — instalar a raiz do módulo criaria um binário chamado magicmarkets-cli:
go install ./cmd/magicmarkets
O caminho do módulo Go é magicmarkets-cli, não uma URL do GitHub, então go install github.com/…/magicmarkets-cli@latest não funcionará. Clonar e executar make install é o caminho suportado.
2. Adicione sua chave de API
Crie uma chave em magicmarkets.com em Configurações → API. Ela é exibida apenas uma vez na criação, então armazene-a imediatamente.
Coloque-a em ~/.magicmarkets/.env para que funcione a partir de qualquer diretório:
mkdir -p ~/.magicmarkets
echo 'MAGICMARKETS_API_KEY=your-key-here' > ~/.magicmarkets/.env
chmod 600 ~/.magicmarkets/.env
Uma variável de ambiente ou um .env local ao projeto também funciona — cp env.example .env e preencha.
3. Verificação
$ magicmarkets status
version v1.0.0
api url https://magicmarkets.com/v2
ws url wss://magicmarkets.com/v2/stream
lang en
api key ***********1234
env files [/Users/you/.magicmarkets/.env]
authenticated yes
Sempre verifique isso antes de abrir um stream — o WebSocket rejeita uma chave inválida no handshake sem uma mensagem de erro útil.
Isso é toda a configuração. Tudo abaixo é opcional.
Experimente
magicmarkets balance # money position
magicmarkets xrates # exchange rates to USDT
magicmarkets markets --sport fb --limit 5 # events that currently have prices
magicmarkets offers fb <event-id> --depth 2 # priced bet types on one event
magicmarkets orders --open # your open orders
magicmarkets ticks 2.345 # where a price lands on the tick schedule
magicmarkets api endpoints # every endpoint (no key, no network)
Adicione --json a qualquer comando para canalizá-lo para jq.
Configuração
Resolvida nesta ordem, vencendo a primeira correspondência:
| Prioridade | Fonte |
|---|---|
| 1 | Variáveis de ambiente reais |
| 2 | ./.env |
| 3 | ~/.magicmarkets/.env |
| 4 | ~/.env |
| 5 | Padrões integrados |
| Variável | Padrão | Finalidade |
|---|---|---|
MAGICMARKETS_API_KEY | — | Chave de API (X-Api-Key). Obrigatória, a menos que MAGICMARKETS_ACCESS_TOKEN esteja definida |
MAGICMARKETS_ACCESS_TOKEN | — | Token Bearer OAuth para CLI/stdio. O MCP HTTP ainda recebe o token por requisição |
MAGICMARKETS_OAUTH_ISSUER | https://magicmarkets.com/api/auth | AS upstream para o qual mcp --http faz proxy de /authorize e /token, e contra o qual POST /oauth2/firebase-token é resolvido para uma credencial Bearer — veja Autenticação |
MAGICMARKETS_SESSION_GROUP_ID | — | ID específico do ambiente da Magic Markets, usado para construir o valor session ao qual uma credencial Bearer OAuth resolve. Sem padrão seguro — obrigatório onde uma credencial Bearer for esperada |
MAGICMARKETS_FIREBASE_WEB_API_KEY | — | Chave da API Web do Firebase para o projeto Firebase da Magic Markets, usada para trocar o token customizado do Firebase de /oauth2/firebase-token por um token de ID Firebase real — veja Autenticação. Sem padrão seguro — obrigatório onde uma credencial Bearer for esperada |
MAGICMARKETS_OAUTH_CLIENT_ID | — | Cliente pré-registrado que permite o próprio /mcp/callback deste host |
MAGICMARKETS_OAUTH_PROXY_SECRET | — | Selo do estado do proxy OAuth; toda réplica deve compartilhá-lo |
MAGICMARKETS_MCP_PUBLIC_URL | — | Base pública de mcp --http (este host é o Servidor de Autorização MCP) |
MAGICMARKETS_API_URL | https://magicmarkets.com/v2 | Base REST, incluindo /v2 |
MAGICMARKETS_BASIC_AUTH | — | user:pass em Base64 enviado como cabeçalho adicional Authorization: Basic em toda chamada REST /v2/* — o token para uma barreira de infraestrutura que alguns ambientes não produtivos colocam diante da API v2. Produção não tem essa barreira, então isso fica indefinido lá. Nunca enviado para /oauth2/firebase-token, signInWithCustomToken do Firebase ou o stream WebSocket |
MAGICMARKETS_WS_URL | derivado de MAGICMARKETS_API_URL | Endpoint do stream |
MAGICMARKETS_LANG | en | Idioma do nome do evento: en, ko, zh-hans |
MAGICMARKETS_TIMEOUT | 30s | Tempo limite por requisição |
MAGICMARKETS_ALLOW_TRADING | indefinido (desligado) | Permite que magicmarkets mcp coloque apostas. Sem efeito na CLI. |
Flags globais: --json, --verbose/-v, --api-key, --api-url, --ws-url.
--api-url rederiva o endpoint do stream a partir dele (correspondendo à própria
derivação de MAGICMARKETS_WS_URL), a menos que MAGICMARKETS_WS_URL ou --ws-url o fixe explicitamente —
então --api-url https://staging... não deixa stream lendo preços de produção
enquanto todos os outros comandos alcançam o ambiente de staging.
Como usar
O fluxo de aposta em duas etapas
Colocar uma aposta sempre exige duas etapas:
- Um betslip registra interesse em uma seleção e recebe uma cotação ao vivo. Não custa nada e não compromete nada.
- Uma ordem compromete um valor contra essa cotação.
Betslips são de curta duração e não têm preço quando criados — a cotação chega de forma assíncrona, pelo WebSocket como uma mensagem pmm ou por polling. Daí o --wait:
# 1. find an event that has prices
$ magicmarkets markets --sport fb --limit 5
SPORT EVENT ID EVENT COMPETITION STATUS START
----- -------- ----- ----------- ------ -----
fb 2026-06-15,1001,2002 Arsenal v Chelsea England Premier League pre_event 2026-06-15 16:00:00
# 2. read a bet_type straight off the feed
$ magicmarkets offers fb 2026-06-15,1001,2002 --depth 2
BET TYPE MARKET IR PRICES (stake @ price) TOTAL
-------- ------ -- ---------------------- -----
for,h 1x2 - 150.00@2.10 80.00@2.08 230.00
for,ah,h,-4 ah - 200.00@1.95 120.00@1.94 320.00
# 3. quote it, waiting for the price to land
$ magicmarkets betslip create fb 2026-06-15,1001,2002 for,h --wait 5s
betslip id bs-abc123
bet type for,h
description Home
expires 2026-06-15 15:42:10 (28s)
total available 230.00 USDT
Prices:
PRICE MIN MAX
----- --- ---
2.10 5.00 150.00
2.08 - 80.00
# 4. commit a stake (asks for confirmation)
$ magicmarkets order place --betslip bs-abc123 --price 2.10 --stake 50
Nunca construa um bet_type manualmente. Copie-o literalmente de magicmarkets offers ou do stream — ele codifica mercado, handicap, resultado e direção em uma única string.
Referência de comandos
Conta
| Comando | Finalidade |
|---|---|
magicmarkets status | Mostrar configuração e verificar a chave |
magicmarkets balance | Saldo, valor aberto, crédito inteligente, disponível |
magicmarkets xrates | Taxas de câmbio para USDT |
magicmarkets position | P&L agregado, com --grid para a matriz de pagamento |
Descoberta
| Comando | Finalidade |
|---|---|
magicmarkets markets | Eventos que atualmente têm preços |
magicmarkets offers <sport> <event-id> | Tipos de aposta com preço em um evento |
magicmarkets stream | Acompanhar o feed ao vivo de preços e da conta |
A API REST v2 não tem endpoint de listagem de eventos — a descoberta acontece pelo WebSocket. Esses comandos conectam, leem o snapshot e desconectam, então levam alguns segundos.
magicmarkets markets --sport fb,tennis --limit 20
magicmarkets markets --search arsenal
magicmarkets markets --in-play
magicmarkets offers fb 2026-06-15,1001,2002 --market ah --depth 3
magicmarkets stream --register fb:2026-06-15,1001,2002
magicmarkets stream --type order,bet # only order activity
Negociação
| Comando | Finalidade |
|---|---|
magicmarkets betslip create [sport] [event] [bet-type] | Cotar uma seleção |
magicmarkets betslip get <id> | Mostrar um betslip e seus preços |
magicmarkets betslip list | IDs de betslips abertos (--expand para detalhes completos) |
magicmarkets betslip refresh <id> | Estender expiração |
magicmarkets order place | Colocar uma ordem contra um betslip |
magicmarkets orders / magicmarkets order list | Listar ordens |
magicmarkets order get <id> | Mostrar uma ordem e suas apostas |
magicmarkets order tracked <uuid> | Buscar uma ordem pela chave de idempotência |
magicmarkets order updates | Ordens alteradas em uma janela de tempo |
magicmarkets order close <id> | Cancelar uma ordem |
magicmarkets order close-many <id>... | Cancelar até 500 ordens |
magicmarkets order close-all | Cancelar todas as ordens abertas |
Apostas lay e combinadas:
# lay (against) a selection
magicmarkets betslip create --lay fb 2026-06-15,1001,2002 for,over,2.5
# a 2-leg accumulator
magicmarkets betslip create \
--leg fb:2026-06-15,1001,2002:for,h \
--leg fb:2026-06-16,1003,2004:for,over,2.5 --wait 5s
Risco
| Comando | Finalidade |
|---|---|
magicmarkets heartbeat run | Criar um heartbeat e mantê-lo ativo em primeiro plano |
magicmarkets heartbeat create/list/get/refresh/cancel | Gerenciar heartbeats diretamente |
Um heartbeat é um interruptor de segurança: se não for renovado antes de expirar, toda ordem aberta é fechada automaticamente. Execute um junto com uma estratégia automatizada para que uma falha não deixe ordens ativas.
$ magicmarkets heartbeat run --timeout 60
heartbeat hb-xyz created, expires 2026-06-15 15:43:10; refreshing every 20s
press Ctrl-C to cancel it and leave orders open
No Ctrl-C, o heartbeat é cancelado de forma limpa, deixando as ordens abertas. Se o processo morrer, o interruptor dispara.
MCP
| Comando | Finalidade |
|---|---|
magicmarkets mcp | Ferramentas MCP via stdio por padrão, ou --http para o transporte HTTP transmissível em localhost. Veja MCP |
Referência
| Comando | Finalidade |
|---|---|
magicmarkets bet-type <sport> <bet-type> | Validar um tipo de aposta, mostrar sua grade de pagamento |
magicmarkets ticks <price> | Ajustar um preço ao cronograma de ticks |
magicmarkets api endpoints | Listar todos os endpoints |
magicmarkets api show <path> [method] | Detalhe do endpoint: parâmetros, corpo, respostas |
magicmarkets api schema [name] | Esquemas de componentes |
magicmarkets api curl <method> <path> | Gerar um comando curl executável |
magicmarkets api search <term> | Buscar endpoints e esquemas |
magicmarkets api spec | Imprimir a especificação OpenAPI embutida |
Os comandos magicmarkets api não precisam de chave de API nem de rede — a especificação OpenAPI 3.1 é compilada no binário.
Checklist de operação segura
Vale internalizar antes de executar qualquer coisa que gaste dinheiro:
- Passe
--request-uuidem toda ordem. Isso torna a colocação idempotente: uma nova tentativa após timeout não pode criar uma segunda ordem, e a ordem permanece recuperável por seis horas. Sem isso, um timeout deixa você em dúvida se a aposta foi colocada. - Execute um heartbeat ao automatizar. Sem um, uma estratégia que falha deixa ordens ativas no mercado.
- Verifique a chave via REST antes de abrir um stream. O WebSocket falha no handshake sem erro útil.
- Pegue
bet_typedo feed, nunca manualmente. Linhas de handicap asiático são 4× a linha real, então uma string construída à mão é fácil de errar silenciosamente. - Confira o preço ajustado.
magicmarkets order placeo mostra na confirmação; esse é o preço com o qual a ordem realmente executa, não o que você digitou.
Preços e o cronograma de ticks
Todo preço está em um cronograma de ticks fixo cujo passo aumenta conforme o preço cresce:
| Preço decimal | Tick |
|---|---|
| 1,01 – 2 | 0,01 |
| 2 – 3 | 0,02 |
| 3 – 4 | 0,05 |
| 4 – 6 | 0,10 |
| 6 – 10 | 0,20 |
| 10 – 20 | 0,50 |
| 20 – 30 | 1 |
| 30 – 50 | 2 |
| 50 – 100 | 5 |
| 100 – 1000 | 10 |
Um preço de ordem fora do tick é arredondado para nunca apertar seu limite: para baixo em ordens back (for), para cima em ordens lay (against). magicmarkets order place ajusta o preço por conta própria e mostra o resultado na confirmação.
$ magicmarkets ticks 2.345
snapped price 2.34 # back: rounded down
$ magicmarkets ticks 2.345 --lay
snapped price 2.36 # lay: rounded up
Preços cotados pelo feed já estão no cronograma e nunca são rearredondados.
Gramática de tipos de aposta
bet_type é uma string separada por vírgulas que começa com a direção: for para back, against para lay. Handicaps sempre se referem ao time da casa.
| Exemplo | Significado |
|---|---|
for,h / for,d / for,a | Vitória da casa / empate / vitória fora |
for,dnb,h | Vitória da casa, anulada se empate |
for,dc,h,d | Chance dupla: casa ou empate |
for,over,2.5 / for,under,2.5 | Mais/menos 2,5 gols |
for,ah,h,-4 | Handicap asiático, casa -1,0 |
for,ahover,7 | Total asiático acima de 1,75 |
for,cs,2,1 | Placar exato 2–1 |
for,score,both,yes | Ambas as equipes marcam |
for,win,<team_id> | Participante vence um mercado outright |
for,top,3,<team_id> | Participante termina no top 3 |
Linhas de handicap asiático são inteiros iguais a 4× a linha real — -4 é -1,0, 2 é +0,5, 7 é +1,75. Isso mantém linhas com passo de 0,25 como inteiros apenas no fio.
Valide qualquer string candidata:
$ magicmarkets bet-type fb for,ah,h,-4
description Home -1.0 (Asian)
valid yes
A gramática completa — períodos de tênis, tokens de período de tempo, todos os mercados — está em docs/api-reference.md.
Saída JSON
Todo comando aceita --json:
magicmarkets orders --open --json | jq -r '.[] | "\(.order_id) \(.status)"'
magicmarkets markets --sport fb --json | jq -r '.[].event_id'
magicmarkets stream --type order --json # one JSON object per line
Valores são tuplas de dois elementos, não objetos — --json espelha o formato
do fio da API exatamente, então indexe-os em vez de buscar um nome de campo:
$ magicmarkets balance --json
{
"balance": ["USDT", 10000.5],
"open_stake": ["USDT", 152.55],
"smart_credit": null
}
$ magicmarkets balance --json | jq '.balance[1]' # amount
10000.5
$ magicmarkets balance --json | jq -r '.balance[0]' # currency
USDT
O mesmo se aplica a todo campo monetário: want_stake, stake, profit_loss, total e o min/max dentro de um nível de preço.
MCP
magicmarkets mcp serve a API como ferramentas MCP, para que um agente LLM possa ler preços e gerenciar ordens. Por padrão, fala stdio: um cliente MCP (Claude Code, Cursor e similares) o inicia como subprocesso e eles trocam JSON-RPC em stdin/stdout. Passe --http para, em vez disso, servir o transporte HTTP transmissível do MCP em localhost — veja Servindo via HTTP em localhost abaixo.
stdio
Registre o comando em um cliente:
claude mcp add magicmarkets -e MAGICMARKETS_API_KEY=your-key -- magicmarkets mcp
Ou no arquivo de configuração MCP de um cliente — mcpServers é o nome do cliente para um subprocesso stdio, não um serviço de rede:
{
"mcpServers": {
"magicmarkets": {
"command": "magicmarkets",
"args": ["mcp"],
"env": { "MAGICMARKETS_API_KEY": "your-key" }
}
}
}
O suporte a MCP está em modo de desenvolvedor por enquanto — espere alguma fricção ao conectar um servidor stdio a um cliente específico, e espere que isso continue melhorando. Para configuração específica de cliente (onde o arquivo de configuração fica, comportamento de reinicialização, locais de log), consulte a documentação do próprio cliente em vez deste README:
- Claude Code: Connect to tools via MCP
- Model Context Protocol: Connect to local (stdio) servers — cobre Claude Desktop e outros clientes MCP genericamente
Entre clientes, o problema mais comum é o campo command: um cliente frequentemente inicia o subprocesso com um PATH mínimo, não o do seu shell, então um "command": "magicmarkets" simples pode falhar ao resolver mesmo que o mesmo comando funcione em um terminal. Se isso acontecer, use o caminho absoluto:
which magicmarkets # or: make where
Servindo via HTTP localhost
Passe --http para servir o transporte HTTP streamable em vez de stdio — útil quando um cliente se conecta pela rede, ou você quer um servidor de longa duração compartilhado por vários clientes em vez de um subprocesso por cliente:
magicmarkets mcp --http --addr 127.0.0.1:8383
--addr padrão é 127.0.0.1:8383 — somente loopback, então nada fora da máquina pode alcançá-lo de qualquer forma. --http não tem TLS próprio — coloque-o atrás de um proxy reverso se você o expor além do loopback.
O servidor não usa MAGICMARKETS_API_KEY. Cada requisição deve enviar a credencial do chamador como X-Api-Key ou Authorization: Bearer. Uma chave de API é encaminhada para a API REST da Magic Markets e /v2/stream inalterada. Um token Bearer não é — ele é resolvido primeiro, via POST {MAGICMARKETS_OAUTH_ISSUER}/oauth2/firebase-token, para a credencial que eles realmente exigem; veja Authentication para o fluxo completo e suas limitações conhecidas. Stdio ainda aceita MAGICMARKETS_API_KEY ou MAGICMARKETS_ACCESS_TOKEN do ambiente.
Aponte um cliente para a URL em vez de um comando:
{
"mcpServers": {
"magicmarkets": {
"url": "http://127.0.0.1:8383/mcp",
"headers": { "X-Api-Key": "your-key" }
}
}
}
A mesma URL aceita um token de acesso OAuth:
{
"mcpServers": {
"magicmarkets": {
"url": "http://127.0.0.1:8383/mcp",
"headers": { "Authorization": "Bearer your-token" }
}
}
}
Hosts MCP remotos que falam OAuth podem pular o mapa de cabeçalhos. Respostas /mcp não autenticadas incluem WWW-Authenticate apontando para metadados de recurso protegido que nomeiam este host MCP como o Servidor de Autorização (então o Dynamic Client Registration do Claude faz POST /register aqui, não https://magicmarkets.com/register). Este processo executa seu próprio login PKCE contra https://magicmarkets.com/api/auth — ele nunca encaminha o redirect_uri de um cliente downstream para cima, já que o AS real da Magic Markets só permite o callback deste próprio host ({public-url}/mcp/callback), nunca o do Claude ou do Cursor. Defina:
--public-url https://magicmarkets-mcp.dev-eu.kubershmuber.comno deploy hospedadoMAGICMARKETS_OAUTH_CLIENT_IDpara um cliente emMAGICMARKETS_OAUTH_ISSUERcuja allowlist de redirect_uris inclui o/mcp/callbackdesse hostMAGICMARKETS_OAUTH_PROXY_SECRETem cada réplica de um deploy multi-réplica — o proxy não mantém estado de sessão no servidor (estado de login e códigos de uso único são tokens selados e autocontidos), então réplicas que não compartilham este segredo não conseguem decodificar os logins em andamento umas das outras
Habilitando negociação
Negociação está desativada por padrão. Um registro novo é somente leitura, então um agente pedindo para fazer uma aposta não encontrará nenhuma ferramenta place_order. Habilite com MAGICMARKETS_ALLOW_TRADING=1, substitua o registro existente e reinicie seu cliente:
claude mcp remove magicmarkets
claude mcp add magicmarkets -e MAGICMARKETS_API_KEY=your-key -e MAGICMARKETS_ALLOW_TRADING=1 -- magicmarkets mcp
Reiniciar importa: um cliente lê a lista de ferramentas do subprocesso uma vez na inicialização, então uma sessão já em execução mantém a lista somente leitura mesmo depois de você re-registrar.
Editando o arquivo de configuração MCP de um cliente diretamente, defina MAGICMARKETS_ALLOW_TRADING em env:
{
"mcpServers": {
"magicmarkets": {
"command": "magicmarkets",
"args": ["mcp"],
"env": {
"MAGICMARKETS_API_KEY": "your-key",
"MAGICMARKETS_ALLOW_TRADING": "1"
}
}
}
}
Confirme em qual modo você está sem envolver um cliente:
$ MAGICMARKETS_ALLOW_TRADING=1 magicmarkets mcp --print-tools
mode: trading ENABLED (via MAGICMARKETS_ALLOW_TRADING) — this process can place real bets
TOOL
----
close_all_orders
close_order
create_betslip
place_order
...
Execute sem a variável de ambiente para ver a lista somente leitura (11 ferramentas vs 19). magicmarkets mcp também registra o modo no stderr a cada início, o que aparece nos logs MCP do seu cliente (stderr é o único lugar onde essas linhas podem ir — stdout é o fluxo JSON-RPC).
O que cada modo expõe
| Sempre disponível | Requer MAGICMARKETS_ALLOW_TRADING |
|---|---|
get_balance, get_exchange_rates, get_position | create_betslip |
list_events, list_event_offers | place_order |
list_orders, get_order | close_order, close_all_orders |
list_betslips, get_betslip | create_heartbeat, refresh_heartbeat, cancel_heartbeat, list_heartbeats |
validate_bet_type, snap_price |
Habilite a negociação apenas se o agente deve ser capaz de apostar dinheiro real. As ferramentas que gastam dinheiro carregam dicas destrutivas MCP para que os clientes peçam confirmação antes de chamá-las.
Observe que esta porta se aplica apenas a magicmarkets mcp. O próprio magicmarkets order place da CLI está sempre disponível — ele tem seu próprio prompt de confirmação.
Erros
Erros carregam um code estável legível por máquina para ramificar:
| HTTP | Código | Significado |
|---|---|---|
| 400 | validation_error | Corpo ou consulta falhou na validação; razões por campo são impressas |
| 400 | order_closed | Pedido existe mas já está fechado ou liquidado |
| 401 | auth_error | Chave ausente, malformada ou rejeitada |
| 403 | forbidden | Chave válida mas ação não permitida |
| 404 | not_found | Recurso desconhecido ou invisível para esta chave |
| 409 | order_already_created | Um request_uuid foi reutilizado; o ID do pedido existente é relatado |
| 409 | limit_reached | Um limite por cliente foi atingido |
| 429 | throttled | Limitado por taxa; repetido automaticamente, respeitando Retry-After |
| 500 | server_error | Erro interno; cite o token de suporte ao relatar |
Requisições limitadas são repetidas automaticamente (duas vezes por padrão) porque um 429 significa que a requisição foi rejeitada completamente, então nada foi criado. Nenhum outro status é repetido.
Idempotência
uuid=$(uuidgen)
magicmarkets order place --betslip bs-123 --price 2.10 --stake 50 --request-uuid "$uuid"
magicmarkets order tracked "$uuid" # recover after a timeout, safely
Se um UUID reutilizado for detectado, magicmarkets busca e mostra o pedido original em vez de falhar.
Limites de taxa
Por conta, janela deslizante: 100 req/s de rajada e 1200 req/min sustentados no geral, com orçamentos dedicados de 10 req/s para criação de betslip e 5 req/s para colocação de pedidos.
Solução de problemas
no API key configured — defina MAGICMARKETS_API_KEY. magicmarkets status mostra quais arquivos .env foram lidos.
auth_error (401) — nenhuma chave foi enviada. Verifique o nome da variável.
session_not_found (404) em toda chamada — a chave foi enviada mas não é reconhecida. Regere-a em Configurações → API.
stream handshake failed — o WebSocket rejeita uma chave ruim no handshake HTTP. Execute magicmarkets status primeiro; REST dá um erro mais claro.
magicmarkets markets retorna nada — o snapshot só contém eventos que atualmente têm preços ao vivo, não a lista completa de fixtures. Tente sem --sport, ou aumente --timeout.
Betslip sem preços — cotações chegam assincronamente. Use --wait 5s. Se ainda não tiver nenhuma, nenhuma fonte está cotando essa seleção.
updated_at_to must be at least 60 seconds in the past — janelas magicmarkets order updates devem terminar ≥60s atrás e durar ≤70 minutos.
Um agente diz que não pode apostar / precisa de MAGICMARKETS_ALLOW_TRADING — o subprocesso magicmarkets mcp está rodando somente leitura, então as ferramentas de aposta não estão registradas. Veja Enabling trading: re-registre com MAGICMARKETS_ALLOW_TRADING=1 e reinicie o cliente. Verifique o modo atual com magicmarkets mcp --print-tools.
Desenvolvimento
Layout
cmd/magicmarkets/main.go entry point — signal handling, version
internal/config/ .env + environment resolution
internal/magicmarkets/ API client — no CLI or MCP dependencies
client.go transport, envelope, 429 retry
errors.go typed APIError per error code
types.go wire types (Stake is a [ccy, amount] tuple)
ticks.go tick schedule and price snapping
betslips.go orders.go account.go heartbeats.go
stream.go WebSocket client
internal/cli/ cobra command tree, table/JSON rendering
internal/mcpserver/ MCP tools over stdio or localhost HTTP, same client
internal/spec/ embedded openapi.json + reference commands
internal/magicmarketsapi/ generated models + the contract test guarding drift
tools/prepspec/ adapts the spec for oapi-codegen
docs/api-reference.md full API reference (vendored)
internal/magicmarkets não tem dependência das camadas CLI ou MCP, então é utilizável como uma biblioteca Go cliente simples.
Comandos do dia a dia
make test # go test ./...
make lint # go vet ./...
make fmt # gofmt -w
make build # ./build/magicmarkets
make install # install `magicmarkets` into your Go bin directory
make where # print where make install puts the binary
make generate # regenerate internal/magicmarketsapi from the vendored spec
make update-spec # refresh the vendored spec + docs, then regenerate
Testes não precisam de chave de API nem rede. Mantenha assim.
O pacote principal fica em cmd/magicmarkets/, não na raiz do módulo, então o binário é nomeado magicmarkets. Construir a raiz o nomearia pelo caminho do módulo — magicmarkets-cli — o que não é o que os docs ou magicmarkets --help dizem para executar. Mantenha novos alvos de build apontados para $(PKG).
make build e make install carimbam main.version de git describe, então magicmarkets --version relata algo rastreável. Substitua com make build VERSION=v1.2.3.
Geração de código
Modelos em internal/magicmarketsapi são gerados a partir da especificação OpenAPI vendida com oapi-codegen.
Configuração: nenhuma. oapi-codegen é fixado pela diretiva tool em go.mod, então make generate funciona em um clone novo. O arquivo gerado é versionado, então git clone && go build nunca requer codegen.
make generate # regenerate from internal/spec/openapi.json
make update-spec # pull the latest spec from the API, then regenerate
A especificação canônica vem de magicmarkets.com/magic-api/docs — make update-spec busca /magic-api/v2/openapi.json mais a referência Markdown. Execute git diff depois para ver exatamente o que a API mudou.
Esses tipos gerados são uma referência de contrato, não o que a CLI usa
O cliente em internal/magicmarkets mantém tipos escritos à mão, porque código gerado não consegue expressar três coisas que esta API precisa:
- Tuplas de stake.
["USDT", 115.38]é uma tupla OpenAPI 3.1; oapi-codegen não consegue gerar uma. - A união de status de aposta. O status de uma aposta é ou uma string simples ou um objeto.
magicmarkets.BetStatusdesserializa ambos; um tipo de união gerado empurra esse ramo para todo chamador. - Acesso não ponteiro. A especificação marca quase nada como
required, então todo campo gerado é um ponteiro. Passar verificações de nil pela CLI para campos que a API sempre envia seria ruído.
O que mantém os dois honestos
internal/magicmarketsapi/contract_test.go compara os nomes de campos JSON de cada tipo escrito à mão contra sua contraparte gerada, em ambas as direções, e falha em qualquer diferença. Um campo upstream adicionado, removido ou renomeado quebra go test após make generate em vez de ser descoberto em tempo de execução.
Ele provou seu valor na primeira execução: pegou bet_bar_values faltando em Order, que estava silenciosamente descartando um campo de magicmarkets order get --json.
Se falhar, a especificação e o cliente divergiram. Corrija o cliente, ou registre a exceção no mapa specOnly / handOnly desse par com uma razão. Não delete o par para fazê-lo passar.
Duas rugas tratadas por tools/prepspec
Ele adapta a especificação antes do codegen sem tocar no arquivo vendido:
number→float64. oapi-codegen mapeia umnumberOpenAPI sem formato parafloat32(~7 dígitos significativos), não suficiente para preços e stakes. prepspec adicionaformat: double. Isso inclui a forma anulável["number", "null"], que cobre precisamente os campos de preço alcançado. Um teste afirma que nenhum campo de dinheiro gerado é nuncafloat32.StakeTupleachatado para um array sem tipo, já que oapi-codegen falha completamente em uma tupla 3.1.magicmarkets.Stakeé o equivalente tipado real.
Não edite internal/magicmarketsapi/types.gen.go à mão.
Convenções e invariantes
Coisas nas quais este código depende. Quebrar uma deve ser deliberado.
Nunca faça um pedido real para testar uma mudança. magicmarkets order place, magicmarkets order close*, e as ferramentas MCP place_order / close_* gastam dinheiro real. Comandos somente leitura (status, balance, xrates, markets, offers, orders, position) e os comandos offline magicmarkets api são seguros de exercitar. Para caminhos de escrita, use um servidor stub local.
Verifique a especificação antes de inferir uma forma. magicmarkets api show orders POST vence adivinhação. Vários endpoints quebram o padrão comum {status, data}, e cada quebra foi um bug pego apenas lendo a especificação:
GET /v2/heartbeats/envolve dados sob uma chaveheartbeats; todo outro endpoint de lista retorna um array plano.POST /v2/orders/{id}/close/sempre retornadata: null. Re-leia o pedido para seu estado final.POST /v2/betslips/{id}/refresh/não tem corpo de resposta documentado, entãoRefreshBetslipre-lê o betslip em vez de decodificar a resposta.
Mantenha as camadas. internal/magicmarkets não deve importar internal/cli ou internal/mcpserver.
Dinheiro é float64, e preços passam por SnapPrice. Nunca introduza float32 em um caminho de preço ou stake.
Novas ferramentas MCP que gastam dinheiro vão atrás de AllowTrading e carregam uma dica destrutiva. A porta é testada; não a enfraqueça.
Código que lida com dinheiro precisa de um teste. ticks.go e o gate de negociação do MCP têm testes que verificam propriedades de segurança — um snap nunca aperta o limite do apostador, e as ferramentas de negociação são inacessíveis sem MAGICMARKETS_ALLOW_TRADING. Estenda esses testes em vez de contorná-los.
Todo comando suporta --json e renderiza uma tabela caso contrário. Dados vão para stdout; avisos e prompts vão para stderr, para que o pipe permaneça limpo.
Faça branch por códigos de erro, não por strings. Use magicmarkets.HasCode(err, magicmarkets.CodeOrderClosed).
Autenticação
Este repositório tem como alvo a API pública v2: https://magicmarkets.com/v2. Um chamador apresenta uma de duas credenciais — X-Api-Key, ou Authorization: Bearer com um token de https://magicmarkets.com/api/auth — mas apenas a chave de API é o que realmente vai para a rede. Um token Bearer não é aceito pela API v2 ou /v2/stream como está; ele deve primeiro ser resolvido, em dois saltos, para o par magic-metadata-jwt/session que esses endpoints exigem:
POST {issuer}/oauth2/firebase-token(Authorization: Bearer <access token>) emite um token customizado do Firebase carregando as permissões reais do jogador.- Um token customizado do Firebase não é por si só um token de ID válido — o próprio guia de integração OAuth da Magic Markets para implementadores de servidores MCP é explícito que um token customizado deve ser trocado por um token de ID do Firebase antes de ser utilizável. Este processo faz essa troca por conta própria, chamando a REST API do Identity Toolkit do Google diretamente —
POST https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=<MAGICMARKETS_FIREBASE_WEB_API_KEY>— e usa oidTokenretornado comomagic-metadata-jwt.
GET {issuer}/me não é usado para nada disso: é protegido por verificação de token de ID do Firebase, que o token de acesso OAuth autoassinado nunca satisfaz. internal/magicmarkets.MeResolver (internal/magicmarkets/meauth.go) faz ambos os saltos e armazena o resultado em cache; tanto internal/mcpserver quanto o caminho CLI/stdio passam pelo mesmo cliente, então ambos o obtêm automaticamente. magicmarkets mcp --http anuncia metadados de recursos protegidos por OAuth para que hosts MCP (Claude, Cursor, ...) possam obter um token Bearer em primeiro lugar.
Por que MAGICMARKETS_FIREBASE_WEB_API_KEY existe: o salto 2 acima é uma chamada à API do Identity Toolkit do Firebase, não a qualquer endpoint da Magic Markets, e o Firebase exige uma chave de API Web — com escopo no projeto Firebase da Magic Markets — para identificar qual token customizado do projeto está sendo trocado. Não é um segredo emitido por chamador; é a mesma chave de nível de projeto que qualquer cliente web do Firebase já incorpora no lado do cliente. Não tem um padrão seguro porque é específica do projeto e do ambiente (STG e produção são projetos Firebase diferentes), então — como MAGICMARKETS_SESSION_GROUP_ID — deve ser definida explicitamente onde quer que um chamador OAuth Bearer seja esperado, ou toda chamada autenticada por Bearer falha.
sequenceDiagram
participant Caller
participant Resolver as MeResolver (cache)
participant Issuer as Magic Markets AS
participant Firebase as Firebase Identity Toolkit
participant API as v2 API / stream
Note over Caller,API: An X-Api-Key credential skips all of this, forwarded unchanged.
Caller->>Resolver: Authorization Bearer access token
alt cache hit, MeCacheTTL is 1 minute
Resolver->>Resolver: reuse cached magic-metadata-jwt and session
else cache miss
Resolver->>Issuer: POST /oauth2/firebase-token<br/>Authorization Bearer access token
Issuer-->>Resolver: firebase_token, a Firebase custom token<br/>not yet valid as magic-metadata-jwt
Resolver->>Firebase: POST accounts:signInWithCustomToken<br/>key is MAGICMARKETS_FIREBASE_WEB_API_KEY, token is firebase_token
Firebase-->>Resolver: idToken
Resolver->>Resolver: cache magic-metadata-jwt as idToken<br/>session as m, group id, uuid joined by dashes<br/>uuid read from the access token's own sub claim
end
Resolver-->>Caller: magic-metadata-jwt, session
Caller->>API: REST headers magic-metadata-jwt and session<br/>stream query params jwt and token
Limitações conhecidas
- Este processo troca um token customizado do Firebase por um token de ID por conta própria, em vez de isso ser problema da Magic Markets.
POST /oauth2/firebase-tokenpoderia facilmente chamarsignInWithCustomTokenno lado do servidor e devolver um token de ID pronto para uso — poupando todo implementador de servidor MCP (não apenas este) de precisar deMAGICMARKETS_FIREBASE_WEB_API_KEY, uma dependência direta do endpoint do Identity Toolkit do Google e conhecimento da distinção token customizado/token de ID. Este é um workaround no lado do cliente para uma lacuna no contrato do Servidor de Autorização, não o estado final pretendido — reavalie quando/se/oauth2/firebase-tokenretornar um token de ID (ou a API v2 aceitar um token customizado diretamente). - O cache é em processo, não compartilhado. O cache de
MeResolveré um LRU por réplica (via variante expirável dehashicorp/golang-lru), não o estado selado e independente de réplica queinternal/mcpserver/oauth.gousa para estado de proxy OAuth. Em uma implantação hospedada com múltiplas réplicas, uma requisição roteada para um pod diferente de um anterior apenas paga por uma troca extra (agora duas chamadas:/oauth2/firebase-tokenesignInWithCustomToken) em uma falha de cache — não falha, ao contrário de umMAGICMARKETS_OAUTH_PROXY_SECRETnão compartilhado. Este é um workaround deliberado, não o estado final: um cache compartilhado (ou uma credencial documentada e de vida mais longa da Magic Markets) removeria o custo de cold-start por réplica inteiramente. - O TTL e o tamanho do cache são constantes fixas, não variáveis de ambiente (
magicmarkets.MeCacheTTL= 1 minuto; um tamanho de 4096 tokens de acesso distintos) — vejainternal/magicmarkets/meauth.go. Isso mantém o workaround simples enquanto o contrato/meainda está se firmando; reavalie quando valer a pena ajustar. MAGICMARKETS_SESSION_GROUP_IDeMAGICMARKETS_FIREBASE_WEB_API_KEYnão têm padrão seguro e diferem por ambiente — ambos devem ser definidos explicitamente onde quer que um chamador OAuth Bearer seja esperado, ou toda chamada autenticada por Bearer falha.- Sem renovação proativa de token. A resolução é tentada novamente a cada falha de cache, mas nada renova um token de acesso antes de expirar — um token expirado surge como uma troca falha (e, portanto, uma chamada de ferramenta falha), o mesmo que qualquer outra credencial inválida.
Fazendo uma alteração
Veja CONTRIBUTING.md para o fluxo de trabalho branch → código → check → PR, incluindo o que fazer se sua alteração tocar a especificação OpenAPI vendida.
Licença
MIT — veja LICENSE.
Mantido por Magic Markets.