API de Cronometragem
Documentação técnica para empresas de cronometragem integrarem com a ArenaBRticket: consulta de atletas inscritos e envio dos resultados da prova.
Introdução
A API de Cronometragem permite que sistemas de empresas parceiras consultem os atletas inscritos de um evento (com número de peito e chip/RFID) e enviem de volta os resultados da prova em lote.
Cada credencial é criada por um organizador, dentro do painel dele (menu Organizador → Empresas de Cronometragem), e só enxerga os eventos desse organizador específico — nunca dados de outro organizador na plataforma.
Autenticação
Toda requisição deve incluir o header X-Api-Key com a chave fornecida pelo organizador. A chave é exibida uma única vez no momento da criação — se for perdida, é preciso criar uma nova.
X-Api-Key: tck_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Cada credencial tem permissões independentes: read_athletes (consultar atletas), submit_results (enviar resultados) e assign_chips (associar chip/RFID a um número de peito). Uma chamada sem a permissão necessária recebe 403.
Ambientes
Homologação e produção são projetos completamente independentes — URLs e credenciais diferentes em cada um.
| Ambiente | URL base |
|---|---|
| Produção | https://auth.arenabrticket.com.br |
| Homologação | https://zowqyltqdbihtwtiboua.supabase.co |
Limites de Requisições
Consulta de atletas: até 500 registros por página (per_page, padrão 200).
Envio de resultados: até 1.000 itens por requisição.
Associação de chips: até 1.000 itens por requisição.
Taxa de requisições: 60 requisições por minuto por credencial (janela fixa de 1 minuto), padrão de mercado. Chamadas acima do limite recebem 429 até a janela seguinte começar.
Precisa de um limite maior para o seu volume de integração? Fale com o organizador que emitiu sua credencial para que ele solicite o ajuste junto à plataforma.
Versionamento
A API está na sua primeira versão, sem prefixo de versão na URL. Mudanças que quebrem compatibilidade serão anunciadas no changelog abaixo com antecedência antes de qualquer alteração.
Códigos de Erro
| Status | Código | Significado |
|---|---|---|
| 400 | BAD_REQUEST | Parâmetro obrigatório ausente ou inválido. |
| 401 | UNAUTHORIZED | Chave ausente, inválida, suspensa, revogada ou expirada. |
| 403 | UNAUTHORIZED | Chave válida, mas sem a permissão necessária. |
| 404 | NOT_FOUND | Evento não encontrado, ou não pertence ao organizador dono da chave. |
| 405 | METHOD_NOT_ALLOWED | Método HTTP errado (confira GET vs. POST). |
| 429 | - | Limite de requisições por minuto excedido para essa credencial. Aguarde a janela seguinte. |
| 503 | - | Serviço temporariamente desabilitado pela plataforma. |
| 500 | INTERNAL_ERROR | Erro inesperado — tente novamente; se persistir, contate o organizador. |
GET Consultar Atletas Inscritos
/functions/v1/external-api-timing-athletes — parâmetros de query: event_id (obrigatório), modalidade_id, page, per_page. Requer a permissão read_athletes.
curl -X GET "https://auth.arenabrticket.com.br/functions/v1/external-api-timing-athletes?event_id=SEU_EVENT_ID" \
-H "X-Api-Key: SUA_CHAVE_AQUI"
const res = await fetch(
"https://auth.arenabrticket.com.br/functions/v1/external-api-timing-athletes?event_id=SEU_EVENT_ID",
{ headers: { "X-Api-Key": "SUA_CHAVE_AQUI" } }
);
const data = await res.json();
console.log(data.athletes);
interface Athlete {
registration_id: string;
bib_number: string | null;
chip: string | null;
participant_name: string;
modality: { id: string; name: string; distance: string };
}
const res = await fetch(
`https://auth.arenabrticket.com.br/functions/v1/external-api-timing-athletes?event_id=${eventId}`,
{ headers: { "X-Api-Key": apiKey } }
);
const data: { athletes: Athlete[] } = await res.json();
<?php
$ch = curl_init("https://auth.arenabrticket.com.br/functions/v1/external-api-timing-athletes?event_id=SEU_EVENT_ID");
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-Api-Key: SUA_CHAVE_AQUI"]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
print_r($data["athletes"]);
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Api-Key", "SUA_CHAVE_AQUI");
var response = await client.GetAsync(
"https://auth.arenabrticket.com.br/functions/v1/external-api-timing-athletes?event_id=SEU_EVENT_ID"
);
var json = await response.Content.ReadAsStringAsync();
Console.WriteLine(json);
import requests
response = requests.get(
"https://auth.arenabrticket.com.br/functions/v1/external-api-timing-athletes",
params={"event_id": "SEU_EVENT_ID"},
headers={"X-Api-Key": "SUA_CHAVE_AQUI"},
)
data = response.json()
print(data["athletes"])
Resposta
{
"meta": {
"generated_at": "2026-07-20T12:00:00.000Z",
"event": { "id": "...", "name": "Corrida de Verão" },
"pagination": { "page": 1, "per_page": 200, "total_records": 1, "total_pages": 1, "has_next_page": false }
},
"athletes": [
{
"registration_id": "...",
"registration_number": "INS-0000-0000-2689",
"bib_number": "1023",
"chip": "CHIP-ABC123",
"participant_name": "Antonio Fagundes",
"document": null,
"modality": { "id": "...", "name": "Corrida 10km", "distance": "10km" }
}
]
}
POST Enviar Resultados
/functions/v1/external-api-timing-results — corpo com event_id e um array results (até 1.000 itens). Requer a permissão submit_results.
curl -X POST "https://auth.arenabrticket.com.br/functions/v1/external-api-timing-results" \
-H "X-Api-Key: SUA_CHAVE_AQUI" \
-H "Content-Type: application/json" \
-d '{
"event_id": "SEU_EVENT_ID",
"results": [
{
"bib_number": "1023",
"elapsed_time": "00:43:21",
"gun_time": "00:43:45",
"position": 15,
"category_position": 3,
"status": "FINISHER"
}
]
}'
const res = await fetch(
"https://auth.arenabrticket.com.br/functions/v1/external-api-timing-results",
{
method: "POST",
headers: {
"X-Api-Key": "SUA_CHAVE_AQUI",
"Content-Type": "application/json",
},
body: JSON.stringify({
event_id: "SEU_EVENT_ID",
results: [
{ bib_number: "1023", elapsed_time: "00:43:21", position: 15, status: "FINISHER" },
],
}),
}
);
const data = await res.json();
console.log(data.summary);
interface ResultInput {
bib_number?: string;
chip?: string;
elapsed_time?: string; // "HH:MM:SS"
gun_time?: string;
position?: number;
category_position?: number;
status?: "FINISHER" | "DNF" | "DNS" | "DSQ";
penalties?: Record<string, unknown>;
}
async function submitResults(eventId: string, results: ResultInput[]) {
const res = await fetch("https://auth.arenabrticket.com.br/functions/v1/external-api-timing-results", {
method: "POST",
headers: { "X-Api-Key": apiKey, "Content-Type": "application/json" },
body: JSON.stringify({ event_id: eventId, results }),
});
return res.json();
}
<?php
$payload = [
"event_id" => "SEU_EVENT_ID",
"results" => [
["bib_number" => "1023", "elapsed_time" => "00:43:21", "position" => 15, "status" => "FINISHER"],
],
];
$ch = curl_init("https://auth.arenabrticket.com.br/functions/v1/external-api-timing-results");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"X-Api-Key: SUA_CHAVE_AQUI",
"Content-Type: application/json",
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
print_r(json_decode($response, true));
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Api-Key", "SUA_CHAVE_AQUI");
var payload = new {
event_id = "SEU_EVENT_ID",
results = new[] {
new { bib_number = "1023", elapsed_time = "00:43:21", position = 15, status = "FINISHER" }
}
};
var content = new StringContent(
JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json"
);
var response = await client.PostAsync(
"https://auth.arenabrticket.com.br/functions/v1/external-api-timing-results", content
);
Console.WriteLine(await response.Content.ReadAsStringAsync());
import requests
payload = {
"event_id": "SEU_EVENT_ID",
"results": [
{"bib_number": "1023", "elapsed_time": "00:43:21", "position": 15, "status": "FINISHER"},
],
}
response = requests.post(
"https://auth.arenabrticket.com.br/functions/v1/external-api-timing-results",
json=payload,
headers={"X-Api-Key": "SUA_CHAVE_AQUI"},
)
print(response.json()["summary"])
Resposta
{
"batch_id": "...",
"processed_at": "2026-07-20T12:05:00.000Z",
"summary": { "total": 1, "matched": 1, "pending_review": 0, "errors": 0 },
"results": [{ "bib_number": "1023", "chip": null, "status": "matched" }]
}
Matching: cada item é casado por bib_number ou chip contra os inscritos do evento. Sem correspondência, o resultado fica marcado como pending_review pro organizador resolver manualmente — nunca é rejeitado nem descartado silenciosamente.
Correções: reenviar o mesmo bib_number no mesmo evento atualiza o resultado existente (idempotente). Envios só com chip (sem peito) sempre criam um novo registro — prefira sempre enviar o peito quando disponível.
POST Atribuir Chips
/functions/v1/external-api-timing-chip-assignments — corpo com event_id e um array assignments (até 1.000 itens). Requer a permissão assign_chips.
curl -X POST "https://auth.arenabrticket.com.br/functions/v1/external-api-timing-chip-assignments" \
-H "X-Api-Key: SUA_CHAVE_AQUI" \
-H "Content-Type: application/json" \
-d '{
"event_id": "SEU_EVENT_ID",
"assignments": [
{ "bib_number": "1023", "chip": "CHIP-ABC123" }
]
}'
const res = await fetch(
"https://auth.arenabrticket.com.br/functions/v1/external-api-timing-chip-assignments",
{
method: "POST",
headers: {
"X-Api-Key": "SUA_CHAVE_AQUI",
"Content-Type": "application/json",
},
body: JSON.stringify({
event_id: "SEU_EVENT_ID",
assignments: [{ bib_number: "1023", chip: "CHIP-ABC123" }],
}),
}
);
const data = await res.json();
console.log(data.summary);
interface ChipAssignment {
bib_number: string;
chip: string;
}
async function assignChips(eventId: string, assignments: ChipAssignment[]) {
const res = await fetch("https://auth.arenabrticket.com.br/functions/v1/external-api-timing-chip-assignments", {
method: "POST",
headers: { "X-Api-Key": apiKey, "Content-Type": "application/json" },
body: JSON.stringify({ event_id: eventId, assignments }),
});
return res.json();
}
<?php
$payload = [
"event_id" => "SEU_EVENT_ID",
"assignments" => [
["bib_number" => "1023", "chip" => "CHIP-ABC123"],
],
];
$ch = curl_init("https://auth.arenabrticket.com.br/functions/v1/external-api-timing-chip-assignments");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"X-Api-Key: SUA_CHAVE_AQUI",
"Content-Type: application/json",
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
print_r(json_decode($response, true));
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Api-Key", "SUA_CHAVE_AQUI");
var payload = new {
event_id = "SEU_EVENT_ID",
assignments = new[] {
new { bib_number = "1023", chip = "CHIP-ABC123" }
}
};
var content = new StringContent(
JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json"
);
var response = await client.PostAsync(
"https://auth.arenabrticket.com.br/functions/v1/external-api-timing-chip-assignments", content
);
Console.WriteLine(await response.Content.ReadAsStringAsync());
import requests
payload = {
"event_id": "SEU_EVENT_ID",
"assignments": [
{"bib_number": "1023", "chip": "CHIP-ABC123"},
],
}
response = requests.post(
"https://auth.arenabrticket.com.br/functions/v1/external-api-timing-chip-assignments",
json=payload,
headers={"X-Api-Key": "SUA_CHAVE_AQUI"},
)
print(response.json()["summary"])
Resposta
{
"batch_id": "...",
"processed_at": "2026-07-21T12:05:00.000Z",
"summary": { "total": 1, "updated": 1, "errors": 0 },
"results": [{ "bib_number": "1023", "chip": "CHIP-ABC123", "status": "updated" }]
}
Matching: cada item é casado só por bib_number — é o único identificador confiável nesse momento, já que o chip ainda não foi associado. Um BIB não encontrado no evento vira erro naquela linha, sem travar o restante do lote.
Quando usar: normalmente antes da prova, na entrega de kit ou na largada, assim que a empresa de cronometragem souber qual chip foi entregue a qual atleta. Depois disso o endpoint de Enviar Resultados passa a conseguir casar também por chip, não só por peito.
Webhooks
Se a credencial tiver uma URL de webhook cadastrada (HTTPS obrigatório), toda vez que um lote de resultados for processado enviamos uma notificação POST:
{
"event": "results.processed",
"sent_at": "2026-07-20T12:05:00.000Z",
"data": {
"batch_id": "...",
"summary": { "total": 1, "matched": 1, "pending_review": 0, "errors": 0 },
"results": [{ "bib_number": "1023", "chip": null, "status": "matched" }]
}
}
Se a primeira tentativa falhar (endpoint fora do ar, timeout, HTTP fora da faixa 2xx), a notificação entra numa fila de retentativa automática: novas tentativas com intervalo crescente (dobrando a cada vez, até um teto de 24h entre tentativas) por até 7 dias. Depois disso, se ainda não tiver sido entregue, a tentativa é encerrada.
Se as falhas persistirem, avisamos por e-mail o organizador e, se a credencial tiver um e-mail técnico cadastrado, também esse contato — tanto no aviso de falha persistente quanto no aviso final de encerramento após os 7 dias. O organizador pode acompanhar o histórico completo de cada entrega (incluindo o código HTTP e o erro de cada tentativa) e forçar um reenvio manual a qualquer momento pela tela de Auditoria de Webhooks no painel dele.
A resposta da chamada original de envio de resultados nunca é afetada por isso — a entrega do webhook (inclusive a fila de retentativa) é sempre assíncrona em relação à sua requisição.
Changelog
2026-07-21
Novo endpoint: Atribuir Chips (assign_chips) — associa chip/RFID a um número de peito antes da prova.
Webhooks passam a ter retentativa automática por até 7 dias, com aviso por e-mail em caso de falha persistente ou expiração.
2026-07-20
Lançamento: consulta de atletas, envio de resultados, webhooks e gestão de credenciais.