ArenaBRticket · API de Cronometragem
ArenaBRticket · Documentação Técnica

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.

AmbienteURL base
Produçãohttps://auth.arenabrticket.com.br
Homologaçãohttps://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

StatusCódigoSignificado
400BAD_REQUESTParâmetro obrigatório ausente ou inválido.
401UNAUTHORIZEDChave ausente, inválida, suspensa, revogada ou expirada.
403UNAUTHORIZEDChave válida, mas sem a permissão necessária.
404NOT_FOUNDEvento não encontrado, não pertence ao organizador dono da chave, ou fora do escopo de eventos autorizados para essa credencial.
405METHOD_NOT_ALLOWEDMé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.
500INTERNAL_ERRORErro 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.