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 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.
BIB (número de peito) é a referência preferida, quando existe. O organizador pode atribuir um bib_number único por atleta dentro do evento, quando isso está configurado, é esse valor que identifica o atleta em toda a API. Só que a atribuição de BIB é opcional: se o organizador não configurou nenhuma faixa de peito para a modalidade, todo item da consulta de atletas vem com bib_number: null, e é esperado ser assim, não é erro nem falta de dado. Nesse caso, use registration_number (ver nota abaixo).
Chip/RFID é opcional. Nem toda empresa de cronometragem trabalha com chip, a plataforma funciona 100% sem ele. O campo chip só existe como atalho extra para quem usa leitura por RFID; não presuma que ele vai estar preenchido, e nunca construa sua integração dependendo só dele.
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.
Toda credencial é restrita a eventos específicos e tem validade obrigatória (no máximo 1 ano). O organizador escolhe, na criação, quais eventos aquela credencial pode acessar, uma chamada com event_id fora dessa lista recebe 404, mesmo sendo um evento válido do mesmo organizador. Se a sua integração precisar de um evento novo, ou a validade estiver perto de vencer, o organizador ajusta isso no painel dele sem precisar gerar uma chave nova, a credencial que você já configurou continua a mesma.
Use o endpoint Descobrir Eventos Autorizados para saber, a qualquer momento, exatamente quais event_id a sua credencial pode usar, não presuma isso por fora.
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, não pertence ao organizador dono da chave, ou fora do escopo de eventos autorizados para essa credencial. |
| 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 Descobrir Eventos Autorizados
/functions/v1/external-api-timing-events, sem parâmetros. Devolve os eventos que a sua credencial pode acessar. Requer a permissão read_athletes.
Chame esse endpoint antes de qualquer outro, é a forma correta de saber quais event_id usar nas próximas chamadas, sem precisar que o organizador te passe isso por fora (e-mail, planilha etc.). A lista reflete exatamente o escopo configurado pelo organizador no momento da chamada.
curl -X GET "https://auth.arenabrticket.com.br/functions/v1/external-api-timing-events" \
-H "X-Api-Key: SUA_CHAVE_AQUI"
const res = await fetch(
"https://auth.arenabrticket.com.br/functions/v1/external-api-timing-events",
{ headers: { "X-Api-Key": "SUA_CHAVE_AQUI" } }
);
const data = await res.json();
console.log(data.events);
interface AuthorizedEvent {
id: string;
name: string;
date: string;
city: string;
state: string;
}
const res = await fetch(
"https://auth.arenabrticket.com.br/functions/v1/external-api-timing-events",
{ headers: { "X-Api-Key": apiKey } }
);
const data: { events: AuthorizedEvent[] } = await res.json();
<?php
$ch = curl_init("https://auth.arenabrticket.com.br/functions/v1/external-api-timing-events");
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["events"]);
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-events"
);
var json = await response.Content.ReadAsStringAsync();
Console.WriteLine(json);
import requests
response = requests.get(
"https://auth.arenabrticket.com.br/functions/v1/external-api-timing-events",
headers={"X-Api-Key": "SUA_CHAVE_AQUI"},
)
data = response.json()
print(data["events"])
Resposta
{
"meta": { "generated_at": "2026-08-17T12:00:00.000Z" },
"events": [
{ "id": "...", "name": "Corrida de Verão", "date": "2026-12-13", "city": "Peruíbe", "state": "SP" }
]
}
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"
},
{
"chip": "CHIP-9981",
"elapsed_time": "00:51:07",
"position": 42,
"status": "FINISHER",
"participant_name": "Maria Souza",
"participant_nascimento": "1990-04-12",
"participant_genero": "F"
}
]
}'
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;
modalidade?: string; // nome da modalidade -- ver nota "Modalidade" abaixo
elapsed_time?: string; // "HH:MM:SS"
gun_time?: string;
position?: number;
category_position?: number;
status?: "FINISHER" | "DNF" | "DNS" | "DSQ";
penalties?: Record<string, unknown>;
// Só necessário quando o item NÃO casa com peito/chip nenhum -- ver nota
// "Participante sem correspondência" abaixo.
participant_name?: string;
participant_nascimento?: string; // "YYYY-MM-DD"
participant_genero?: string;
}
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" }]
}
Nem todo evento usa BIB. A numeração de peito só existe se o organizador configurar uma faixa na modalidade dentro da plataforma, muitos eventos não fazem isso. Para esse caso existe o registration_number: é o mesmo valor devolvido em registration_number na resposta de Consultar Atletas (ex: "INS-0000-0000-2701"), e ele sempre existe, em toda inscrição, sem depender de nenhuma configuração. Se o evento que você está integrando não mostra bib_number para ninguém na consulta de atletas, use registration_number no envio de resultados no lugar dele.
Matching: cada item é casado primeiro por bib_number; se não vier, tenta chip; se também não vier (ou não bater), tenta registration_number. Sem correspondência por nenhum dos três, o resultado fica marcado como pending_review para o organizador resolver manualmente, nunca é rejeitado nem descartado silenciosamente.
Idempotência: bib_number e registration_number têm o mesmo comportamento. Reenviar o mesmo valor (de qualquer um dos dois) no mesmo evento atualiza o resultado existente, nunca duplica, cada um tem sua própria trava de unicidade por evento. Envios só com chip (sem BIB nem registration_number) sempre criam um novo registro em vez de atualizar, já que chip sozinho não tem trava de duplicidade.
Modalidade: por padrão a modalidade do resultado é herdada da inscrição feita na plataforma. Se no dia da prova o atleta trocou de bateria/categoria (remanejamento, troca de largada etc.), envie modalidade no item com o nome da modalidade (ex: "21 KM"), o valor que você mandar sempre prevalece sobre o que está na inscrição, porque quem sabe o que realmente aconteceu na pista é a cronometragem. É por nome (texto) e não por id de propósito: a amarração com o atleta já é feita pelo peito/chip, então pedir um id nosso só criaria risco de erro de digitação invisível. O nome precisa bater (sem diferenciar maiúsculas/minúsculas) com alguma modalidade deste evento, use o mesmo texto que aparece em modality.name na resposta de Consultar Atletas; se não encontrar correspondência, o item volta como erro.
Participante sem correspondência: quando um item não casa com nenhum peito/chip da plataforma (pessoa sem inscrição aqui, substituição de última hora, kit repassado etc.), envie participant_name e participant_nascimento (formato "YYYY-MM-DD"), e participant_genero se disponível. Sem esses campos, o item fica em pending_review sem nome, sem categoria e sem idade, e a única opção do organizador é descartar. Com eles, o organizador pode publicar o resultado como participante externo mesmo sem vínculo com nenhuma inscrição da plataforma, de novo, propositalmente sem nenhum id nosso envolvido, só os dados que a própria cronometragem já coleta no local da prova.
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-08-17 (2)
Novo campo em Enviar Resultados: registration_number, identificador alternativo para eventos onde o organizador não configurou BIB. Sempre disponível (vem em registration_number na resposta de Consultar Atletas), com a mesma idempotência do bib_number (reenviar atualiza, nunca duplica).
2026-08-17
Novo endpoint: Descobrir Eventos Autorizados (GET /external-api-timing-events), consulte a qualquer momento quais eventos a sua credencial pode acessar.
Toda credencial nova passa a ser restrita a eventos específicos escolhidos pelo organizador (antes acessava todos os eventos dele automaticamente) e exige validade obrigatória, de no máximo 1 ano. Credenciais emitidas antes desta mudança continuam funcionando exatamente como antes, sem restrição de evento nem expiração.
Uma chamada com event_id fora do escopo autorizado da credencial passa a receber 404.
2026-08-15
Melhorias e pequenos ajustes em alguns campos do envio de resultados.
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.