Quickstart
Validate your first Spanish ID card with the Constaia API in 5 minutes using a free test key, with curl, JavaScript, PHP or Python.
In five minutes you will create an account, get a test key and run real analyses against the API without spending credits. Then you will see how to go live.
1. First analysis
Create an account
Sign up at app.constaia.com/signup. The free plan includes 150 credits per month and no card is required. Credits renew on the 1st (UTC). More in Pricing.
Copy your test key
In the dashboard, go to API keys and copy the key that starts with ck_test_.
Keys are shown only once: store it in your secrets manager.
Test keys never spend credits and return deterministic results based on the file name. Everything about this mode in Test mode.
Export the key as an environment variable
export CONSTAIA_API_KEY="ck_test_..."The official SDKs read CONSTAIA_API_KEY automatically. The key always stays on your server: never put it
in browser code or a mobile app.
Prepare a test file
In test mode the result is chosen by the file name, but the content must be a real JPEG, PNG, WEBP, HEIC or PDF (the type is detected from the first bytes). Copy any photo and any PDF you have at hand with these names:
cp ~/Pictures/any-photo.jpg dni_valid.jpg
cp dni_valid.jpg dni_expired.jpg
cp dni_valid.jpg blurry.jpg
cp dni_valid.jpg nie.jpg
cp dni_valid.jpg passport.jpg
cp ~/Documents/any-document.pdf medical_certificate.pdfAnalyse an ID card
curl https://api.constaia.com/v1/analyze \
-H "Authorization: Bearer $CONSTAIA_API_KEY" \
-F file=@dni_valid.jpg \
-F 'options={"expect":"es_dni"}'Response (fields trimmed; size_bytes and timestamps depend on your file and the moment):
{
"id": "an_01J...",
"object": "analysis",
"status": "completed",
"livemode": false,
"created_at": "2026-09-29T10:00:00Z",
"completed_at": "2026-09-29T10:00:02Z",
"file": { "name": "dni_valid.jpg", "mime_type": "image/jpeg", "pages": 1, "size_bytes": 482133 },
"document": { "type": "es_dni", "label": "DNI (España)", "confidence": 0.97, "side": "both", "country": "ESP" },
"verdict": {
"expected": ["es_dni"],
"match": true,
"status": "valid",
"reasons": [
{ "code": "type_match", "severity": "info", "message": "El documento es DNI (España)." },
{ "code": "not_expired", "severity": "info", "message": "Vigente hasta el 12/03/2031." }
]
},
"fields": {
"document_number": {
"value": "12345678Z",
"confidence": 0.99,
"validated": true,
"source": { "page": 1, "bbox": [0.61, 0.12, 0.83, 0.16] }
}
},
"checks": [
{ "code": "nif_check_digit", "passed": true, "message": "La letra del documento 12345678Z es correcta." },
{ "code": "mrz_checksums", "passed": true, "message": "Los dígitos de control de la MRZ son correctos." },
{ "code": "mrz_matches_visual", "passed": true, "message": "La MRZ coincide con los datos impresos." }
],
"warnings": [],
"exports": {},
"storage": { "mode": "none", "file_deleted_at": "2026-09-29T10:00:02Z", "expires_at": null },
"usage": { "credits": 0, "pages": 1 },
"metadata": {}
}Messages default to Spanish. Add "language":"en" to the options to get "The document is Spanish ID card (DNI)."
and "Valid until 12/03/2031.". Besides document_number, fields includes first_name (MARÍA),
last_name_1 (GARCÍA), last_name_2 (LÓPEZ), sex, nationality, birth_date (1990-05-14),
issue_date, expiry_date (2031-03-12), support_number, address, birth_place, parents and mrz, all
with the same structure.
Note usage.credits: 0 (test mode) and storage.mode: "none": the file was not stored.
2. Try other outcomes
Change only the file name to see each verdict. We show verdict and warnings; the rest of the response has
the same shape.
Expired ID card: invalid
curl https://api.constaia.com/v1/analyze \
-H "Authorization: Bearer $CONSTAIA_API_KEY" \
-F file=@dni_expired.jpg \
-F 'options={"expect":"es_dni"}'{
"expected": ["es_dni"],
"match": true,
"status": "invalid",
"reasons": [
{ "code": "type_match", "severity": "info", "message": "El documento es DNI (España)." },
{ "code": "not_expired", "severity": "error", "message": "Caducado el 15/06/2020." }
]
}The code is the same (not_expired) as for the valid card; what changes is the severity. A reason with
severity error makes the verdict No válido. With "language":"en" the message is
"Expired on 15/06/2020.".
Blurry photo: review
curl https://api.constaia.com/v1/analyze \
-H "Authorization: Bearer $CONSTAIA_API_KEY" \
-F file=@blurry.jpg \
-F 'options={"expect":"es_dni"}'{
"verdict": {
"expected": ["es_dni"],
"match": true,
"status": "review",
"reasons": [
{ "code": "type_match", "severity": "info", "message": "El documento es DNI (España)." },
{ "code": "not_expired", "severity": "info", "message": "Vigente hasta el 12/03/2031." },
{ "code": "low_quality", "severity": "warning", "message": "La calidad de la imagen es insuficiente (blurry, low_quality)." }
]
},
"warnings": ["blurry", "low_quality"]
}A warning reason gives Revisar: ask the user for another photo or send it to manual
review.
Several accepted types: NIE
expect accepts a list. Useful when you accept a DNI, NIE or passport in the same field:
curl https://api.constaia.com/v1/analyze \
-H "Authorization: Bearer $CONSTAIA_API_KEY" \
-F file=@nie.jpg \
-F 'options={"expect":["es_dni","es_nie","passport"]}'{
"document": { "type": "es_nie", "label": "NIE / TIE (España)", "confidence": 0.97, "side": "both", "country": "ESP" },
"verdict": {
"expected": ["es_dni", "es_nie", "passport"],
"match": true,
"status": "valid",
"reasons": [
{ "code": "type_match", "severity": "info", "message": "El documento es NIE / TIE (España)." },
{ "code": "not_expired", "severity": "info", "message": "Vigente hasta el 30/11/2029." }
]
}
}The number is in fields.nie_number (X1234567L). Each type has its own fields: look them up in the
catalogue or with GET /v1/document-types/es_nie.
Passport
curl https://api.constaia.com/v1/analyze \
-H "Authorization: Bearer $CONSTAIA_API_KEY" \
-F file=@passport.jpg \
-F 'options={"expect":"passport"}'Returns valid with document.type: "passport", fields.document_number PAA123456, expiry 2032-06-01 and
the mrz_checksums and mrz_matches_visual checks passed.
Medical certificate with rules
checks add business rules. Here we require the certificate to be at most one year old and signed:
curl https://api.constaia.com/v1/analyze \
-H "Authorization: Bearer $CONSTAIA_API_KEY" \
-F file=@medical_certificate.pdf \
-F 'options={"expect":"medical_certificate_sport","checks":{"max_age_days":365,"require_signature":true}}'{
"expected": ["medical_certificate_sport"],
"match": true,
"status": "valid",
"reasons": [
{ "code": "type_match", "severity": "info", "message": "El documento es Certificado médico deportivo." },
{ "code": "max_age_days", "severity": "info", "message": "Emitido hace 28 días (máximo 365)." },
{ "code": "require_signature", "severity": "info", "message": "El documento está firmado." }
]
}The test certificate is issued on 2026-09-01, so the number of days in the message depends on the date you run
it. All available rules are in Checks.
3. From your code
The same call with the SDKs or plain HTTP. Always handle errors: the API returns
{ "error": { "type", "code", "message", "request_id" } } (see Errors).
#!/usr/bin/env bash
set -euo pipefail
curl --fail-with-body -sS https://api.constaia.com/v1/analyze \
-H "Authorization: Bearer $CONSTAIA_API_KEY" \
-F file=@dni_valid.jpg \
-F 'options={"expect":"es_dni","checks":{"min_age_years":18},"language":"en"}'A synchronous analysis waits up to 30 seconds. If it has not finished, the API responds 202 with
status: "queued" or "processing" and the result arrives by webhook (or by polling
GET /v1/analyses/{id}). With short documents in test mode you will always get 200.
4. Go live
Verify your email
ck_live_ keys can only be created with a verified email. If it is not verified, the dashboard returns
email_not_verified.
Create a ck_live_ key
In the dashboard → API keys, create a production key. It is shown only once. Live analyses consume credits: first the month's 150 free credits and then those from the packs you buy. See Credits and billing.
Swap the environment variable
No code changes needed: replace the value of CONSTAIA_API_KEY in your production environment with the
ck_live_ key. Each mode's analyses are separate: listing with a live key only shows live analyses.
Set up production webhooks
Webhook endpoints belong to the mode of the key you create them with. Create your endpoints again with the live key; see Webhooks.
live_mode_unavailable
Live keys never get simulated results. If the environment has no real AI providers available, live calls fail
with 503 live_mode_unavailable. Meanwhile, keep developing with your ck_test_ key.
Next steps
Introduction
Constaia is a European API that tells you whether a document is the one you expect and valid, with reasons, extracted fields and zero retention by default.
Key concepts
Document types, expect, verdicts, reasons, fields, checks, warnings, test and live modes, credits, storage, webhooks and idempotency in Constaia.