Face Local / Para desenvolver

Do download ao primeiro build.

Documentação de verdade. Permissões, integração e limites — sem letras miúdas.

SDKv0.6.0

Contrato do resultado — Face Local SDK#

SDK 0.5+ · schemaVersion: "1.0" · tipo exportado: VerificationResult.

Este documento descreve o retorno da API runFaceLocalVerification, não o relatório interno da API anterior. Os detalhes são destinados ao app integrador; não precisam aparecer no onboarding do usuário.

1. O que “aprovado” significa#

approved: true significa que uma captura nativa passou pela prova de vida e encontrou uma foto local, com uma única face, EXIF elegível do mesmo modelo de aparelho e similaridade igual ou superior ao limiar configurado.

Não confirma identidade civil, nome, documento, titularidade do celular nem autenticidade do EXIF. Uma pessoa pode aparecer em fotos de outro aparelho do mesmo modelo. Metadados também podem ser copiados ou editados.

Não há upload, consulta remota de rostos, cadastro em banco biométrico ou seleção manual de imagem de referência. Os templates são internos ao nativo, temporários e nunca retornam no JSON.

2. Status e aprovação#

statusapprovedSignificado
approvedtruePrimeiro match elegível; a busca para imediatamente
not_approvedfalseProva de vida recusada, ou busca elegível concluída sem match
inconclusivenullNão há elementos suficientes para decidir
cancellednullO usuário ou a integração cancelou a tentativa
errornullPermissão, ambiente, evidência ou outra operação impediu a conclusão

Nunca converta null para “reprovado”. Use status no controle de fluxo. not_approved significa que a tentativa não atingiu os critérios, não que a pessoa é uma impostora ou que o celular não lhe pertence.

Motivos comuns:

reasonInterpretação
match-foundHá comparação aprovada
no-matchComparações válidas concluídas abaixo do limiar
liveness-failedA captura não foi aprovada; não existe referência exportável
no-eligible-photosNenhuma candidata elegível
photos-missing-metadataFaltam os campos EXIF obrigatórios
different-device-modelFotos candidatas informam outro fabricante/modelo
no-single-faceNão foi obtida uma comparação válida com uma única face
search-limitO limite foi atingido e ainda há fotos não examinadas
unreadable-photosA leitura ficou incompleta
comparison-failedO motor não concluiu comparações necessárias
photo-full-access-requiredO sistema concedeu acesso limitado ou não informou escopo completo
photo-permissionAcesso às fotos recusado
cancelledTentativa cancelada
reference-expiredA referência deixou de ser válida
evidence-unavailableBinário nativo anterior à API de exportação
evidence-invalidFotos, sessão, score ou metadados não correspondem ao resultado
cleanup-failedNão foi possível confirmar a limpeza

Erros adicionais usam o código nativo sanitizado, em minúsculas e com hífens, em reason. errorCode preserva a forma maiúscula, por exemplo EVIDENCE_EXPIRED. Não use mensagens de erro como lógica de negócio; códigos desconhecidos também devem bloquear aprovação. Não são incluídos stack traces nem mensagens internas.

Uma foto ilegível não impede um match válido encontrado depois. Mas erro de integridade, expiração, cancelamento, falha da exportação ou da limpeza aborta a tentativa inteira, sem aprovação parcial.

3. Similaridade não é acurácia#

Os métodos usados são evaluateFaceSimiByFeature no Android e evaluateSimilarity(f1:f2:) no iOS. O adaptador recebe um score de similaridade do motor, não uma estimativa calibrada da probabilidade de identidade.

CampoDescrição
faceMatch.similarityScore original da comparação selecionada; null sem comparação válida
faceMatch.thresholdLimiar realmente aplicado, padrão 0.83
faceMatch.similarityPercentÍndice de apresentação entre 0 e 100; null sem score
faceMatch.thresholdPercentLimiar na mesma escala de apresentação
faceMatch.percentageIsProbabilitySempre false
faceMatch.selectedComparisonmatched, best-compared ou none

O índice é calculado assim:

ts
Math.round(Math.max(0, Math.min(1, similarity)) * 10000) / 100

O adaptador aceita scores finitos de -1 a 1. Valores negativos são mantidos no score original e apresentados como zero no índice. A conversão não altera a decisão, que sempre usa similarity >= threshold, sem arredondamento. Por exemplo, 0.82996 aparece como 83 no índice, mas continua abaixo de 0.83.

Exemplo ilustrativo, não medição realizada: 0.91 produz índice 91 e passa um limiar 0.83. Isso não significa “91% de certeza”, “91% de acurácia” nem “9% de risco de fraude”.

Acurácia é uma medida de desempenho em um conjunto rotulado. Sua avaliação exige pares da mesma pessoa e de pessoas diferentes, separação entre calibração e teste, falsos matches, falsas recusas e condições de uso representativas. Uma taxa de benchmark isolada não é a acurácia deste fluxo nem de uma foto. O limiar padrão é um valor inicial e não foi calibrado para este produto ou para todos os aparelhos. Não há campo accuracy fictício no retorno.

4. Qual foto e qual comparação são retornadas#

O contrato expõe no máximo duas cópias temporárias, não a biblioteca inteira:

  • photos.liveness: imagem da mesma captura nativa aprovada que gerou a referência. Não é outra selfie tirada depois e não pode ser fornecida pela integração.
  • photos.compared: a imagem da galeria efetivamente analisada na comparação selecionada. Com aprovação, é a primeira que passou. Sem match, é a de maior score entre as comparações concluídas; empates mantêm a primeira.
  • Sem comparação válida, photos.compared é null.
  • Falha na prova de vida, cancelamento ou erro: ambas são null.
  • Um resultado inconclusivo pode conter o par da melhor comparação já concluída; isso não o transforma em uma aprovação nem em uma rejeição.

faceMatch, exif e photos.compared sempre se referem ao mesmo par selecionado. O EXIF não é de uma foto enquanto o score e a imagem são de outra. O nativo valida o vínculo da sessão e o integrador recebe um objeto filtrado, sem nomes de arquivos originais, identificadores da galeria ou lista de fotos.

Cada foto presente possui:

CampoDescrição
urifile:///..., em cache privado do próprio app; não é URL pública
mimeTypeimage/jpeg
width, heightDimensões da cópia, em pixels
sizeBytesTamanho do JPEG, limitado a 8 MiB
sha256Hash dos bytes da cópia JPEG; 64 caracteres hexadecimais
representationnormalized-jpeg
base64Presente somente ao solicitar format: "base64"; sem prefixo data:

São cópias normalizadas/recomprimidas das imagens já utilizadas na inferência, não cópias byte a byte do original. A exportação não relê um arquivo da galeria que poderia ter mudado. Orientação é normalizada; EXIF original, GPS e demais metadados não são embutidos no JPEG exportado.

O score foi calculado sobre a imagem utilizada pelo motor antes da recompressão de exportação. Refazer o match sobre os JPEGs pode produzir um score numericamente diferente. O hash não é assinatura do fabricante, prova da origem da câmera, hash do EXIF original ou atestado externo de integridade.

5. EXIF materializado#

exif é null quando não houve comparação válida selecionada. Quando presente:

CampoDescrição
sourceoriginal-photo-bytes: leitura nativa dos mesmos bytes decodificados para o motor
comparisonmatched ou best-compared, coerente com o par selecionado
fields.MakeFabricante registrado no EXIF original
fields.ModelModelo registrado no EXIF original
fields.DateTimeOriginalData original como texto EXIF, sem inventar fuso horário
expectedFabricante/modelo da captura e eventual código de hardware verificado
manufacturerMatches, modelMatchesResultado da regra de correspondência
matchRuleexact-model-or-verified-hardware-alias
editableSempre true: EXIF pode ser alterado
provesDeviceOriginSempre false

Os três campos são obrigatórios. O filtro normaliza somente espaços e caixa; não aceita modelo da mesma família por aproximação. Um código de hardware alternativo precisa de equivalência explicitamente verificada no módulo nativo. O EXIF nunca ensina ao SDK qual seria o modelo do aparelho.

DateTimeOriginal é uma presença obrigatória, não uma validação forense de cronologia ou autenticidade. Datas compartilhadas/editadas podem ser falsas. Não são retornados GPS, localização, número de série, MakerNote nem o EXIF completo.

6. Device Fingerprint: perfil, não identidade única#

O nome deviceFingerprint descreve, neste contrato, um perfil do modelo:

text
kind: "model-profile"
make: fabricante usado na captura
model: nome/código exato resolvido nativamente
hardwareModel?: código real do sistema, quando houver alias Android verificado
platform: android ou ios
osVersion: versão informada pelo sistema, ou null se indisponível
uniqueDevice: false
provesOwnership: false

O objeto só é retornado após uma captura aprovada, vinculada ao aparelho atual. No Android, fabricante/código vêm do sistema e de equivalências verificadas. No iPhone, o modelo é resolvido localmente pelo DeviceKit. Modelo desconhecido é recusado; nunca vira simplesmente “iPhone” ou “Android”.

Não coletamos IDFA, IDFV, IMEI, MAC, número de série ou nome personalizado para este contrato. Não construímos um identificador de rastreamento. Milhares de unidades compartilham o mesmo perfil. Portanto:

  • Model correspondente não prova que a foto veio daquela unidade física.
  • Ter fotos parecidas no álbum não prova propriedade do celular.
  • Acrescentar a versão do sistema não torna o perfil único.
  • Esse campo não substitui App Attest, Play Integrity ou identidade civil.

7. Auditoria da decisão#

CampoDescrição
checks.livenessPassedtrue após captura validada, false em recusa explícita, null sem conclusão
checks.faceMatchPassedtrue no positivo, false na busca concluída sem match, null nos demais casos
checks.exifModelMatchedResultado para a comparação selecionada; null se não houve uma
decision.policylocal-face-continuity-v1
decision.livenessScore real, mínimo exclusivo 0.85, método passive-active e origem native-camera; ou null
decision.engineProvedor, versão do motor e plataforma; ou null se indisponível
decision.requirementsCâmera com liveness, permissão completa, EXIF do modelo atual, face única e limiar configurado
decision.comparedPhotosQuantidade de pares com uma face e similaridade válida
decision.nativeAttemptsChamadas de comparação nativa, incluindo ausência de face e falhas
decision.stoppedOnFirstMatchtrue somente quando retornou aprovação
decision.exclusionsContagens agregadas por motivo, sem listar o conteúdo do álbum

A prova de vida exige também sequência de câmera e checagens locais de integridade; não é decidida apenas pelo valor escalar. O score de liveness também não é uma probabilidade calibrada.

As contagens só descrevem o relatório efetivamente concluído. Em aborto/erro que não produziu relatório final, podem ser zero mesmo que alguma tentativa tenha ocorrido; não são um log permanente de processamento.

verificationId é um identificador aleatório de sessão, não de pessoa ou aparelho. completedAt é uma data ISO 8601 UTC baseada no relógio local, não em fonte de tempo atestada. processing: "on-device" descreve o caminho desta integração. identityVerified: false evita interpretar o resultado como verificação civil.

8. Exemplo do formato#

Exemplo sintético e abreviado, com valores ilustrativos, nunca utilizável como resultado biométrico real. O retorno real inclui os demais campos documentados acima e definidos em VerificationResult.

json
{
  "schemaVersion": "1.0",
  "verificationId": "identificador-aleatorio-da-sessao",
  "status": "approved",
  "approved": true,
  "reason": "match-found",
  "errorCode": null,
  "checks": {
    "livenessPassed": true,
    "faceMatchPassed": true,
    "exifModelMatched": true
  },
  "faceMatch": {
    "similarity": 0.91,
    "threshold": 0.83,
    "similarityPercent": 91,
    "thresholdPercent": 83,
    "percentageIsProbability": false,
    "selectedComparison": "matched"
  },
  "processing": "on-device",
  "identityVerified": false
}

Não valide o resultado por um percentual arredondado, presença de uma foto, campo EXIF isolado ou texto exibido. Consuma o estado final e trate o JSON inteiro como dado sensível, não como conteúdo para logs ou analytics.

9. Integridade e limites#

Os templates são removidos antes do retorno. As fotos opcionais têm ciclo de vida separado, descrito em INTEGRACAO.md. Cancelamento, falha de validação ou de limpeza nunca libera um resultado aprovado.

A captura não aceita imagem externa; a comparação confere EXIF em nativo; o resultado associa sessão, imagem, metadados, hash e score. Essas medidas reduzem troca acidental ou manipulação conhecida, mas não tornam confiável um processo hostil. Root/jailbreak oculto, hooks, câmera/driver adulterado ou um app modificado podem contornar checagens locais.

O JSON não é assinado nem atestado remotamente. Não aceite um objeto enviado por um cliente como prova irrefutável para um servidor. Testes unitários e compilação não medem acurácia ou resistência real a ataques. O iOS foi testado em iPhone. Para integrar, gere o build com Mac/Xcode e valide o comportamento de rede, armazenamento e biometria do seu app em aparelho físico.