For: the product owners and developers of organisations that issue or verify credentials with 12iD. Describes: what a 12iD credential is, how long it is valid, why it cannot be revoked, what happens when a user loses their phone, and how to design schemas whose values work in proofs. Read it before you design your first schema: some of these choices cannot be changed later.
Related: the API guide (api.md), data protection (data-protection.md) and
the Android SDK guide (the Android SDK guide).
A credential is a set of named values that your organisation signs and gives to one person. For example: name, employee number, department, valid until. The person keeps it in the wallet inside your app, on their phone. Later, a verifier (you or another organisation) asks the wallet for a proof, and the wallet shows only what that request asks for:
What lives where:
| Where | Notes | |
|---|---|---|
| The credential | Only on the user’s phone | 12iD keeps no copy and cannot read the wallet |
| The schema (the list of value names) and your credential definition (your signing key’s public part) | The public Indy ledger | Never any value or personal data |
| The exchange record (who was offered what, and when) | 12iD’s platform, for you | Values are erased after your retention period (data-protection.md) |
There is no revocation in this version of 12iD, so expiry is how a credential stops being accepted.
expiresAt. The console adds it when you publish a schema."1798761600" is 2027-01-01 00:00 UTC), and must be in the future.expiresAt >= now), and you cannot remove that check. An expired credential fails every proof, and the wallet still keeps it.expiresAt is never disclosed. A proof only shows that the credential is still valid, not its date.2147483647). Proofs compare integers of at most 32 bits, so a later date could never be checked; offers with one are refused (400).Choose the validity period for each credential type. A shorter period limits how long a credential you can no longer stand behind stays usable. A longer one means fewer re-issues. As a guide: months for access and membership, a year or two for qualifications, with re-issue on renewal.
expiresAt. This holds even when the person leaves your organisation, or you erase your records of the exchange (data-protection.md, “What erasure does not do”).A wallet’s keys live in the phone’s secure hardware and never leave it. There is no backup or export in this version. These all mean a new, empty wallet:
WalletKeyLost, because the key stayed on the old phone.What happens and what you do:
POST /v1/wallets (a new installation id gives a new wallet; a person may have several).DELETE /v1/wallets/{id}, and optionally erase the old connection (POST /v1/connections/{id}/erase). 12iD removes a revoked wallet’s mediator connection after 30 days.The SDK guide shows the app side (reset(), WalletKeyLost, and excluding the wallet from Android
backups, so it is never restored where its key is missing).
Credentials are built on AnonCreds, which signs strings. Getting the encoding right at schema design time is the most common source of mistakes.
Rules
>=, >, <=, <) work only on whole numbers from -2147483648 to 2147483647, written as plain digits, optionally with a leading -. Anything else (decimals, numbers above that range, text) is stored as a hash: the value can be disclosed but cannot be compared."00123" and "123" compare as the same number. Store identifiers that must keep their leading zeros as text, and never compare them.Recipes
| You want | Store as | Example | Predicate example |
|---|---|---|---|
| A date someone may need to compare (birth date, start date) | Days or a compact integer: YYYYMMDD |
"19851103" |
Over 18 on 2026-10-05: dateOfBirth <= 20081005 |
| A date only to display | ISO 8601 text | "1985-11-03" |
none (text) |
| An amount | Integer in the smallest unit | "125000" (cents) |
salary >= 5000000 |
| Yes/no | "1" / "0" |
"1" |
isAdult >= 1 |
| A choice from a list | Short text code | "ENGINEERING" |
none; disclose it |
| A document | Its hash or a reference | "sha256:9f86…" |
none |
| A timestamp to compare | Unix seconds, until 2038 | "1798761600" |
validFrom <= now (you supply now) |
YYYYMMDD compares correctly as a number (20081005 > 19851103) and stays readable. Use it for every date
a verifier may test with a predicate.
Limits on an offer: at most 125 attributes, names up to 100 characters, values up to 4 KB, and 64 KB for all names and values together. Credentials carry claims, not documents.
expiresAt (added for you).externalId and out of attribute names.