API Web — contratos completos
Esta é a página de referência da API Web: contrato HTTP completo dos cinco endpoints, com campos, tipos, respostas de sucesso e todos os códigos de erro. Para o passo a passo de integração, comece pela Integração Web.
No fluxo do Web SDK, você chama diretamente POST /web/sessions (criar a sessão) e, quando precisar renovar uma URL expirada, GET /web/image/:session_id. O resultado chega inline no evento uploaded do componente — não é preciso consultar. O GET /web/session-results/:token é um endpoint de leitura backend (reconciliação; uso do Hosted Flow futuro), documentado abaixo mas não necessário no fluxo inline. Os endpoints GET /web/sessions/:token e POST /web/ingest são chamados internamente pelo componente <provvi-camera>; estão aqui só para você interpretar os erros que chegam no evento error.
Base URL
Seção intitulada “Base URL”Todas as chamadas usam o mesmo host:
https://sessions.provvi.com.brNos exemplos de backend abaixo, esse host é referenciado pela variável de ambiente PROVVI_SESSIONS_URL. Configure-a no seu backend em vez de fixar a URL no código.
export PROVVI_SESSIONS_URL="https://sessions.provvi.com.br"A API de licenciamento (
POST /validate) vive em outro host (https://api.provvi.com.br) e é chamada pelo SDK, não pelo seu backend. Ela não serve as rotas/web/*.
Autenticação
Seção intitulada “Autenticação”Há dois modos de autenticação, conforme o endpoint:
| Modo | Onde | Endpoints |
|---|---|---|
Header x-api-key: pvv_api_... |
Backend do integrador (nunca no frontend) | POST /web/sessions |
Token/session_id no path (capability) |
Backend ou componente | GET /web/session-results/:token, GET /web/image/:session_id, GET /web/sessions/:token |
A x-api-key é uma credencial de alto privilégio — obtenha-a no Admin Console e mantenha-a apenas no backend.
Os endpoints de consulta usam o token da sessão como capability: não há autenticação forte adicional. Quem tem o token pode ler os resultados daquela sessão — portanto trate o token como um segredo e não o exponha em URLs públicas, logs ou clientes não confiáveis.
Envelope de erro comum
Seção intitulada “Envelope de erro comum”Toda resposta de erro traz um corpo JSON com o campo error. Alguns erros de captura acrescentam campos auxiliares:
{ "error": "Descrição legível do erro", "block_reason": "opcional — presente em alguns bloqueios de captura", "recapture_risk": "opcional — presente quando a rejeição é por recaptura"}Os corpos literais de cada código estão nas tabelas de erro de cada endpoint.
TTLs e limites
Seção intitulada “TTLs e limites”| Recurso | Valor | Efeito ao estourar |
|---|---|---|
Criação de sessões por x-api-key |
100 por hora | 429 em POST /web/sessions |
| Recarregamentos por sessão | 20 | 429 em GET /web/sessions/:token |
| Validade do token de sessão | 30 minutos | 410 (sessão expirada) |
| Validade da URL presigned de upload | 5 minutos | Upload falha; recarregar a sessão |
| Validade das URLs de resultado/asset | 7 dias | Renove com GET /web/image/:session_id |
Sessões são single-use: uma vez concluída ou expirada, o token não pode ser reutilizado (410).
POST /web/sessions
Seção intitulada “POST /web/sessions”Cria uma sessão de captura. Chamado pelo backend do integrador. Retorna um token de curta duração que o frontend passa ao componente.
Auth: header x-api-key.
Headers
| Header | Valor |
|---|---|
Content-Type |
application/json |
x-api-key |
Sua API Key (pvv_api_*) |
Request body
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
reference_id |
string | Sim | ID do anúncio/entidade no seu sistema (veículo, imóvel, apólice) |
license_key |
string | Sim | Sua license (pvv_*) — a mesma usada no frontend. Amarrada à sessão; ausente ⇒ 400 |
A Provvi valida a ownership da license_key no momento da criação: ela precisa pertencer ao mesmo cliente da x-api-key. Se não pertencer, a resposta é 403 com corpo único (anti-enumeração).
Exemplo
curl -X POST "$PROVVI_SESSIONS_URL/web/sessions" \ -H "Content-Type: application/json" \ -H "x-api-key: $PROVVI_API_KEY" \ -d '{ "reference_id": "VIN-9BWZZZ377VT004251", "license_key": "'"$PROVVI_LICENSE_KEY"'" }'Response 200
Retorna exatamente dois campos:
{ "token": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "expires_at": "2026-07-07T12:30:00Z"}| Campo | Tipo | Descrição |
|---|---|---|
token |
string | Token da sessão; passe ao componente via camera.open({ token }). Válido por 30 minutos |
expires_at |
string | Expiração em ISO-8601 (RFC 3339, UTC) |
Erros
| HTTP | Causa | Body |
|---|---|---|
400 |
Campo obrigatório ausente ou malformado (inclui license_key ausente/malformada) |
{ "error": "..." } |
401 |
x-api-key ausente ou inválida |
{ "error": "API key inválida ou ausente" } |
401 |
x-api-key revogada |
{ "error": "API key inválida ou revogada" } |
401 |
Prefixo de chave incorreto | { "error": "apenas pvv_api_* aceito" } |
403 |
license_key não pertence ao cliente da x-api-key (corpo único, byte-idêntico independente da causa) |
{ "error": "License inválida para esta API key" } |
429 |
Limite de criação de sessões atingido (100/hora por x-api-key) |
{ "error": "Limite de criação de sessões atingido..." } |
503 |
Indisponibilidade transitória do contador de sessões (fail-closed) | { "error": "..." } |
Em 503, a criação falhou fechada por segurança — tente novamente após alguns segundos.
GET /web/sessions/:token
Seção intitulada “GET /web/sessions/:token”⚠️ Chamado internamente pelo componente — você não chama diretamente. Documentado aqui só para interpretar os erros que chegam no evento
errordo<provvi-camera>.
O componente busca o estado da sessão (perfil, progresso multi-foto e o destino de upload da próxima foto) neste endpoint antes de cada captura. O corpo de sucesso é consumido internamente pelo SDK.
Auth: token no path (capability).
Erros visíveis ao integrador
| HTTP | Causa | Body |
|---|---|---|
404 |
Token não encontrado | { "error": "Sessão não encontrada" } |
410 |
Sessão já utilizada ou expirada (single-use; mesma mensagem para ambos) | { "error": "Sessão expirada ou já utilizada" } |
429 |
Recarregamentos demais na mesma sessão (limite de 20) | { "error": "Limite de tentativas atingido para esta sessão" } |
503 |
Indisponibilidade transitória | { "error": "..." } |
Esses erros chegam ao seu handler de error como ProvviAPIError — mapeie a ação do usuário (recriar sessão em 410, aguardar em 429/503). Ver Tratamento de erros.
GET /web/session-results/:token
Seção intitulada “GET /web/session-results/:token”Consulta os resultados completos de uma sessão. Endpoint de leitura backend (reconciliação; uso do Hosted Flow futuro). Não é necessário no fluxo do Web SDK — o resultado chega inline no evento uploaded do componente. Chamado pelo backend do integrador com o token da sessão.
Auth: token no path (capability — trate como segredo).
Exemplo
curl "$PROVVI_SESSIONS_URL/web/session-results/$TOKEN"Response 200
{ "reference_id": "VIN-9BWZZZ377VT004251", "profile": "vehicle_inspection", "status": "captured", "total_photos": 3, "captured_count": 3, "created_at": "2026-07-07T12:00:00Z", "photos": [ { "order": 1, "label": "Frente do veículo", "status": "captured", "session_id": "sess_abc123", "heading": 87.4, "gps_lat": -23.5505, "gps_lon": -46.6333, "gps_accuracy": 12.5, "captured_at_ms": 1751889600000, "frame_hash_hex": "9f2c...e1a4", "authenticated_jpeg_url": "https://.../authenticated.jpg?...", "manifest_url": "https://.../manifest.c2pa?..." } ]}Campos da resposta
| Campo | Tipo | Descrição |
|---|---|---|
reference_id |
string | ID informado na criação da sessão |
status |
string | Estado da sessão — ver tabela abaixo |
total_photos |
number | Total de fotos previstas na sessão |
captured_count |
number | Fotos já capturadas |
created_at |
string | Criação da sessão (ISO-8601) |
photos |
array | Fotos da sessão — objeto Photo abaixo |
Objeto Photo
| Campo | Tipo | Descrição |
|---|---|---|
order |
number | Ordem da foto no perfil (1-based) |
label |
string | Rótulo da foto (ex.: “Frente do veículo”) |
status |
string | "pending" ou "captured" |
session_id |
string | ID da captura |
heading |
number | Direção da bússola em graus (pode ser ausente se o usuário pulou a bússola) |
gps_lat |
number | Latitude WGS84 (0 se a localização não foi obtida) |
gps_lon |
number | Longitude WGS84 (0 se a localização não foi obtida) |
gps_accuracy |
number | Precisão em metros (9999 se a localização não foi obtida) |
captured_at_ms |
number | Timestamp da captura em epoch ms |
frame_hash_hex |
string | Hash SHA-256 do frame, em hexadecimal |
authenticated_jpeg_url |
string | null | URL presigned do JPEG autenticado. null quando o cliente não armazena o asset (política NEVER) |
manifest_url |
string | URL presigned do manifesto C2PA (sidecar) |
Estados da sessão (status)
| Status | Descrição |
|---|---|
pending |
Nenhuma foto capturada |
partial |
Algumas fotos capturadas |
captured |
Todas as fotos capturadas |
expired |
Token expirou antes de concluir |
Erros
| HTTP | Causa | Body |
|---|---|---|
404 |
Token não encontrado | { "error": "Sessão não encontrada" } |
As URLs em
authenticated_jpeg_urlemanifest_urlsão presigned e expiram em 7 dias. Para renovar sem perder o asset, useGET /web/image/:session_id. Quandoauthenticated_jpeg_urlénull, a prova está apenas nomanifest_url(cliente zero-knowledge) — ver Resultados.
GET /web/image/:session_id
Seção intitulada “GET /web/image/:session_id”Regera uma URL presigned fresca (nova validade de 7 dias) para o asset autenticado de uma captura. Use quando a URL retornada em session-results já expirou.
Auth: session_id no path (capability — trate como segredo).
Exemplo
curl "$PROVVI_SESSIONS_URL/web/image/$SESSION_ID"Retorna uma URL presigned GET nova para o JPEG autenticado da captura. Se o cliente não armazena o asset (política NEVER), não há JPEG a servir — nesse caso a prova é o manifest_url de session-results.
POST /web/ingest
Seção intitulada “POST /web/ingest”⚠️ Chamado internamente pelo componente — você não chama diretamente. O SDK monta e envia o payload da captura (imagem, hash, GPS, nonce). Documentado aqui só para interpretar os erros que chegam no evento
errordo<provvi-camera>.
Auth: contexto da sessão (interno ao SDK).
Response de sucesso
{ "session_id": "sess_abc123", "photo_order": 1, "authenticated_jpeg_url": "https://.../authenticated.jpg?...", "manifest_url": "https://.../manifest.c2pa?...", "complete": true}| Campo | Tipo | Descrição |
|---|---|---|
session_id |
string | ID da captura |
photo_order |
number | Ordem da foto que acabou de ser capturada |
authenticated_jpeg_url |
string | null | URL presigned do JPEG autenticado; null quando o cliente não armazena o asset (política NEVER) |
manifest_url |
string | URL presigned do manifesto C2PA (sidecar interno) |
complete |
boolean | true ao concluir a captura (o Web SDK é single-photo) |
O componente não repassa todos esses campos ao integrador. No evento
uploaded, o<provvi-camera>surfaça apenas{ session_id, authenticated_jpeg_url }— o manifesto C2PA vai embutido no JPEG autenticado (auto-verificável), então não há URL de manifesto separada exposta. Ver Passo 5 · Resultados.
Erros visíveis ao integrador
O corpo traz sempre error e, em alguns casos, block_reason e/ou recapture_risk.
| HTTP | Causa | Body |
|---|---|---|
400 |
Payload inválido, frame_hash ausente, ou imagem estática detectada (liveness) |
{ "error": "..." } |
403 |
License revalidada e reprovada (revogada/suspensa/expirada) | { "error": "..." } |
403 |
Relógio do dispositivo fora de sincronia | { "error": "...verifique data e hora" } |
403 |
Nonce ausente ou inválido | { "error": "...recarregue a sessão" } |
404 |
Sessão não encontrada | { "error": "Sessão não encontrada" } |
409 |
Reuso de sessão já concluída | { "error": "Sessão já utilizada" } |
409 |
Captura não processável (corpo opaco — a sub-causa não é revelada) | { "error": "Esta captura não pôde ser processada." } |
410 |
Sessão expirada | { "error": "Sessão expirada" } |
422 |
Recaptura detectada | { "error": "Imagem rejeitada — possível recaptura", "recapture_risk": "HIGH" } |
422 |
Divergência entre GPS e IP, localização não verificável, ou deslocamento de GPS dentro da sessão | { "error": "...", "block_reason": "..." } |
502 |
Falha transitória de assinatura ou dependência | { "error": "..." } |
Os 422 de localização só ocorrem para clientes com verificação de GPS em modo enforce — ver Configuração.
Como isso chega no componente: o SDK entrega qualquer erro deste endpoint como ProvviAPIError com código INGEST_FAILED (4002) e error.details.status igual ao HTTP acima. Mapeie details.status para a ação do usuário. Ver Tratamento de erros e a Referência do SDK.
Verificação
Seção intitulada “Verificação”O resultado da captura chega inline ao frontend (evento uploaded do componente): a imagem autenticada, com o manifesto C2PA + assinatura ICP-Brasil embutidos no JPEG. A verificação do manifesto assinado está em Resultados.
Tier da garantia
Seção intitulada “Tier da garantia”A assinatura da captura web é server-side ICP-Brasil A1 sobre os bytes recebidos (assurance_level: "fraud_deterrence") — dissuasão de fraude com identidade legal brasileira. Não há âncora de proveniência de hardware no dispositivo: não é uma cadeia de prova com origem device-side. Para esse nível, use o SDK mobile.