Get started

Docs · Concepts

Authentication

Every merchant endpoint is authenticated with a bearer API key that carries its own environment, scopes and expiry.

Bearer tokens

Every /v1/* route except the public status poll requires an Authorization header:

Header
Authorization: Bearer vk_test_...   (or vk_live_...)

A missing, malformed, revoked or unknown key all return the same 401 invalid_api_key. The message never distinguishes "no such key" from "revoked key" from "wrong tenant" — the API is not an oracle for which keys exist.

There is a second credential kind. A device token (vd_…) is what a venue till or tablet carries instead of a key; it goes in the same header, resolves through the same hook, and differs only where a device is deliberately narrower — see Devices API.

How keys are stored

The prefix carries the environment: vk_test_ or vk_live_. Valitel never stores the raw key — only a SHA-256 hash and the first 12 characters as a display prefix (vk_test_3fk2…), which is what the dashboard lists. A leaked database dump therefore contains no usable credentials, and a key that was not copied at mint time cannot be recovered.

Scopes

Every key carries a set of scopes, chosen when it is minted. There are four:

ScopeGrants
verifications:createPOST /v1/verifications
verifications:readGET /v1/verifications, GET /v1/verifications/:id, POST /v1/verifications/:id/redeem
verifications:deleteDELETE /v1/verifications/:id
devices:managePOST /v1/devices, GET /v1/devices, DELETE /v1/devices/:id

Redeem counts as a read: it fetches a result that already exists. Erasure is its own scope because reading a claim and destroying the record of it are different powers, and only one of them is irreversible — the mint form offers it unticked, so a key created by accepting the defaults cannot erase anything.

A key presented to a route it lacks the scope for gets 403 insufficient_scope, and the message names the missing scope. The check runs before the request body is parsed, so a malformed payload on a route you are not scoped for is still a 403, never a 400. Authentication is decided first: an unknown or expired key always gets its 401.

Expiry

A key may be minted with a deadline — the dashboard offers never, 30, 90 or 365 days, and never is the default. Past it the key answers 401 api_key_expired, a distinct code from invalid_api_key so an integration can tell "rotate me" from "this was never valid". The boundary is inclusive: a key expiring at exactly now is already expired.

The routes that need no key

Three routes are deliberately unauthenticated:

  • GET /v1/verifications/:id/status — the poll the widget makes from the merchant's own page. It returns {id, status} and never claims.
  • GET /wallet/request/:sessionId and POST /wallet/response/:sessionId — the wallet's two calls. The unguessable session id in the path is the capability.