Sessions and publishable keys
Reference for /v1/sessions and pk_ publishable keys: your backend creates a 15-minute session and the browser uploads each document straight to Constaia with the client_secret, without exposing your secret key.
Sessions (sess_…) let the browser upload documents straight to Constaia, without the file going through your
server and without exposing your secret key:
- Your backend creates a session with the secret key (
ck_…) and decides what is requested and what is checked. - Your page only receives the session's
client_secret. - The browser uploads the document to
POST /v1/analyzewith a publishable key (pk_…) and thatclient_secret.
Full guide with the widget, fetch and the SDK: Frontend-only integration.
| Method and path | Key | What it does |
|---|---|---|
POST /v1/sessions | secret | Creates a session and returns its client_secret. |
GET /v1/sessions/{id} | secret | Retrieves the session: status and each document's analysis. |
POST /v1/analyze | publishable | Uploads a session document from the browser. |
Your server (ck_) Browser (pk_) Constaia
───────────────── ───────────── ────────
POST /v1/sessions ──────────────────────────────────────────────────▶ sess_… + client_secret (15 min)
└─ client_secret ─────────────▶ <constaia-upload> / fetch
POST /v1/analyze ───────────────────▶ analysis with the session's options
GET /v1/sessions/{id} ──────────────────────────────────────────────▶ documents[].analysis_id
GET /v1/analyses/{id} (or analysis.completed webhook) ─────────────▶ verdictPublishable keys
A publishable key (pk_live_… or pk_test_…) can ship in your website's code: it only uploads documents to a
session your server has already created, and only from the domains you allow.
- Create them in the dashboard: Developers → API keys, Publishable type. Like every key, it's shown only once.
- Allowed domains (1–20):
app.yoursite.com(that exact host),*.yoursite.com(any subdomain, but not bareyoursite.com: add it separately) and, only on test keys,localhost:3000. The port is part of the host. Domain changes apply immediately. - The browser request must carry the
Originheader of one of those domains (browsers add it themselves). Otherwise:403 origin_not_allowed. - They only work on
POST /v1/analyzewithclient_secret. Any other route answers403 publishable_key_not_allowed. - The mode comes with the key: a
pk_test_…only opens sessions created with ack_test_…, and apk_live_…, those created with ack_live_….
Secret keys, never in the browser
A ck_… key that arrives from a page on another origin (Sec-Fetch-Site header other than same-origin) is rejected
with 403 secret_key_in_browser, even if it's valid. If it has already been published, revoke it in the dashboard. See
Authentication.
Create a session
POST /v1/sessions
Authorization: Bearer ck_live_…
Content-Type: application/json| Field | Type | Description |
|---|---|---|
template | string | Template (tpl_…) with the analysis options of every document. 422 template_not_found if it doesn't exist. |
documents | object[] (1–10) | Documents that can be uploaded. Each one: key (1–40 characters: letters, digits, - or _, unique), label, expect (type or list of types) and checks (as in analyze). Without documents: one, key: "document", with the template's options. |
reference | string | Your reference (up to 200 characters). It reaches each analysis as metadata.session_reference. |
metadata | object string → string | Up to 20 keys. Copied to every analysis. Put the user id here to match the result. |
language | es | en | pt | fr | Language of the verdict messages, unless the browser sends another. |
Each document's options come from the template and, on top, from the document's expect and checks (checks key by
key). This is where you set what the browser must not decide: for example the expected holder (checks.holder) from
the authenticated user.
curl https://api.constaia.com/v1/sessions \
-H "Authorization: Bearer $CONSTAIA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"template": "tpl_01J9Z8Q3K4M5N6P7Q8R9S0T1V2",
"reference": "user_42",
"metadata": { "user_id": "42" },
"documents": [
{ "key": "id_card", "label": "ID card", "expect": ["es_dni", "es_nie"], "checks": { "holder": { "full_name": "María García López" } } }
]
}'{
"id": "sess_01J9Z8Q3K4M5N6P7Q8R9S0T1V2",
"object": "session",
"client_secret": "sess_01J9Z8Q3K4M5N6P7Q8R9S0T1V2_secret_Xk2…",
"status": "open",
"livemode": true,
"template": "tpl_01J9Z8Q3K4M5N6P7Q8R9S0T1V2",
"reference": "user_42",
"metadata": { "user_id": "42" },
"documents": [
{ "key": "id_card", "label": "ID card", "expect": ["es_dni", "es_nie"], "analysis_id": null, "status": "pending" }
],
"expires_at": "2026-09-30T10:15:00Z",
"created_at": "2026-09-30T10:00:00Z"
}| Field | Description |
|---|---|
client_secret | Only on creation. Hand it to the page. It grants access to upload this session's documents and nothing else. |
status | open, completed (every document uploaded) or expired (15 minutes passed without completing). |
documents[].status | pending (not uploaded), processing (upload in progress) or submitted (it has an analysis). |
documents[].analysis_id | The analysis created by the upload, or null. |
expires_at | 15 minutes after creation. Create the session when you render the page, not in advance. |
Upload from the browser
POST /v1/analyze
Authorization: Bearer pk_live_…
Content-Type: multipart/form-data| Field | Description |
|---|---|
file | The document. |
client_secret | The session's. |
document_key | The key of the session document. Optional if the session has only one. |
options | Optional. JSON where only language is taken into account. Everything else (expect, checks, storage…) comes from the session and its template. |
const form = new FormData();
form.append("file", input.files[0]);
form.append("client_secret", clientSecret);
form.append("document_key", "id_card");
const res = await fetch("https://api.constaia.com/v1/analyze", {
method: "POST",
headers: { Authorization: "Bearer pk_live_…" },
body: form,
});
const analysis = await res.json();Answers with the analysis object, like a normal call (200, or 202 if it takes more than
30 s), with session_id and document_key. It's charged to your account like any analysis.
- Each document is analysed once. A second upload of the same document returns
409 document_already_submitted. If the upload fails before the analysis is created (invalid file, no credits, rate limit), the document is freed to try again. - There are no progressive results (
stream) orIdempotency-Keyin this mode. - The browser can't read analyses or sessions: the result it sees is for the UI, not for deciding.
Retrieve a session
curl https://api.constaia.com/v1/sessions/sess_01J9Z8Q3K4M5N6P7Q8R9S0T1V2 \
-H "Authorization: Bearer $CONSTAIA_API_KEY"Returns the session without client_secret. When the user is done, your backend reads each document's analysis_id
here and the analysis with GET /v1/analyses/{id} (or receives it through the
analysis.completed webhook), and decides with that verdict. It only sees sessions of the key's mode; if it doesn't
exist, 404 resource_missing.
SDKs
import { Constaia } from "@constaia/sdk";
// Backend (secret key)
const constaia = new Constaia();
const session = await constaia.sessions.create({
template: "tpl_01J9Z8Q3K4M5N6P7Q8R9S0T1V2",
reference: "user_42",
documents: [{ key: "id_card", expect: ["es_dni", "es_nie"] }],
});
// → session.client_secret to the page
// Browser (publishable key): only sessions.submit() is allowed
const browser = new Constaia({ apiKey: "pk_live_…" });
const analysis = await browser.sessions.submit(file, { clientSecret, documentKey: "id_card" });Errors
| HTTP | code | When |
|---|---|---|
401 | session_invalid | The client_secret doesn't exist, is malformed or belongs to another mode or account. |
401 | session_expired | The session expired (15 minutes). Create another one. |
403 | origin_not_allowed | The page's origin is not in the publishable key's allowed domains. |
403 | publishable_key_not_allowed | A pk_ key on a route other than POST /v1/analyze. |
403 | secret_key_in_browser | A ck_ key used from a browser. |
409 | document_already_submitted | That session document was already uploaded. |
422 | invalid_document_key | document_key is not in the session, or it's missing and the session has several documents. |
422 | duplicate_document_key | On creation: two documents with the same key. |
422 | template_not_found | On creation: the template doesn't exist (param: "options.template"). |
These errors' messages are meant to be shown to the person (in es, en, pt or fr). The other upload errors
(balance, limits, invalid file) are those of analyze.
Next steps
Dossiers
Reference for /v1/dossiers: group several documents of one person or procedure with requirements, add documents by file or analysis_id and get an overall verdict with cross-document holder and date checks.
GET /v1/document-types
Reference for GET /v1/document-types: the public catalogue of document types with fields, JSON Schema, validators and applicable checks, no API key needed.