--- name: bora-api description: Integra a API BORA (autenticação pela palma da mão em catracas de transporte) no backend de uma operadora ou parceiro. Use quando o usuário pedir para cadastrar pessoas na BORA, registrar consentimento, abrir o cadastro da palma (QR ou código curto), consultar passagens para conciliação, levar registros do carteiro (inbox e outbox) ao DBD, revogar aparelhos ou apagar uma pessoa a pedido (LGPD). --- # API BORA A BORA faz autenticação pela palma da mão em catracas de transporte. O BORA ACCESS fica na catraca, reconhece a palma offline e manda um veredito assinado pelo cabo USB ao validador da operadora, o DBD, que decide e abre a catraca. A API BORA é a parte na nuvem. Esta skill ensina a integrar as 16 operações da prova de conceito. ## Fontes - Referência completa em Markdown: https://docs.bora.id/llms-full.txt - Contrato OpenAPI 3.1: https://docs.bora.id/openapi.json - Índice curto: https://docs.bora.id/llms.txt Leia o contrato antes de escrever código. Não invente rota, campo nem código de erro. ## Conexão - Sandbox: `https://sandbox.api.bora.id` (em breve). Até lá, use a URL que a equipe BORA passar. - Produção: `https://api.bora.id` (em breve). - Header: `Authorization: Bearer $BORA_API_KEY`. Key de teste no formato `bora_test__`. - A key de teste é emitida pela equipe BORA, a pedido. Leia a key de variável de ambiente, nunca do código. ## Fluxo 1. Criar a pessoa com o `external_ref` da conta do cliente: `POST /v1/subjects`. O `external_ref` é o ID da conta no meu sistema, nunca o CPF. O CPF é opcional e vai só no corpo. 2. Registrar o consentimento da finalidade de acesso: `POST /v1/subjects/{subject_id}/consents` com `purpose` igual a `identification` e a evidência do aceite (`method`, `terms_version`, `accepted_at`, `evidence_sha256`). 3. Abrir a sessão de cadastro: `POST /v1/enrollment-sessions`. Mostrar o `qr_payload` como QR code para o totem ler, ou o `short_code` para digitar no totem. O `token` só aparece nessa resposta. Não guarde o token em log. 4. Acompanhar o estado do cadastro: `GET /v1/enrollment-sessions/{session_id}` até `completed`. Trate também `expired`, `cancelled`, `failed` e `review`. 5. Consultar as passagens para conciliação: `GET /v1/verdicts`, com filtros (`gate_id`, `device_id`, `from`, `to`, `reconciliation`) e paginação por cursor. A prova assinada de uma passagem está em `GET /v1/verdicts/{verdict_id}`. 6. Carteiro: para cada aparelho (`GET /v1/devices`), buscar `GET /v1/devices/{device_id}/inbox?after_seq=<último seq entregue>` e entregar os registros ao DBD do jeito que vieram. O que o DBD puxar do aparelho, devolver em `POST /v1/devices/{device_id}/outbox`, sem abrir nem alterar. Guarde o último `seq` entregue de cada aparelho. 7. Apagar a pessoa a pedido (LGPD): `DELETE /v1/subjects/{subject_id}`. Guarde o recibo assinado que volta na resposta. ## Operações | operação | rota | escopo | grupo | |---|---|---|---| | `createSubject` | `POST /v1/subjects` | `subjects:write` | Pessoas | | `listSubjects` | `GET /v1/subjects` | `subjects:read` | Pessoas | | `getSubject` | `GET /v1/subjects/{subject_id}` | `subjects:read` | Pessoas | | `eraseSubject` | `DELETE /v1/subjects/{subject_id}` | `subjects:write` | Pessoas | | `grantConsent` | `POST /v1/subjects/{subject_id}/consents` | `subjects:write` | Consentimento | | `revokeConsent` | `DELETE /v1/subjects/{subject_id}/consents/{purpose}` | `subjects:write` | Consentimento | | `createEnrollmentSession` | `POST /v1/enrollment-sessions` | `subjects:write` | Cadastro | | `getEnrollmentSession` | `GET /v1/enrollment-sessions/{session_id}` | `subjects:read` | Cadastro | | `listVerdicts` | `GET /v1/verdicts` | `verdicts:read` | Passagens | | `getVerdict` | `GET /v1/verdicts/{verdict_id}` | `verdicts:read` | Passagens | | `listDevices` | `GET /v1/devices` | `devices:read` | Aparelhos | | `revokeDevice` | `POST /v1/devices/{device_id}/revoke` | `devices:write` | Aparelhos | | `listDeviceInbox` | `GET /v1/devices/{device_id}/inbox` | `devices:read` | Carteiro | | `submitDeviceOutbox` | `POST /v1/devices/{device_id}/outbox` | `devices:write` | Carteiro | | `getHealth` | `GET /v1/health` | pública | Sistema | | `listSigningKeys` | `GET /v1/keys` | pública | Sistema | ## Regras - Nunca ponha CPF em URL, query string ou log. - Mande `Idempotency-Key` (um UUID v4 novo) em todo POST. Ao repetir a mesma operação, repita a mesma chave. - Se vier `429`, espere os segundos do header `Retry-After` antes de tentar de novo. Em `5xx`, tente de novo com espera crescente e a mesma `Idempotency-Key`. - Os erros vêm em `application/problem+json` com um `code` estável. Trate pelo `code`, nunca pelo texto do `detail`. - No sandbox, use só a key de teste. Nunca use uma key `bora_live_` em teste. - Ignore campos desconhecidos nas respostas. A API só cresce com campos novos. - Não chame rotas internas do aparelho (registro, galeria, envio de vereditos). Elas não são do parceiro. ## Erros comuns | code | o que fazer | |---|---| | `external_ref_looks_like_cpf` (422) | Use o ID da conta no seu sistema, não o CPF. | | `external_ref_conflict` (409) | Já existe pessoa com esse `external_ref`. Busque com `GET /v1/subjects?external_ref=`. | | `cpf_conflict` (409) | O CPF já está ligado a outra pessoa ativa do tenant. A API não diz qual. | | `idempotency_key_required` (428) | Mande `Idempotency-Key` no POST. | | `idempotency_key_mismatch` (422) | Mesma chave com corpo diferente. Gere uma chave nova para uma operação nova. | | `insufficient_scope` (403) | A key não tem o escopo da rota. | | `unauthenticated` (401) | Confira a key e o ambiente (test ou live). A resposta não diz o motivo. | | `rate_limited` (429) | Espere os segundos do `Retry-After`. |