Digital Product Passport
A Digital Product Passport (DPP) is a signed digital record that follows a physical product across its life — from the factory floor, through publication and on-chain certification, all the way to the moment a consumer takes ownership of the item. Keyban exposes that life as four discrete steps; this page walks through each one so you can map the right API calls to the right phase of your business process.
Pick a granularity first
Every passport sits at one of three levels. The level determines which fields are required, which sibling passports it links to, and whether it can ultimately be claimed by a consumer.
| Granularity | Represents | Identified by | Claimable on-chain? |
|---|---|---|---|
model | A product reference / SKU template, shared across every unit of that reference | modelNumber | No |
batch | A production lot of the same model — series, dated run, factory run | modelNumber + batchNumber | No |
item | An individual serialized unit | modelNumber + batchNumber + itemNumber | Yes |
The three levels nest: a batch resolves its parent model automatically through modelNumber; an item resolves both its parent batch and model through batchNumber and modelNumber. You can create them in any order — the link is rebuilt server-side on each read.
model and batch carry the public, shareable content (specifications, compliance, marketing). item is the only level that an end consumer can take ownership of, because each item maps one-to-one to an on-chain ERC-721 token.
The four-step lifecycle
Step 1: Build
Draft the passport in the Keyban database — nothing on-chain yet.
Step 2: Publish & certify
Public visibility plus on-chain certificate anchored at certifiedPaths.
Step 3: Configure claim
Email or phone gating, magic token, or both — item passports only.
Step 4: Consumer claims
ERC-721 minted to the consumer's wallet, mintedTo populated.
Steps 1 to 3 are partner-side and are driven from your backend with an X-Api-Key. Step 4 is consumer-side and runs through the Keyban front-end SDK; your job at step 3 is to prepare it.
1. Build the draft
You create the passport with POST /v1/dpp/passports (or POST /v1/imports for bulk uploads), picking the granularity. The passport lands in status = draft:
- It lives only in the Keyban database — nothing on-chain happens at this stage.
- It is not publicly readable — the public read endpoint returns it only after publication.
- It is freely editable through
PATCH /v1/dpp/passports/{id}until you decide to publish.
Use this phase to assemble whatever the passport must show: identifiers (gtin, serial), images, descriptions, custom data fields, and the list of certifiedPaths you want anchored on-chain at the next step.
2. Publish and certify
POST /v1/dpp/passports/publish flips matching drafts to status = published. This transition is irreversible and triggers two effects at once:
- Public visibility. The passport is now readable through the unauthenticated
GET /v1/dpp/passports/{id}. Anyone with a link or a QR code can verify it. - On-chain certification. The server canonicalises the values at
certifiedPaths, signs them with your organization's certification key, and anchors the resulting hash (lastCertificateHash) on-chain.
The two effects are bundled because they answer two complementary questions about a public passport: "Can I see it?" and "Can I prove it has not been tampered with?". Re-certification happens automatically and incrementally if you later edit a certified value or change certifiedPaths itself.
At this point you have a public, signed, on-chain-anchored record. For model and batch passports, the lifecycle ends here — they are not meant to be claimed.
3. Configure the claim (item only)
The certificate from step 2 says what the product is. The next step says who can take ownership of an individual unit. This applies only to item passports.
Two mechanisms are available, and you can use either or both:
- Email or phone gating. Set
allowedClaimEmailorallowedClaimPhoneNumberon the item (at create or update time, including viaPATCH). The first user that signs into a Keyban-powered front-end with that email or phone is associated with the item. - Magic token. Call
GET /v1/dpp/passports/{id}/magic-tokento mint a JWT bound to the passport. Print it on a QR code, embed it in a one-time URL, or include it in a packing slip. Whoever holds the token can claim the passport. Treat it as a bearer credential.
Both mechanisms converge on the same on-chain mint at step 4. They give you flexibility in how you connect the physical product to a known or anonymous consumer.
4. The consumer claims the item
The end consumer interacts with a Keyban-powered front-end (your e-commerce account area, a dedicated claim page, the Keyban consumer SDK in your mobile app). When the front-end establishes that the consumer matches the gating you configured at step 3, it calls the consumer-only endpoint POST /v1/dpp/claim. The Keyban backend then:
- Mints the ERC-721 token to the consumer's wallet — this is the on-chain mint of the item.
- Records the new owner's account UUID in
mintedTo. From that point on,mintedTois non-null andtransactionHashis recorded against the mint job.
Your backend has nothing to do during this step. To know whether an item has been claimed, poll GET /v1/dpp/passports/{id} and inspect mintedTo.
What "update" means at each step
The word update refers to slightly different things depending on the phase. To avoid confusion:
| When | "Update" means | Caller |
|---|---|---|
| Draft (step 1) | Free-form PATCH on any field except status. No on-chain effect. | Partner backend |
| Published, non-certified field (step 2) | PATCH succeeds, no re-certification. | Partner backend |
Published, certified field or certifiedPaths (step 2) | PATCH succeeds and automatically re-certifies — new lastCertificateHash, new on-chain event. | Partner backend |
| Claim (step 4) | The consumer takes ownership of the on-chain token. The passport content does not change; only mintedTo does. | Consumer (via Keyban SDK) |
Source-level immutability: passports created via the Shopify catalogue sync (source = "shopify") have editable = false and reject PATCH with 403. Passports created via POST /v1/imports (source = "import") remain editable through PATCH, but the natural way to update them is to re-upload the row through /v1/imports — the unique tuple (application, granularity, modelNumber, batchNumber, itemNumber) triggers a deterministic upsert.
How passports are created
Three entry points converge on the same passport store. Each tags the resulting record with a source value that drives editable:
- Shopify catalogue syncsource = "shopify" · editable = false (read-only)
- POST /v1/importssource = "import" · editable = true · async bulk upsert on the unique tuple
- Passport storeSingle source of truth, indexed on (application, granularity, modelNumber, batchNumber, itemNumber).
- POST /v1/dpp/passportssource = "manual" · editable = true · one passport per call
Where to go next
- Step-by-step PHP / Laravel walkthrough, including the bulk-import flow: PHP integration guide.
- Endpoint reference (request / response schemas, error shapes): DPP API reference.
- Admin UI for partners who want to manage passports without writing code: Keyban Dashboard.