API BORA
A API BORA conecta o seu sistema ao BORA ACCESS, o aparelho que reconhece a palma da mão na catraca. Esta referência cobre as 16 rotas da prova de conceito: pessoas, consentimento, cadastro da palma, passagens, aparelhos e carteiro.
Para agentes de IA: use o llms.txt para ver o índice completo da documentação, ou o llms-full.txt para tudo em um arquivo.
Visão geral
O BORA ACCESS fica na catraca e reconhece a palma offline, na base local do aparelho. Ele não abre a catraca. Ele manda um veredito assinado pelo cabo USB ao validador da operadora, o DBD. O DBD decide (saldo, bloqueio, categoria) e aciona a catraca.
A API BORA é a parte na nuvem. O seu backend (o BDOS da operadora, um app ou um CRM) usa a API para cadastrar pessoas, registrar o consentimento, abrir o cadastro da palma, conciliar as passagens e levar registros entre a BORA e os aparelhos.
Esta referência cobre só as rotas de parceiro da prova de conceito. As rotas internas do aparelho (registro, galeria, envio de vereditos e cadastro pelo totem) não fazem parte dela. O aparelho fala com a BORA com credenciais próprias, nunca com a API key do parceiro.
Fluxo básico
- Crie a pessoa com o
external_refda conta do cliente. - Registre o consentimento da finalidade
identification. - Abra o cadastro e mostre o QR ou o código curto para o totem.
- Acompanhe o cadastro até
completed. - Consulte as passagens para conciliar.
- Faça o carteiro: busque a inbox de cada aparelho, entregue ao DBD e devolva a outbox.
- Apague a pessoa quando ela pedir (LGPD) e guarde o recibo.
Base URL
Todas as rotas começam com /v1. Corpo e respostas em JSON (UTF-8). Datas em RFC 3339, em UTC, com milissegundos.
| ambiente | URL | estado |
|---|---|---|
| Local | http://127.0.0.1:8710 | servidor de referência |
| Sandbox | https://sandbox.api.bora.id | em breve |
| Produção | https://api.bora.id | em breve |
Autenticação
Toda rota de parceiro exige o header Authorization com a sua API key:
Authorization: Bearer bora_test_<key_id>_<secret>A key tem o formato bora_<ambiente>_<key_id>_<secret>. Keys bora_test_ valem só no sandbox, com dados separados. Keys bora_live_ valem em produção. Hoje a key de teste é emitida pela equipe BORA, a pedido.
Cada key tem escopos, e cada rota diz o escopo que precisa: subjects:read, subjects:write, devices:read, devices:write e verdicts:read. As rotas GET /v1/health e GET /v1/keys são públicas.
Guarde a key numa variável de ambiente (BORA_API_KEY), nunca no código. Toda falha de credencial devolve o mesmo 401 unauthenticated, sem dizer o motivo.
curl "https://sandbox.api.bora.id/v1/subjects?limit=1" \
-H "Authorization: Bearer $BORA_API_KEY"import os
import requests
BASE = "https://sandbox.api.bora.id"
r = requests.get(
f"{BASE}/v1/subjects",
headers={
"Authorization": f"Bearer {os.environ['BORA_API_KEY']}",
},
params={
"limit": 1
},
timeout=10,
)
r.raise_for_status()
print(r.json())const BASE = "https://sandbox.api.bora.id";
const res = await fetch(`${BASE}/v1/subjects?limit=1`, {
headers: {
"Authorization": `Bearer ${process.env.BORA_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.code}`);
console.log(data);Erros
A API usa os códigos HTTP de sempre: 2xx é sucesso, 4xx é erro na requisição e 5xx é erro do servidor.
| status | significado |
|---|---|
200 | Deu certo. |
201 | Recurso criado. |
400 | Requisição malformada (JSON inválido, cursor inválido, parâmetro com formato errado). |
401 | Credencial ausente ou inválida. Sempre a mesma resposta, sem dizer o motivo (sem oráculo). |
403 | Autenticado, mas sem permissão (escopo, ambiente test/live, token de cadastro inválido). |
404 | Recurso inexistente ou de outro tenant (mesma resposta). |
409 | Conflito com o estado atual. |
410 | Recurso ou histórico fora da retenção. |
415 | Content-Type diferente de application/json. |
422 | JSON válido, mas fora do contrato. |
428 | POST sem Idempotency-Key. |
429 | Limite de taxa estourado. |
500 | Erro interno. Pode repetir com a mesma Idempotency-Key. |
503 | Serviço indisponível. |
Todo erro vem em application/problem+json (RFC 9457) com um code estável. Trate o erro pelo code. O detail é texto para humanos e pode mudar. Nenhum erro ecoa o valor que você mandou.
{
"type": "https://bora.id/transport/documentation#erro-external_ref_looks_like_cpf",
"title": "external_ref parece um CPF",
"status": 422,
"code": "external_ref_looks_like_cpf",
"detail": "Use o ID da conta no seu sistema; o CPF vai só no campo cpf.",
"instance": "req_01J9Z8X7W6VB",
"retryable": false,
"errors": [
{
"pointer": "/external_ref",
"code": "external_ref_looks_like_cpf"
}
]
}Limites de taxa
| quem | limite |
|---|---|
| API key, leitura | 100 req/s, rajada de 200 |
| API key, escrita | 20 req/s, rajada de 40 |
POST /v1/enrollment-sessions | 60 por minuto por tenant |
Toda resposta traz RateLimit-Limit, RateLimit-Remaining e RateLimit-Reset. O 429 traz Retry-After: espere esses segundos antes de tentar de novo. Os limites ainda são uma proposta e vão ser ajustados com carga real.
Códigos de erro
| code | status | quando | repetir? |
|---|---|---|---|
malformed_json | 400 | corpo não é JSON válido | não |
invalid_request | 400 | parâmetro de query ou header com formato errado | não |
invalid_cursor | 400 | cursor adulterado ou de outra lista | não |
unauthenticated | 401 | qualquer falha de credencial (key, assinatura, mTLS, relógio) | não |
forbidden | 403 | credencial válida sem acesso ao recurso (ex.: totem em rota de galeria) | não |
insufficient_scope | 403 | API key sem o escopo da rota | não |
livemode_mismatch | 403 | key test em recurso live ou o contrário | não |
enrollment_session_invalid | 403 | token de cadastro errado, usado, vencido ou de outro aparelho | não |
subject_not_found | 404 | pessoa inexistente, apagada ou de outro tenant | não |
device_not_found | 404 | aparelho inexistente ou de outro tenant | não |
verdict_not_found | 404 | veredito inexistente ou de outro tenant | não |
enrollment_session_not_found | 404 | sessão inexistente ou de outro tenant | não |
erasure_receipt_not_found | 404 | recibo inexistente ou de outro tenant | não |
external_ref_conflict | 409 | external_ref já usado no tenant | não |
cpf_conflict | 409 | CPF já ligado a outra pessoa ativa do tenant (sem dizer qual) | não |
subject_state_conflict | 409 | operação inválida no estado atual (ex.: unblock de quem não está bloqueado) | não |
enrollment_session_state_conflict | 409 | sessão não está aberta | não |
device_state_conflict | 409 | device_id ativo com outras chaves, ou chave já de outro aparelho | não |
idempotency_in_progress | 409 | a primeira requisição com a mesma chave ainda roda | sim |
history_expired | 410 | delta pedido fora da retenção; corpo traz snapshot_seq | não, refazer por snapshot |
cursor_expired | 410 | cursor fora da retenção | não |
payload_too_large | 413 | corpo acima do limite | não |
unsupported_media_type | 415 | Content-Type errado | não |
validation_failed | 422 | JSON fora do contrato; detalhes em errors[] | não |
external_ref_looks_like_cpf | 422 | external_ref carrega um CPF válido (11 dígitos seguidos, com ou sem separador) | não |
onboard_ref_looks_like_cpf | 422 | onboard_ref carrega um CPF válido (ele viaja no cabo e na base dos aparelhos) | não |
cpf_invalid | 422 | CPF com dígito verificador errado | não |
consent_required | 422 | operação exige consentimento vigente da finalidade | não |
model_unsupported | 422 | vetor de model_id que a galeria do tenant não usa | não |
idempotency_key_mismatch | 422 | mesma Idempotency-Key com corpo diferente | não |
idempotency_key_required | 428 | POST sem Idempotency-Key | não |
rate_limited | 429 | limite de taxa | sim, depois do Retry-After |
internal_error | 500 | erro interno | sim |
not_implemented | 501 | recurso planejado (webhooks no v1.0) | não |
service_unavailable | 503 | manutenção ou sobrecarga | sim, depois do Retry-After |
Idempotência
Todo POST exige o header Idempotency-Key. Use um UUID v4 novo para cada operação. Repita a mesma chave só quando reenviar a mesma operação.
- Mesma chave e mesmo corpo: a API devolve a resposta gravada, com
Idempotent-Replayed: true. - Mesma chave com corpo diferente:
422 idempotency_key_mismatch. - A primeira requisição ainda em andamento:
409 idempotency_in_progress. - Sem a chave:
428 idempotency_key_required. - Respostas
5xxnão são gravadas. Repita com a mesma chave.
A chave vale por 24 horas, por credencial, método e rota. Nos DELETE ela é opcional.
Idempotency-Key: 0f8b2c7e-4a1d-4b9e-8c3f-2d1e0a9b8c7dPaginação
As listas usam cursor opaco. Mande limit (de 1 a 200, padrão 50). Para a próxima página, mande cursor com o next_cursor da página anterior e repita os mesmos filtros.
{
"data": [{ "subject_id": "3f6c2a9e-8b1d-4e7a-9c3f-5d2e1a0b7c64", "status": "active" }],
"has_more": true,
"next_cursor": "eyJrIjoiZGVjaWRlZF9hdCIsInYiOjE3OTA4NTAwMDA4NDB9"
}Quando has_more vier false, a lista acabou. Cursor inválido dá 400 invalid_cursor. Cursor fora da retenção dá 410 cursor_expired. Não existe paginação por offset.
A inbox do carteiro é diferente: ela pagina por after_seq, o último seq que você entregou ao aparelho.
Pessoas
Uma pessoa nasce pelo external_ref, o ID da conta dela no seu sistema. A palma vem depois, no cadastro. O CPF é opcional, entra só no corpo e nunca volta numa resposta.
Cria uma pessoa pelo external_ref
/v1/subjectsCria a pessoa sem biometria (status pending_enrollment). O cadastro da palma vem depois por POST /v1/enrollment-sessions. cpf é opcional e só serve para KYC e deduplicação dentro do tenant: já existir outra pessoa ativa com o mesmo CPF dá 409 cpf_conflict (sem revelar qual).
Autenticação: API key com o escopo subjects:write.
Parâmetros
Chave única da operação. Use um UUID v4 novo por operação (veja Idempotência).
^[\x21-\x7E]+$.Id de correlação do cliente (ecoado na resposta). Se ausente, a BORA gera um.
^[A-Za-z0-9._:-]{1,64}$.Corpo da requisição application/json
ID da conta da pessoa no sistema do parceiro (matrícula, número da conta). Único por tenant. Nunca CPF.
^[A-Za-z0-9._:@-]{1,128}$.Opcional, só para KYC e dedup. Só dígitos. Nunca devolvido.
^[0-9]{11}$.Token opaco opcional do operador que viaja no veredito de accept (campo onboard_ref do BORA-LINK/1) e na base de todos os aparelhos. Não use CPF nem nome; um valor que carrega CPF válido dá 422 onboard_ref_looks_like_cpf.
^[A-Za-z0-9._:+/=-]{1,128}$.Respostas
| status | significado |
|---|---|
| 201 | Pessoa criada. |
| 400 | Requisição malformada (JSON inválido, cursor inválido, parâmetro com formato errado). |
| 401 | Credencial ausente ou inválida. Sempre a mesma resposta, sem dizer o motivo (sem oráculo). |
| 403 | Autenticado, mas sem permissão (escopo, ambiente test/live, token de cadastro inválido). |
| 409 | Conflito com o estado atual. |
| 415 | Content-Type diferente de application/json. |
| 422 | JSON válido, mas fora do contrato. |
| 428 | POST sem Idempotency-Key. |
| 429 | Limite de taxa estourado. |
| 500 | Erro interno. Pode repetir com a mesma Idempotency-Key. |
Lista pessoas (ou busca pelo external_ref)
/v1/subjectsCom external_ref, devolve 0 ou 1 item. Não existe busca por CPF (CPF nunca vai em URL).
Autenticação: API key com o escopo subjects:read.
Parâmetros
ID da conta da pessoa no sistema do parceiro (matrícula, número da conta). Único por tenant. Nunca CPF.
^[A-Za-z0-9._:@-]{1,128}$.pending_enrollment, active, blocked.50.Cursor opaco (next_cursor da página anterior).
^[A-Za-z0-9_=-]+$.Respostas
| status | significado |
|---|---|
| 200 | Página de pessoas. |
| 400 | Requisição malformada (JSON inválido, cursor inválido, parâmetro com formato errado). |
| 401 | Credencial ausente ou inválida. Sempre a mesma resposta, sem dizer o motivo (sem oráculo). |
| 403 | Autenticado, mas sem permissão (escopo, ambiente test/live, token de cadastro inválido). |
| 410 | Recurso ou histórico fora da retenção. |
| 429 | Limite de taxa estourado. |
Consulta uma pessoa
/v1/subjects/{subject_id}Autenticação: API key com o escopo subjects:read.
Parâmetros
Respostas
| status | significado |
|---|---|
| 200 | A pessoa. |
| 401 | Credencial ausente ou inválida. Sempre a mesma resposta, sem dizer o motivo (sem oráculo). |
| 403 | Autenticado, mas sem permissão (escopo, ambiente test/live, token de cadastro inválido). |
| 404 | Recurso inexistente ou de outro tenant (mesma resposta). |
| 429 | Limite de taxa estourado. |
Apaga a pessoa (LGPD) e devolve recibo assinado
/v1/subjects/{subject_id}Apagamento definitivo (LGPD art. 18, VI). Na nuvem, na hora: crypto-shred da DEK do template, remoção do HMAC e da cifra do CPF, do external_ref e do onboard_ref, tombstone no próximo delta e compactação do log de deltas (o vetor some também do histórico servido). Nos aparelhos: o tombstone chega pelo delta; aparelho offline aplica no próximo sync e, se passar do TTL de 6 h da galeria, para de reconhecer todo mundo (fail-closed). O recibo é assinado pela BORA (bora.erasure.receipt.v1) e o andamento da propagação fica em GET /v1/erasure-receipts/{receipt_id}. Depois do apagamento, GET da pessoa dá 404.
Na mesma transação saem também: o external_ref dos eventos da pessoa (e o subject_id dos eventos verdict.recorded), o vínculo dos vereditos com o subject_id (o filtro ?subject_id= deixa de achar as passagens dela), o onboard_ref dos deltas guardados e as respostas gravadas de Idempotency-Key que falavam dela.
O que fica, sem biometria:
- o recibo assinado e o pedido (auditoria do próprio apagamento);
- a prova assinada das passagens já registradas (
signed_verdict, com o pseudônimosubject_refe, se havia, oonboard_refque andaram no cabo), até o fim da retenção de vereditos (13 meses), porque a assinatura cobre esses bytes e reescrever quebraria a prova. Base legal proposta: conciliação tarifária e defesa em disputa (LGPD art. 16, I e art. 7º, VI), a validar com o jurídico. Quando há prova retida, o recibo diz isso emretained: [verdict_proofs].
Autenticação: API key com o escopo subjects:write.
Parâmetros
^[\x21-\x7E]+$.Respostas
| status | significado |
|---|---|
| 200 | Apagado na nuvem; recibo assinado e estado da propagação. |
| 401 | Credencial ausente ou inválida. Sempre a mesma resposta, sem dizer o motivo (sem oráculo). |
| 403 | Autenticado, mas sem permissão (escopo, ambiente test/live, token de cadastro inválido). |
| 404 | Recurso inexistente ou de outro tenant (mesma resposta). |
| 409 | Conflito com o estado atual. |
| 429 | Limite de taxa estourado. |
| 500 | Erro interno. Pode repetir com a mesma Idempotency-Key. |
Consentimento
Consentimento por finalidade (LGPD art. 11). Revogar identification suspende o reconhecimento na catraca.
Registra consentimento para uma finalidade
/v1/subjects/{subject_id}/consentsRegistra o consentimento coletado pelo parceiro (por exemplo, no app dele) com a evidência. O consentimento dado na tela do totem durante o cadastro é registrado pelo próprio POST /v1/enrollments. Repetir a mesma finalidade com consentimento vigente devolve o registro existente (200).
Autenticação: API key com o escopo subjects:write.
Parâmetros
Chave única da operação. Use um UUID v4 novo por operação (veja Idempotência).
^[\x21-\x7E]+$.Corpo da requisição application/json
Finalidade: enrollment (guardar a palma cadastrada), identification (reconhecer na catraca) ou dataset_collection (coleta opcional para treino). payment fica para o v2.
enrollment, identification, dataset_collection.device_screen, partner_app, paper.^[A-Za-z0-9._:-]{1,64}$.SHA-256 do artefato de evidência guardado pelo parceiro (termo assinado, log do app). A BORA não recebe o artefato.
^[0-9a-f]{64}$.Respostas
| status | significado |
|---|---|
| 200 | Já havia consentimento vigente para a finalidade; devolve o registro. |
| 201 | Consentimento registrado. |
| 401 | Credencial ausente ou inválida. Sempre a mesma resposta, sem dizer o motivo (sem oráculo). |
| 403 | Autenticado, mas sem permissão (escopo, ambiente test/live, token de cadastro inválido). |
| 404 | Recurso inexistente ou de outro tenant (mesma resposta). |
| 422 | JSON válido, mas fora do contrato. |
| 428 | POST sem Idempotency-Key. |
| 429 | Limite de taxa estourado. |
Revoga o consentimento de uma finalidade
/v1/subjects/{subject_id}/consents/{purpose}Efeitos: identification revogado suspende o reconhecimento (mesmo efeito do block); enrollment revogado apaga o template (crypto-shred) e a pessoa volta a pending_enrollment; dataset_collection revogado apaga as amostras coletadas para treino. Para apagar a pessoa inteira, use DELETE /v1/subjects/{subject_id}.
Autenticação: API key com o escopo subjects:write.
Parâmetros
Finalidade: enrollment (guardar a palma cadastrada), identification (reconhecer na catraca) ou dataset_collection (coleta opcional para treino). payment fica para o v2.
enrollment, identification, dataset_collection.^[\x21-\x7E]+$.Respostas
| status | significado |
|---|---|
| 200 | Consentimento revogado. |
| 401 | Credencial ausente ou inválida. Sempre a mesma resposta, sem dizer o motivo (sem oráculo). |
| 403 | Autenticado, mas sem permissão (escopo, ambiente test/live, token de cadastro inválido). |
| 404 | Recurso inexistente ou de outro tenant (mesma resposta). |
| 429 | Limite de taxa estourado. |
Cadastro
O cadastro da palma acontece no totem ou no BORA ACCESS. Você abre a sessão, mostra o QR ou o código curto para o aparelho e acompanha o resultado.
Abre um cadastro de palma
/v1/enrollment-sessionsAbre uma sessão de cadastro para uma pessoa já criada. Devolve um token de uso único e um qr_payload para o totem ou o BORA ACCESS ler. Aparelho sem leitor de QR: o operador digita o short_code e o aparelho troca o código pelo token em POST /v1/enrollment-sessions/redeem. O token aparece só nesta resposta, a BORA guarda só o hash dele. O aparelho conclui com POST /v1/enrollments. O consentimento é mostrado e aceito na tela do totem antes da captura. Limite: 60 sessões por minuto por tenant.
Autenticação: API key com o escopo subjects:write.
Parâmetros
Chave única da operação. Use um UUID v4 novo por operação (veja Idempotência).
^[\x21-\x7E]+$.Corpo da requisição application/json
ID da conta da pessoa no sistema do parceiro (matrícula, número da conta). Único por tenant. Nunca CPF.
^[A-Za-z0-9._:@-]{1,128}$.L, R. De 1 a 2 itens. Padrão: ["L", "R"].Restringe a sessão a um aparelho. Sem ele, qualquer totem ou BORA ACCESS do tenant que ler o token conclui.
^[A-Za-z0-9._:-]{1,64}$.300.Pede também o consentimento separado de coleta para treino (opt-in na tela do totem).
false.Respostas
| status | significado |
|---|---|
| 201 | Sessão aberta. |
| 400 | Requisição malformada (JSON inválido, cursor inválido, parâmetro com formato errado). |
| 401 | Credencial ausente ou inválida. Sempre a mesma resposta, sem dizer o motivo (sem oráculo). |
| 403 | Autenticado, mas sem permissão (escopo, ambiente test/live, token de cadastro inválido). |
| 404 | Recurso inexistente ou de outro tenant (mesma resposta). |
| 409 | Conflito com o estado atual. |
| 422 | JSON válido, mas fora do contrato. |
| 428 | POST sem Idempotency-Key. |
| 429 | Limite de taxa estourado. |
Consulta o estado de um cadastro
/v1/enrollment-sessions/{session_id}Nunca devolve o token. Falha de captura aparece só como capture_rejected (sem dizer se foi qualidade ou liveness, para não virar oráculo de ataque de apresentação).
Autenticação: API key com o escopo subjects:read.
Parâmetros
Respostas
| status | significado |
|---|---|
| 200 | Estado da sessão. |
| 401 | Credencial ausente ou inválida. Sempre a mesma resposta, sem dizer o motivo (sem oráculo). |
| 403 | Autenticado, mas sem permissão (escopo, ambiente test/live, token de cadastro inválido). |
| 404 | Recurso inexistente ou de outro tenant (mesma resposta). |
| 429 | Limite de taxa estourado. |
Passagens
Cada passagem é um veredito assinado pelo aparelho, com a decisão do DBD quando ela chega. Use para conciliar com o BDOS e para contestar cobranças.
Log de passagens
/v1/verdictsVereditos que os aparelhos subiram (POST /v1/verdicts/batch), já resolvidos para subject_id e external_ref (só em accept), com a decisão e a passagem do DBD quando chegaram. Ordem padrão: mais recente primeiro por decided_at. Retenção: 13 meses (proposta).
Autenticação: API key com o escopo verdicts:read.
Parâmetros
^[A-Za-z0-9._:-]{1,64}$.^[A-Za-z0-9._:-]{1,64}$.Decisão de identidade do BORA ACCESS.
accept, reject, abstain.complete = veredito, decisão e (se a catraca tem sensor de giro, passage_sensor do aparelho) passagem conciliados; awaiting_decision = veredito sem decisão do DBD ainda; awaiting_passage = acionou numa catraca com sensor e falta o giro (sem sensor, acionar já dá complete); outcome_unknown = o aparelho ficou em OUTCOME_UNKNOWN (decisão perdida); conflict = objetos assinados que não fecham entre si (ex.: decisão apontando para outro verdict_ref, LIBERAR sem actuated, lacuna de verdict_seq).
complete, awaiting_decision, awaiting_passage, outcome_unknown, conflict.Início inclusivo (decided_at).
Fim exclusivo (decided_at).
desc, asc. Padrão: "desc".50.Cursor opaco (next_cursor da página anterior).
^[A-Za-z0-9_=-]+$.Respostas
| status | significado |
|---|---|
| 200 | Página de vereditos. |
| 400 | Requisição malformada (JSON inválido, cursor inválido, parâmetro com formato errado). |
| 401 | Credencial ausente ou inválida. Sempre a mesma resposta, sem dizer o motivo (sem oráculo). |
| 403 | Autenticado, mas sem permissão (escopo, ambiente test/live, token de cadastro inválido). |
| 410 | Recurso ou histórico fora da retenção. |
| 429 | Limite de taxa estourado. |
Veredito com prova assinada
/v1/verdicts/{verdict_id}Devolve o SignedVerdict exato que o aparelho mandou ao DBD, a SignedDecision e a SignedPassage do DBD (quando chegaram) e o material para verificar fora da BORA: chaves públicas e prefixos de domínio. Serve para contestação de cobrança e conciliação com o BDOS.
Autenticação: API key com o escopo verdicts:read.
Parâmetros
O verdict_ref do BORA-LINK/1.
^[0-9a-f]{32}$.Respostas
| status | significado |
|---|---|
| 200 | Prova. |
| 401 | Credencial ausente ou inválida. Sempre a mesma resposta, sem dizer o motivo (sem oráculo). |
| 403 | Autenticado, mas sem permissão (escopo, ambiente test/live, token de cadastro inválido). |
| 404 | Recurso inexistente ou de outro tenant (mesma resposta). |
| 429 | Limite de taxa estourado. |
Aparelhos
Os aparelhos do seu tenant: o BORA ACCESS na catraca e os totens de cadastro. Revogar mata as chaves do aparelho.
Lista os aparelhos do tenant
/v1/devicesAutenticação: API key com o escopo devices:read.
Parâmetros
active, revoked, wiped.^[A-Za-z0-9._:-]{1,64}$.access, totem.50.Cursor opaco (next_cursor da página anterior).
^[A-Za-z0-9_=-]+$.Respostas
| status | significado |
|---|---|
| 200 | Página de aparelhos. |
| 400 | Requisição malformada (JSON inválido, cursor inválido, parâmetro com formato errado). |
| 401 | Credencial ausente ou inválida. Sempre a mesma resposta, sem dizer o motivo (sem oráculo). |
| 403 | Autenticado, mas sem permissão (escopo, ambiente test/live, token de cadastro inválido). |
| 429 | Limite de taxa estourado. |
Revoga um aparelho (kill remoto)
/v1/devices/{device_id}/revokeMata as chaves do aparelho (o kill é da chave, não do nome). Na próxima consulta a GET /v1/devices/self/status o aparelho recebe wipe assinado e apaga galeria, DEKs e seeds; depois disso toda rota de aparelho responde 401 para essas chaves. Com o DBD offline, a revogação chega pelo trust.bundle (v1.1). Irreversível para estas chaves: para pôr o aparelho de volta, o admin BORA emite um token de provisionamento novo para o MESMO device_id e o aparelho se registra com chaves novas (as antigas ficam arquivadas, só para verificar vereditos antigos).
Autenticação: API key com o escopo devices:write.
Parâmetros
Id do aparelho. self, register e attest são reservados.
^[A-Za-z0-9._:-]{1,64}$.Chave única da operação. Use um UUID v4 novo por operação (veja Idempotência).
^[\x21-\x7E]+$.Corpo da requisição application/json
lost, stolen, decommissioned, compromised, other.Respostas
| status | significado |
|---|---|
| 200 | Aparelho revogado. |
| 401 | Credencial ausente ou inválida. Sempre a mesma resposta, sem dizer o motivo (sem oráculo). |
| 403 | Autenticado, mas sem permissão (escopo, ambiente test/live, token de cadastro inválido). |
| 404 | Recurso inexistente ou de outro tenant (mesma resposta). |
| 422 | JSON válido, mas fora do contrato. |
| 428 | POST sem Idempotency-Key. |
| 429 | Limite de taxa estourado. |
Carteiro
O carteiro leva registros entre a BORA e o aparelho pelo caminho que já existe: API BORA, BDOS, DBD e cabo USB. A BORA sela cada registro para o aparelho, e o aparelho sela para a BORA. Quem leva não consegue abrir, forjar nem escolher o que leva.
Registros selados para levar ao aparelho
/v1/devices/{device_id}/inboxCarteiro, ida. Registros que a BORA selou para este aparelho: atualização da base, revogações, config assinada, trust.bundle e atualizações de app ou modelo. O BDOS busca aqui e entrega ao DBD, que oferece ao aparelho pelo cabo (file.offer kind post.in). Nem o BDOS nem o DBD conseguem abrir, forjar ou escolher o que levam: o conteúdo vai cifrado para a chave de selo do aparelho e assinado pela BORA. Peça sempre a partir do último seq entregue (after_seq); o aparelho aplica em ordem estrita.
Autenticação: API key com o escopo devices:read.
Parâmetros
Id do aparelho. self, register e attest são reservados.
^[A-Za-z0-9._:-]{1,64}$.Devolve só registros com seq maior que este. Padrão 0 (desde o início).
0.50.Respostas
| status | significado |
|---|---|
| 200 | Registros pendentes para o aparelho, em ordem de seq. |
| 401 | Credencial ausente ou inválida. Sempre a mesma resposta, sem dizer o motivo (sem oráculo). |
| 403 | Autenticado, mas sem permissão (escopo, ambiente test/live, token de cadastro inválido). |
| 404 | Recurso inexistente ou de outro tenant (mesma resposta). |
| 422 | JSON válido, mas fora do contrato. |
| 429 | Limite de taxa estourado. |
Entrega registros selados que saíram do aparelho
/v1/devices/{device_id}/outboxCarteiro, volta. O aparelho sela para a BORA os vereditos, recibos e eventos (post.out); o DBD puxa pelo cabo e o BDOS entrega aqui do jeito que recebeu, sem abrir. A BORA confere a assinatura do aparelho, o destino e o sha256 de cada registro e guarda. acked_upto_seq é o maior seq contíguo já recebido: o recibo assinado (bora.post.receipt) volta pela caixa de entrada e é ele que libera o aparelho para apagar a cópia local. Reenviar o mesmo registro é seguro (sai em duplicates).
Autenticação: API key com o escopo devices:write.
Parâmetros
Id do aparelho. self, register e attest são reservados.
^[A-Za-z0-9._:-]{1,64}$.Chave única da operação. Use um UUID v4 novo por operação (veja Idempotência).
^[\x21-\x7E]+$.Corpo da requisição application/json
Base64 padrão com padding.
^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$."ed25519".Hash curto: 16 bytes em 32 hex minúsculos (txn_id, verdict_ref, prev_ref, subject_ref, kid, *16).
^[0-9a-f]{32}$.Assinatura Ed25519 de 64 bytes em base64 padrão (88 caracteres). Verificação estrita: S < L e ponto canônico.
^[A-Za-z0-9+/]{86}==$.Certificado de 104 B de uma chave emissora BORA pela raiz, no formato do prelúdio do trust.bundle (7.6): signer_pub(32) | signer_not_after u64 BE | root_sig(64). O prefixo do root_sig separa a finalidade: "bora.bundle.signer.v1" para a chave de bundle (7.6) e "bora.post.signer.v1" para a chave do carteiro, que assina manifesto de file.* e registro selado box in (4.8.10); root_sig sobre prefixo || 0x00 || signer_pub || not_after. Em base64 (140 caracteres).
^[A-Za-z0-9+/]{139}=$.Respostas
| status | significado |
|---|---|
| 200 | Resultado por registro. |
| 401 | Credencial ausente ou inválida. Sempre a mesma resposta, sem dizer o motivo (sem oráculo). |
| 403 | Autenticado, mas sem permissão (escopo, ambiente test/live, token de cadastro inválido). |
| 404 | Recurso inexistente ou de outro tenant (mesma resposta). |
| 422 | JSON válido, mas fora do contrato. |
| 428 | POST sem Idempotency-Key. |
| 429 | Limite de taxa estourado. |
Sistema
Rotas públicas, sem API key: o estado do serviço e as chaves públicas que a BORA usa para assinar recibos e registros.
Estado do serviço
/v1/healthRota pública, sem API key.
Respostas
| status | significado |
|---|---|
| 200 | Serviço no ar. |
| 503 | Serviço indisponível. |
Chaves públicas de assinatura da BORA
/v1/keysChaves Ed25519 que a BORA usa para assinar recibos de apagamento, cabeças e deltas de galeria, status de aparelho e webhooks. O cliente deve fixar (pin) estas chaves por kid e aceitar rotação só quando a nova chave aparecer aqui antes do not_before.
Rota pública, sem API key.
Respostas
| status | significado |
|---|---|
| 200 | Lista de chaves. |