ZPIP — cross-product protocol¶
ZPIP (Zev Product Integration Protocol) is how Zev products call each other. From a compliance perspective it matters because it's the mechanism by which one product accesses another's data — including personal data — and the structure of the user's consent.
This page is the DPO-facing overview. The engineering-facing protocol spec is in zevid-backend/docs/product-integration/PROTOCOL.md (link to be set once internal URL is finalised).
Two flavours¶
Service-only¶
Generic ecosystem utilities, no user in the loop. Example: zevpay.banking.read (look up a Nigerian bank account name from a NUBAN). The lookup doesn't touch any specific user's account — it's a utility that any Zev product can call.
- No consent screen.
- No user data accessed.
- Authenticated by service-key.
- Audit log per call (
zpip_token_login ZevID).
Service + user (consent-gated)¶
A product acts on behalf of a specific user for a specific scope. Example: ZevCommerce calling ZevPay to set up Checkout API access for the user's business — uses zevpay.checkout.write on the user's behalf.
- Requires explicit user consent the first time. The user sees a screen at
accounts.zevop.com/zpip-consentand clicks Allow. - Consent is per (user × calling product × scope). Recorded in
zpip_scope_grantsin ZevID. - Revocable at any time from
accounts.zevop.com → Connected Apps. - Every token issued is logged in
zpip_token_logwith the calling product, the audience, the scope, the user, and a JWT identifier (jti) for replay protection.
Consent flow (overview)¶
sequenceDiagram
participant Caller as Calling product
participant ZevID
participant User as User's browser
participant Target as Target product
Caller->>ZevID: token-exchange (scope, user, return_to)
alt user already granted
ZevID-->>Caller: access_token
Caller->>Target: API call with token
else needs consent
ZevID-->>Caller: { consent_required, consent_url }
Caller->>User: redirect to consent_url
User->>ZevID: load consent screen
ZevID-->>User: render "Caller wants <scope>"
User->>ZevID: Allow (per-scope checkboxes)
ZevID-->>User: redirect to return_to with ?granted=…&denied=…
User->>Caller: returns to product
Caller->>ZevID: token-exchange (retry)
ZevID-->>Caller: access_token
Caller->>Target: API call with token
end
Batched consent¶
To avoid users seeing N consent screens when a single user action needs N scopes, callers can pass scopes: string[] instead of scope: string. ZevID creates ONE consent screen covering every missing scope; user picks per-scope checkboxes; redirect carries ?granted=…&denied=….
Constraint: all scopes in a batch must share the owner-product (first dotted segment). Cross-product batches aren't allowed — each owner gets its own consent dialogue. Compliance rationale: keeps the consent narrative single-owner.
Decision record: zevid-backend/docs/decisions/0001-batched-zpip-consent.md (link to be set).
Lawful basis for ZPIP processing¶
- Service-only calls: legitimate interest (operational necessity for the ecosystem to function). No personal data is exchanged.
- Service + user calls: explicit consent (NDPA §25(1)(a)). The user's grant in
zpip_scope_grantsis the consent record.
Revocation¶
Users revoke at accounts.zevop.com → Connected Apps. On revocation:
zpip_scope_grants.revoked_atis set.- A
user.consent.revokedwebhook fires to the calling product. - Already-issued tokens (≤5 min TTL) continue working. Target products needing instant revocation use per-call introspection (
POST /v1/internal/oauth/introspect).
Catalog¶
Every ZPIP capability registered by a product, with the scopes it requires, lives in the catalog (product_capabilities table). See zpip-catalog.md for the per-product listing.