Authentication
Secret keys on your server, publishable keys and short-lived client tokens in apps and pages.
On this page
Every request carries one credential as Authorization: Bearer <credential> (the header X-ReceViz-Key is accepted as well). What the credential may do is decided on every request, by the API, never by the console alone.
Three kinds of credential#
| Credential | Looks like | Where it lives | What it can do |
|---|---|---|---|
| Secret key | rv_live_…, rv_test_… | Your server, and nowhere else | Everything its capabilities allow, including minting client tokens |
| Publishable key | rv_pk_live_…, rv_pk_test_… | May ship inside an app or a web page | Only POST /v1/client-sessions, which returns a client token |
| Client token | rv_ct_… | The app or page that sends documents, for 10 minutes by default | The capabilities of the key that minted it that a public client may hold |
The prefix says what a string is before anything is looked up, so a key pasted in the wrong place is refused at once: a publishable key on any other endpoint answers 403 publishable_key_not_allowed, and a string with no ReceViz prefix answers 401 invalid_credentials.
Live and test keys#
Keys belong to an application, and the application's environment decides their mode. A production application issues live keys; development and test applications issue test keys. Every extraction and GET /v1/me say which with livemode.
Test keys have a lower default rate limit (30 requests a minute, against 120 for live keys) unless your organization or the key sets another. In the console, developers can create and change the keys of development and test applications; the keys of a production application need an admin or an owner.
Keep secret keys on your server#
A secret key is a permanent credential for the whole application. Anything shipped in a web page or an app binary can be read out of it, so a secret key never goes there, and never into a repository.
- ReceViz stores only a SHA-256 hash of each key. The key is shown once, when it is created.
- The Web SDK refuses a secret key in a browser. The iOS SDK refuses one wherever it appears, even when your own token provider returns one by mistake, and the Android SDK's credential types refuse one when they are created.
- An application holds at most 25 active keys. A key can be given an expiry of 1 to 3650 days when it is created.
Mint client tokens on your server#
When an app or a page needs to send documents, your server calls POST /v1/client-tokens with its secret key (it needs the client_tokens.create capability) and hands the token over. Check your own sign-in first: a token is as good as the key's capabilities for as long as it lives.
import express from "express";
const RECEVIZ_API = "https://accounthouse-backend-793493499887.me-central1.run.app/api/receviz/v1";
const app = express();
// requireSignedIn is your own middleware: only people who may scan get a token.
app.post("/api/receviz-token", requireSignedIn, async (req, res) => {
const response = await fetch(`${RECEVIZ_API}/client-tokens`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.RECEVIZ_SECRET_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ ttl_seconds: 600, document_types: ["payment_receipt"] }),
});
const body = await response.json();
if (!response.ok) return res.status(502).json({ error: body.error.code });
res.json({ token: body.token, expires_at: body.expires_at });
});ttl_secondsintegerHow long the token lives: 60 to 3600 seconds. The default is 600, ten minutes.
scopesarray of stringsCapabilities for the token, from those the key holds that a public client may hold. Leave it out for all of them. Asking for more answers
403 scope_not_granted.document_typesarray of stringsLimit the token to these document types. Each must be one the application can use. Leave it out for all of them.
The response, with a status of 201:
{
"object": "client_token",
"token": "rv_ct_eyJhcHAiOiJhcHBfUTNmSzhzTG0yVnhUOXBOYTRSd1oxYyIsImRzIjoi…",
"expires_at": "2026-09-27T09:24:05Z",
"scopes": [
"documents.upload",
"documents.extract",
"camera.capture",
"adaptive_tiling",
"ocr.basic",
"schemas.read",
"schemas.execute",
"quality.telemetry"
],
"application": "app_Q3fK8sLm2VxT9pNa4RwZ1c",
"livemode": false,
"document_types": [
"payment_receipt"
]
}- A client token never carries a capability that manages the integration:
schemas.write,webhooks.receive,usage.readandclient_tokens.createstay with secret keys, whatever the key that minted the token holds. - Every request made with a token checks the key that minted it again. Revoke the key and all its tokens stop working at their next request, with
401 api_key_revoked. - A token counts against its key: the key's rate limit and monthly quota cover everything its tokens send.
- When a token expires, the API answers
401 token_expired. The SDKs ask your server for a new token and retry once.
Open a client session with a publishable key#
An app or a page with no server of its own can hold a publishable key and exchange it for a client token at POST /v1/client-sessions. The body is optional: {"document_types": ["payment_receipt"]} limits the token to those types. The response is the same client token object; a session's token lives the default ten minutes.
import { Client } from "@receviz/web";
const RECEVIZ_API = "https://accounthouse-backend-793493499887.me-central1.run.app/api/receviz/v1";
// The SDK opens the session, keeps the token and renews it when it expires.
const client = new Client({
baseUrl: RECEVIZ_API,
publishableKey: "rv_pk_live_…",
documentTypes: ["payment_receipt"],
});- When the key is restricted to web origins or app bundle identifiers, the request must match one of them. A browser sends its page's
Originby itself; an app sends its bundle identifier or package name inX-ReceViz-Bundle-Id. Otherwise the answer is403 origin_not_allowed. - A publishable key can open at most 30 sessions a minute.
- A publishable key holds only capabilities a public client may hold; the console refuses to give it a server-only one.
Restrictions narrow a publishable key; they do not hide it
Outside a browser, anyone can send any Origin or bundle identifier. What limits a copied publishable key is what it can do: open ten-minute sessions, with public-client capabilities only, 30 times a minute. When you have a server, mint client tokens there instead.
Restrictions#
Each key can carry restrictions, set in the console under API keys. Every list takes up to 50 entries.
allowed_originslistpublishable keysWeb origins as
scheme://host[:port], such ashttps://app.example.com.https://*.example.commatchesexample.comand every subdomain of it, with the same scheme and port.allowed_bundle_idslistpublishable keysiOS bundle identifiers or Android package names, such as
com.example.app.allowed_ipslistIP addresses or CIDR ranges, such as
203.0.113.0/24. Checked for requests made with the key itself; requests made with its client tokens come from people's devices and are not checked. A secret key is restricted this way, never by origin or bundle identifier.rate_limit_per_minuteinteger1 to 100,000 requests a minute, instead of the default for the key.
monthly_quotaintegerDocuments this key may send in a calendar month (UTC). Past it, extractions answer
429 key_quota_exceededuntil the month turns.
Rotate a key#
Rotating a key in the console creates a new key with the same name, capabilities and restrictions, shown once. The old key keeps working for a grace period so you can roll the new value out: 24 hours unless you choose otherwise, anything from 0 to 168 hours. A grace period of 0 revokes the old key at once; after a longer one, the old key answers 401 api_key_expired. A key that was due to expire sooner than that keeps its earlier date.
Only an active key can be rotated. The rotation is recorded in the organization's audit log.
Revoke a key#
Revoking a key in the console takes effect on the next request: the key answers 401 api_key_revoked, and so does every client token minted from it. You can record a reason. Archiving an application revokes all of its keys.
A capability taken away from the organization or the application is gone from every key at once, with nothing to re-issue; see Capabilities.
Check what a credential can do#
GET /v1/me describes the calling credential: its application, mode, kind, the capabilities it holds after every layer is applied, and its rate limit. For a client token it also gives token_expires_at. It needs no capability, but a publishable key cannot call it.
RECEVIZ_API="https://accounthouse-backend-793493499887.me-central1.run.app/api/receviz/v1"
curl "$RECEVIZ_API/me" -H "Authorization: Bearer $RECEVIZ_SECRET_KEY"{
"object": "credential_context",
"tenant": "tnt_R8mZq2WxK5nL3vTb9HcJ7d",
"application": "app_Q3fK8sLm2VxT9pNa4RwZ1c",
"application_name": "Expense capture",
"environment": "development",
"livemode": false,
"kind": "secret",
"credential": "key_M4tX9pQ2sV7nK1wR8zLb3f",
"scopes": [
"documents.upload",
"documents.extract",
"camera.capture",
"adaptive_tiling",
"ocr.basic",
"schemas.read",
"schemas.execute",
"webhooks.receive",
"quality.telemetry",
"usage.read",
"client_tokens.create"
],
"rate_limit_per_minute": 30,
"token_expires_at": null
}