Authentication
The platform uses three credential types. A secret key (dv_sk_…)
holds all permissions and lives on your server. A session token
(dv_tok_…) is short-lived, scoped to one end user, and safe to ship to a
mobile or browser client. A public key (dv_pk_…) identifies your
tenant and mode in a client SDK and grants nothing on its own.
What each credential can call
| Credential | Can call |
|---|---|
Secret key (dv_sk_…) | Every endpoint. |
Session token (dv_tok_…) or publishable key (dv_pk_…) | POST /v1/ocr (without capture_integrity), POST /v1/fraud/detect (the end-user view), POST /v1/face/verify, POST /v1/liveness/check, POST /v1/liveness/challenges, GET /v1/ping, GET /v1/features and, for a token, its own GET /v1/verifications/{vf_id}. |
Everything else (reading, listing or deleting stored results, the face
gallery, PATCH /v1/features/{name}, /v1/plan, /v1/usage and
DELETE /v1/sessions/{jti}) answers 403 forbidden to a session token or
publishable key: call it from your server with the secret key.
Every request also carries a Drukverify-Version header naming the wire
format it expects. The SDKs send 2026-05-12; the gateway also accepts
2026-04-26. A missing or unknown value is a 400
(missing_version_header, unknown_version).
Secret keys (dv_sk_…)
Create them on the dashboard's API keys page. Treat them like passwords. They authenticate every server-side request and have full tenant authority.
curl -H "Authorization: Bearer dv_sk_test_..." \
-H "Drukverify-Version: 2026-05-12" \
https://api.drukverify.com/v1/ping
Rotate from the same page: create the new key, update your servers, then revoke the old one. Revocation is immediate.
Session tokens (dv_tok_…)
Short-lived JWTs minted by POST /v1/sessions with a secret key, signed by
the gateway, scoped to one customer_ref. Ship these to clients.
curl -X POST https://api.drukverify.com/v1/sessions \
-H "Authorization: Bearer dv_sk_live_..." \
-H "Drukverify-Version: 2026-05-12" \
-H "Content-Type: application/json" \
-d '{"customer_ref":"user_42","ttl_seconds":1800}'
| Field | Notes |
|---|---|
customer_ref | Your user id, 1 to 255 characters, opaque to the platform. Carried into results, webhooks and the audit log. |
ttl_seconds | Optional, 60 to 3600, default 1800. Tokens cannot be renewed; mint a fresh one when the previous expires. |
scopes | Optional list. Not enforced yet; what a token can call is fixed (see the table above). |
The response carries token, jti (for DELETE /v1/sessions/{jti}),
expires_at, scopes and mode. The token's mode matches the key that
minted it. The SDKs treat the token as opaque; only the prefix is meaningful
to a client.
A token minted by POST /v1/verifications is additionally bound to that
verification, so the checks made with it become its steps. See
Verification sessions.
Mobile flow (Flutter)
The app never sees dv_sk_*. It is configured with the public key and a
tokenProvider callback that fetches a fresh dv_tok_* from your backend:
final client = EkycClient(
config: EkycConfig(
host: 'https://api.drukverify.com',
apiKey: 'dv_pk_live_…',
tokenProvider: () async {
final res = await Dio().get<Map<String, dynamic>>(
'https://my-backend.example.com/session',
);
return res.data!['token'] as String;
},
),
);
The SDK calls tokenProvider lazily on the first request, caches the
token, and on a 401 calls the provider again and retries the request once.
Browser flow (JavaScript)
Same shape: your backend exchanges the secret key for a session token and
hands it to the page, which constructs the client with the token as
apiKey. See JS install.
Don'ts
- Don't embed
dv_sk_*in mobile or browser code. Anyone who decompiles the binary or opens DevTools can extract it. - Don't reuse a token across users. Mint one per user session.
- Don't send tokens over email or chat. They are for request-scoped HTTPS only.