Constaia
Endpoints

Verification links

Reference for /v1/verification-links: create a hosted page where the person uploads their documents from their phone, receive the results by webhook, callback or email, and retrieve, list or cancel links.

A verification link is a page hosted by Constaia (url, with its QR code in qr_svg) where the person accepts the consent and uploads each document you ask for, with their phone camera or from a file. Each upload is a normal analysis in the key's mode (in live it costs credits) and they all go into a dossier (dossier_id) with an overall verdict.

Step-by-step guide with callback, results email and the return to your website: Create links via API and receive the results.

Method and routeWhat it does
POST /v1/verification-linksCreates a link (and its dossier).
GET /v1/verification-linksLists the links in the key's mode.
GET /v1/verification-links/{id}Retrieves one, with qr_svg.
DELETE /v1/verification-links/{id}Cancels it: the page stops accepting documents.

All routes use a secret key (ck_test_… or ck_live_…) from your backend.

POST /v1/verification-links
Content-Type: application/json
FieldTypeDefaultDescription
templatestring | null—Id of one of the account's templates (tpl_…). Its analysis options apply to every document. 422 template_not_found if it doesn't exist.
documentsobject[] (1–10)—Documents requested, in order. Each one: key (1–40 characters: letters, numbers, - or _, unique), label (visible text, up to 120), expect (a type or list of types) and checks (as in analyze). If missing and there is a template, one document is requested (key: "document") with the template's expect. Without documents or template: 422 documents_required.
referencestring | null—Your reference (up to 200 characters). Useful to filter the list.
metadataobject string → string{}Up to 20 keys. Copied to the dossier and to each analysis.
expires_in_hoursinteger 1–72072Hours of validity. Once they pass, the link becomes expired.
localees | en | pt | frthe template's or the request's languageLanguage of the page, the emails and the dossier messages.
notify_emailemail | null—Sends the link to the person by email, in locale.
consent_textstring | nullthe account'sYour own consent text (up to 2000 characters).
show_resultbooleanthe account'sShow the person the result of each document.
redirect_urlhttps string | null—Where the person goes back to when they finish (button on the final screen).
redirect_with_statusbooleanfalseAppends ?link_id=vl_…&status=<link status> to redirect_url.
face_verification{ enabled, required? } | nullthe template'sSelfie step after the documents (optional module, EEA accounts with the addendum accepted; otherwise 403 face_verification_not_enabled). The link and the webhook include face_verification and face_match. See Face verification.
callback_urlhttps string | null—Receives a signed POST with verification_link.completed and verification_link.expired. Must be https://; otherwise 422 invalid_url (param: "callback_url").
include_resultsbooleantrueInclude results (the analysis of each document) in the webhook and the callback. false: only the link and the dossier verdict.
results_emailemail | null—Email that receives a summary of the results when the link is completed, in locale.

Fields are strict: an unknown one returns 422 invalid_parameter with its param.

curl https://api.constaia.com/v1/verification-links \
  -H "Authorization: Bearer $CONSTAIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "documents": [
      { "key": "id_card", "label": "DNI or NIE", "expect": ["es_dni", "es_nie"], "checks": { "min_age_years": 18 } },
      { "key": "receipt", "label": "Payment receipt", "expect": "payment_receipt", "checks": { "expected_amount": 45 } }
    ],
    "reference": "registration-4821",
    "metadata": { "registration_id": "4821" },
    "locale": "en",
    "callback_url": "https://example.com/constaia/callback",
    "results_email": "office@example.com",
    "redirect_url": "https://example.com/registration/4821",
    "redirect_with_status": true
  }'
201 Created (trimmed)
{
  "id": "vl_01J9Z8Q3K4M5N6P7Q8R9S0T1V2",
  "object": "verification_link",
  "url": "https://app.constaia.com/v/3kqX…",
  "qr_svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" …</svg>",
  "status": "pending",
  "livemode": false,
  "template": null,
  "reference": "registration-4821",
  "metadata": { "registration_id": "4821" },
  "documents": [
    {
      "key": "id_card",
      "label": "DNI or NIE",
      "expect": ["es_dni", "es_nie"],
      "checks": { "min_age_years": 18 },
      "status": "pending",
      "analysis_id": null,
      "attempts": 0,
      "verdict_status": null,
      "final_status": null,
      "warnings": []
    },
    { "key": "receipt", "label": "Payment receipt", "…": "…" }
  ],
  "dossier_id": "dos_01J9Z8Q3K4M5N6P7Q8R9S0T1V3",
  "expires_at": "2026-10-03T10:00:00Z",
  "redirect_url": "https://example.com/registration/4821",
  "redirect_with_status": true,
  "callback_url": "https://example.com/constaia/callback",
  "include_results": true,
  "results_email": "office@example.com",
  "results_email_sent_at": null,
  "locale": "en",
  "notify_email": null,
  "last_sent_at": null,
  "show_result": false,
  "consent_text": null,
  "consent": null,
  "events": [{ "type": "created", "at": "2026-09-30T10:00:00Z" }],
  "created_at": "2026-09-30T10:00:00Z",
  "completed_at": null,
  "cancelled_at": null
}

Send url to the person (or show them qr_svg). The URL carries a secret token: treat it as a single-use credential and don't publish it.

FieldTypeDescription
idstringPrefix vl_.
urlstringPage the person opens.
qr_svgstringQR code of url as SVG. Only on creation and on the individual GET.
statusstringpending (nobody has started), in_progress, completed, expired or cancelled.
livemodebooleanMode of the key it was created with.
template, reference, metadataWhat you set when creating it.
documents[]object[]Status of each document: key, label, expect, checks, status (pending, processing, completed, failed), analysis_id (the analysis that counts), attempts, verdict_status, final_status (after human review) and warnings (quality warnings from the last attempt).
dossier_idstring | nullDossier with all the documents, the overall verdict and the cross-document checks (same holder, consistent dates).
expires_atISO 8601 stringEnd of validity.
redirect_url, redirect_with_statusReturn to your website.
callback_url, include_resultsSigned callback on completion or expiry.
results_emailstring | nullRecipient of the summary.
results_email_sent_atstring | nullWhen the summary was sent.
localestringLanguage of the page and the emails.
notify_email, last_sent_atWho the link was emailed to, and when.
show_result, consent_textPage settings.
consentobject | nullAccepted consent: accepted_at, ip and text.
events[]object[]History: type, at and, depending on the type, document_key or email.
created_at, completed_at, cancelled_atstring | nullDates.

events[].type values:

TypeWhen
createdThe link is created.
sentThe link is sent by email (with email).
openedThe person opens the page for the first time.
consent_acceptedThey accept the consent.
document_uploadedThey upload a document (with document_key).
document_acceptedA document is accepted (with document_key).
document_rejectedA document needs to be redone, for example because of a blurry photo (with document_key).
completedAll documents are finished.
expiredexpires_at passes without it being completed.
cancelledYou cancel it.
results_sentThe summary is sent to results_email (with email).

Each document allows up to 5 attempts. A document with quality warnings that ask for another photo (and a non-valid verdict) can be redone while attempts remain. The link is completed when every document is accepted or out of attempts.

Results: webhook, callback and email

When the link becomes completed or expired you receive the same body in two ways:

  • Global webhook: verification_link.completed and verification_link.expired, on the webhook endpoints subscribed to those events.
  • Link callback: a POST to callback_url, signed with Standard Webhooks and with the same retries.

data is the verification_link object (without qr_svg) plus:

FieldDescription
results[]Only if include_results is true. One per requested document, in order: document_key, label and analysis (the analysis object that counts for that document, as it was stored: with mask_fields applied and without file_url; null if it wasn't uploaded).
dossier{ id, verdict: { status, reasons: [{ code, severity, message }] } } of the dossier, in the link's language, or null. status: complete_valid, incomplete, invalid or review.

With results_email, a summary without any document data is also sent on completion. Details on the signature, the secret, the retries, the full body and the email are in the results guide.

curl "https://api.constaia.com/v1/verification-links?status=completed&metadata[registration_id]=4821" \
  -H "Authorization: Bearer $CONSTAIA_API_KEY"
ParameterDescription
limit1–100, default 20.
starting_afterId of the last link on the previous page. See pagination.
statuspending, in_progress, completed, expired or cancelled.
referenceYour exact reference.
metadata[key]Filters by a metadata value.

Returns { "object": "list", "data": [...], "has_more": false, "url": "/v1/verification-links" }, only with the links in the key's mode and without qr_svg.

Retrieve and cancel

curl https://api.constaia.com/v1/verification-links/vl_01J9Z8Q3K4M5N6P7Q8R9S0T1V2 \
  -H "Authorization: Bearer $CONSTAIA_API_KEY"

curl -X DELETE https://api.constaia.com/v1/verification-links/vl_01J9Z8Q3K4M5N6P7Q8R9S0T1V2 \
  -H "Authorization: Bearer $CONSTAIA_API_KEY"

DELETE returns the link with status: "cancelled"; cancelling one that is already cancelled returns it unchanged. A completed or expired link can't be cancelled: 409 link_not_active. An id that doesn't exist in the account returns 404 resource_missing.

Errors

HTTPcodeWhen
422invalid_parameterA field is not valid (param says which), for example redirect_url without https://.
422invalid_urlcallback_url is not an https URL (param: "callback_url").
422documents_requiredNeither documents nor template.
422template_not_foundThe template doesn't exist in the account.
409link_not_activeCancelling a completed or expired link.
404resource_missingThe link doesn't exist.

Next steps

On this page