Constaia
Use-case guides

Frontend-only integration

Upload documents from the browser straight to Constaia with a pk_ publishable key and a session created by your backend: no upload route of your own and no exposed secret key. Widget, fetch and SDK.

With this integration the file doesn't go through your server: the browser uploads it straight to Constaia. Your backend only makes two small calls with the secret key: create the session (what is requested and what is checked) and read the result at the end.

Use it when you don't want to build and protect an upload route (file sizes, limits, antivirus…), on static sites with a minimal backend, or in desktop and mobile apps that load your website. If you'd rather the file went through your server, use the widget with endpoint; if you don't even want to build the upload screen, use a verification link.

Your server (ck_)                 Browser (pk_)                       Constaia
─────────────────                 ─────────────                       ────────
1. POST /v1/sessions ───────────────────────────────────────────────▶ sess_… + client_secret (15 min)
2.   └─ client_secret ──────────▶ page
3.                                POST /v1/analyze (pk_ + client_secret) ▶ analysis with the session's options
4. GET /v1/sessions/{id} → GET /v1/analyses/{id}  (or webhook) ─────▶ verdict: you decide, on the server

Reference for every field and error: Sessions and publishable keys.

Step 1: create a publishable key

In the dashboard, Developers → API keys → Create key, choose the Publishable type and add the domains it will be used from:

DomainWhat it allows
app.yoursite.comOnly that host (and port, if you set one: app.yoursite.com:8443).
*.yoursite.comAny subdomain of yoursite.com, not bare yoursite.com (add it separately if you use it).
localhost:3000Your local environment. Test keys only.

Start with a pk_test_… key and localhost to develop for free. The publishable key can ship in your website's code: it only uploads documents to sessions your server creates, and only from those domains.

Step 2: create the session on your backend

Your backend decides which documents are requested and what gets checked, usually with a template and data from the authenticated user (the expected holder, their id in metadata). Create the session when you render the page: it expires after 15 minutes.

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" } } }
    ]
  }'

Never return the secret key or the whole session object to the page: only client_secret.

Step 3: upload from the page

The browser uploads the file to POST https://api.constaia.com/v1/analyze with the publishable key, the client_secret and the document_key. The analysis options come from the session: only the language is accepted from the browser.

index.html
<script type="module" src="https://cdn.jsdelivr.net/npm/@constaia/widget"></script>

<constaia-upload
  publishable-key="pk_test_…"
  session-token="{{ clientSecret }}"
  document-key="id_card"
  document="es_dni"
  lang="en"
></constaia-upload>

<script type="module">
  document.querySelector("constaia-upload").addEventListener("constaia:result", () => {
    // UI only: the verdict that counts is read by your server
    fetch("/api/constaia-done", { method: "POST" });
  });
</script>

With publishable-key, the widget sends file, client_secret, document_key and options with the language; expect and document only drive the UI (framing, two sides). Use one <constaia-upload> per session document. Every attribute is in Widget.

The publishable-key attribute is in widget versions after 0.2.0. If your project pins 0.2.0, upload with fetch or the SDK (the other tabs) until you update.

Step 4: read the result on your server

What the browser sees is for the UI. The decision (signing the user up, approving the registration) is made by your backend with the analysis it reads with its secret key:

const session = await constaia.sessions.get(user.constaiaSessionId);
const doc = session.documents.find((d) => d.key === "id_card");
if (!doc?.analysis_id) return res.status(409).json({ error: "Document missing" });

const analysis = await constaia.analyses.get(doc.analysis_id);
const status = analysis.verdict?.final_status ?? analysis.verdict?.status;

You can also wait for the analysis.completed webhook (and analysis.reviewed if there is human review): the analysis carries session_id, document_key and your metadata, so you can match it with the user without storing anything else. If the verdict is review, handle it as in Human review.

Test locally

  1. Create a ck_test_… key for the backend and a pk_test_… key with the localhost:3000 domain (your dev server's).
  2. Upload the test files (dni_valid.jpg, dni_expired.jpg…): in test no credits are spent and the result depends on the file name.
  3. Try the errors: upload the same document again (409 document_already_submitted), wait 15 minutes (401 session_expired) or open the page from another port (403 origin_not_allowed).

Errors the person will see

These errors carry a message meant to be shown to the person (in their language); the widget already shows them.

codeWhat to do
session_expiredCreate the session again (reload the page).
session_invalidCheck that you pass the right client_secret, from the same mode as the publishable key.
document_already_submittedThat document was already uploaded: read the result on your server.
invalid_document_keyThe document_key isn't in the session (or it's missing and there are several documents).
origin_not_allowedAdd the page's domain to the publishable key in the dashboard.

Checklist

  • The secret key is only on the server; the page only sees pk_… and client_secret.
  • expect, checks and the expected holder are set in the session or the template, never in the browser.
  • You store the session_id (or put the user id in metadata) to match the result.
  • The final decision is made on the server with GET /v1/analyses/{id} or the webhook.
  • The domains of the pk_live_… key are production ones only.

Next steps

On this page