Authentication
Every request to the verification API (except the public health check) must be authenticated with an API key.
Two key types: publishable and secret
Keys come in two scopes, following the Stripe/Clerk model. The scope decides which endpoints a key may call:
| Type | Prefix | Where it lives | Can call |
|---|---|---|---|
| Publishable | pk_… | Client-side — safe to ship in the SDK / your frontend | Start a verification (/verify), uploads (/upload), SDK config (/config), and minimal submission status (/status — state + reason, no PII) |
| Secret | sk_… | Backend only — never in client code | Everything a publishable key can, plus the full verification result + PII (/verifications/:id) and captured media (/verifications/:id/media/:kind) |
Each scope also carries the environment dimension in its prefix:
| Prefix | Type | Environment |
|---|---|---|
pk_test_ | Publishable | Staging |
pk_live_ | Publishable | Production |
sk_test_ | Secret | Staging |
sk_live_ | Secret | Production |
A key looks like pk_test_aB3dE5fG7hJ9kL1mN3pQ5rS7tU9vW1xY — the prefix plus 32 random alphanumeric characters.
Never ship a secret (
sk_) key in your SDK, mobile app, or any client code. The publishable key in your frontend can only start verifications and poll minimal status; it can never read identity data. Fetch results from your backend with a secret key. A publishable key hitting a secret-only endpoint gets403 secret_key_required.
Authenticating a request
Pass the full key as a Bearer token in the Authorization header:
curl "https://identity.myaza.app/api/kyc/config" \
-H "Authorization: Bearer pk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"The key identifies your organization and its environment — there is no separate organization ID to send.
Creating and managing keys
API keys are managed in the dashboard under Settings → Organization → Developers → API Keys.
- Pick the key type (Publishable or Secret) when creating a key. The environment selector in the dashboard header decides whether it's a
_test_(staging) or_live_(production) key. - Secret keys are shown only once, at creation. Store the value immediately in a secrets manager — it cannot be retrieved again, only the prefix is displayed afterward.
- Publishable keys can be viewed anytime in the dashboard — they're public by design (they ship in your frontend), so there's no secret to protect.
- Creating or revoking a key requires the
api_keys:createpermission. Members without it see keys read-only. - Revoke a key the moment it may be compromised. Revocation is immediate — the next request with that key returns
401.
Creating and revoking keys emit the
api_key.createdandapi_key.revokedwebhook events so you can wire them into your own security auditing.
Errors
| Status | Body | Cause |
|---|---|---|
401 | { "error": "Missing or invalid Authorization header" } | No Authorization: Bearer … header. |
401 | { "error": "Invalid API key" } | Unknown or revoked key. |
403 | { "error": "secret_key_required" } | A publishable (pk_) key was used on a secret-only endpoint (full result or media). Use a sk_ key from your backend. |
See Errors for the full list.
Where each key is used
- Publishable (
pk_…) — the client SDKs, which take anapiKeyand call the API from the user's device, plus any frontend code that starts verifications. Match the environment:pk_test_with the SDK'sstagingenvironment,pk_live_withproduction. - Secret (
sk_…) — your backend reading results:GET /verifications/:idfor the full result + PII andGET /verifications/:id/media/:kindfor captured media. Keep it server-side only.
Best practices
- Never put a secret key in client code. If a publishable key leaks it can only start verifications; a leaked secret key exposes identity data — treat it like a password.
- Never commit a key to a public repository or paste it where it could be scraped. Revoke immediately if a key leaks.
- Use
_test_keys in development and CI; reserve_live_keys for production traffic. - Rotate keys periodically.
- Scope keys per service or environment so you can revoke one without disrupting everything.