Identity documents
A document policy, each document named on the screen, and a proof block.
Nothing moves without a document policy
The identity_documents scope is necessary and not sufficient. Documents are released only when the consent in force carries a document policy, and only the documents that policy names.
The policy lives in the application's allowedDataCategories, as marker strings:
document_policy:v1- The marker that switches the policy on. Without it, no document is ever released — every document call answers with nothing.
document:<country>:<type>- A document selection: an issuing country paired with a document type or a provider document code. Only matching documents may be released.
document_field:<scope>- One per document field scope you may receive — for example
document_field:document_expiry_date. A field scope granted but not in the policy releases nothing.
A document also has to qualify on the person's side: verified, active, enabled for identity sharing, not deleted — and listed on the consent screen the person approved.
Listing what you may see
curl -s "$UNIFYID_API/v1/userinfo/identity-documents" \
-H "authorization: Bearer $ACCESS_TOKEN"Each entry in data.documents
documentReference- An opaque reference, derived per client id. Use it on the detail endpoint. It is not the document's identifier and it is not portable between applications.
displayName- Always present — the human name of the document.
documentType, documentCode- Present only with
document_typegranted and in the policy. issuingCountry- Present only with
document_issuing_country. validityStatus- Present only with
document_validity. verified- Present only with
document_verified.
Without identity_documents the endpoint refuses. Unauthenticated, it answers the same recorded 401 shape as userinfo: { "error": { "code": "invalid_token", … } }.
One document, and its proof
GET /v1/userinfo/identity-documents/{documentReference} returns the same per-field rules plus two things the list does not carry: expiryDate, verifiedAt, maskedDocumentNumber — each gated on its own scope — and a proof block.
curl -s \
"$UNIFYID_API/v1/userinfo/identity-documents/$DOCUMENT_REFERENCE" \
-H "authorization: Bearer $ACCESS_TOKEN"The proof block
typeUnifyIDAttestation- What kind of proof this is.
version2- The proof format. In version 2,
attestationIdandproofHashare derived for your application alone. attestationIdatt_and 24 characters: your application's reference to the attestation this document's verification produced. The same every time your application reads this document, and different for every other application, so two applications cannot match a person by it.schemaVersion- The schema the attestation was issued under.
status- The attestation's status.
proofHashhmac-sha256:and 64 hex characters, bound to your application, thesubit knows the person by, and the attestation. Stable for your application, different for any other. It is a keyed value: your application cannot recompute or check it. Record it with your decision, and quote it withattestationIdto UnifyID support, which can recompute both and confirm what they refer to.
proof is null when no attestation exists for the document. document_verified_at is the only verification timestamp the platform exposes anywhere, and it is delivered here as verifiedAt — the attestation's issue time.
maskedDocumentNumber is the masked form only, rendered as bullets and the last four characters. There is no scope in the catalogue that releases a full document number.
Per-document naming
This is why the consent screen names documents individually rather than saying "your identity documents". A person with a passport and a driving licence sees both by name and approves a release that mentions each. The approved ids are pinned to the interaction, so the resulting consent cannot stretch to a document that was not on the screen.
What this does not do
- A document policy is a ceiling too. It bounds what could be released; the person still decides, and only what their screen listed is in force.
- A documentReference is per client id. Do not store it as a global document identifier, and do not expect it to match anything another application sees.
- Every document read is recorded as a document disclosure, in the transaction that reads it, naming the fields and the documents involved.
Verified against API commit 234dba22 on 2026-10-18. The routes, scopes, checklist, events and limits on these pages are read from that export, not typed.