Skip to main content
A primitive is one verified action against the user’s wallet credential. Each step in a session names a primitive and its params. UIP builds the right wallet request, verifies the signed response, and returns a normalized result.

identify

Disclose attested fields

age_verify

Anonymous age threshold

sign

Sign documents

light_sign

Sign inline terms

identify

Disclose specific government-attested fields, called blocks. You request only what you need; the user consents to exactly that.
{ "primitive": "identify", "params": { "blocks": ["first_name", "last_name", "dob", "country"] } }
Blocks (mapped to the credential’s data elements):
BlockMeaning
first_nameGiven name
last_nameFamily name
dobDate of birth (YYYY-MM-DD)
countryIssuing country
document_numberDocument/license number
portraitPortrait image
If blocks is omitted, UIP requests a sensible default (first_name, last_name, dob). A requested block the credential doesn’t carry comes back as "unavailable" rather than failing the step. Result (step.completeddata.claims):
{ "claims": { "first_name": "Jane", "last_name": "Doe", "dob": "1990-04-01", "country": "US" } }

age_verify

Prove the holder meets an age threshold without disclosing their birth date. UIP reads the date from the credential, computes the result, and returns only the boolean. The birth date never reaches your webhook.
{ "primitive": "age_verify", "params": { "min_age": 21 } }
min_age is a whole number (1–120; defaults to 18). Result (step.completeddata.satisfied):
{ "satisfied": true }
This is “anonymous to you, not to UIP”: the full identity used to compute the result is sealed in the audit record, flagged as redacted, and never exposed on your webhook or session reads.

sign

The holder’s device signs a hash of one or more documents you provide — a what-you-see-is-what-you-sign signature bound to their wallet credential. Because sign carries files, its session is created as multipart/form-data.
  • UIP computes the SHA-256 of each file and a single manifest digest over all of them (in declared order). The wallet signs the manifest; you never supply a hash.
  • The user reads the exact bytes UIP hashed on the hosted page before signing.
  • Up to 50 files, 50 MB combined. Files are deleted the moment the step verifies — only the hash + signature persist as the durable proof.
curl https://api.uip.digital/v1/sessions \
  -H "Authorization: Bearer $UIP_API_KEY" \
  -F 'steps=[{"primitive":"identify"},{"primitive":"sign"}]' \
  -F '[email protected]'
Result (step.completed):
{ "primitive": "sign", "signature": "mdoc:3045...", "document_hash": "sha256:9f86..." }

light_sign

The same device signature, over short inline terms text — no file upload. For consent, acceptance, and disclosure flows.
{ "primitive": "light_sign", "params": { "terms": "I authorize a one-time charge of $49.00." } }
  • Terms are limited to ~1,000 words (about a 5-minute read).
  • UIP canonicalizes the text (normalizes line endings), hashes it, and the wallet signs that hash. The exact terms + hash + signature are kept in the audit, so the signature stays provable.
Result (step.completed):
{ "primitive": "light_sign", "signature": "mdoc:3045...", "document_hash": "sha256:1b2c..." }

Chaining steps

Chaining is not a primitive — it’s the steps[] array. List steps in order and the user completes them in one session; you receive one step.completed per step, in sequence, then a terminal session.* event.
{
  "steps": [
    { "primitive": "identify", "params": { "blocks": ["first_name", "country"] } },
    { "primitive": "age_verify", "params": { "min_age": 18 } },
    { "primitive": "sign" }
  ]
}

Gates (pause for a decision)

Set "gate": true on a step to pause the session after it completes, so your server can decide whether to continue. Useful when a later step should depend on an earlier result (e.g. only sign if the identity matched your records).
{ "primitive": "identify", "gate": true }
On a gated step the session moves to awaiting_decision after step.completed. Resume with POST /v1/sessions/{id}/continue or end it with POST /v1/sessions/{id}/stop. A gate on the final step is rejected at create (there’s nothing to continue to).

Reserved: envelope (multi-party)

All primitives today are single-subject. Multi-party signing (Alice → Bob → Carol) is a reserved primitive on the roadmap.