Skip to the content

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 kinds
CredentialLooks likeWhere it livesWhat it can do
Secret keyrv_live_…, rv_test_…Your server, and nowhere elseEverything its capabilities allow, including minting client tokens
Publishable keyrv_pk_live_…, rv_pk_test_…May ship inside an app or a web pageOnly POST /v1/client-sessions, which returns a client token
Client tokenrv_ct_…The app or page that sends documents, for 10 minutes by defaultThe 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_secondsinteger

How long the token lives: 60 to 3600 seconds. The default is 600, ten minutes.

scopesarray of strings

Capabilities 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 strings

Limit 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:

json
{
  "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.read and client_tokens.create stay 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 Origin by itself; an app sends its bundle identifier or package name in X-ReceViz-Bundle-Id. Otherwise the answer is 403 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 keys

Web origins as scheme://host[:port], such as https://app.example.com. https://*.example.com matches example.com and every subdomain of it, with the same scheme and port.

allowed_bundle_idslistpublishable keys

iOS bundle identifiers or Android package names, such as com.example.app.

allowed_ipslist

IP 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_minuteinteger

1 to 100,000 requests a minute, instead of the default for the key.

monthly_quotainteger

Documents this key may send in a calendar month (UTC). Past it, extractions answer 429 key_quota_exceeded until 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"
json
{
  "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
}