Sexual offences certificate (LOPIVI)
Validate the Spanish sex offender registry clearance of coaches and volunteers (recent, right holder, no records) without storing it.
If people in your organisation work with minors (coaches, instructors, volunteers), Spanish law (LOPIVI) requires you
to ask them for the clearance certificate from the Central Sex Offender Registry (Registro Central de Delincuentes
Sexuales). With the es_sexual_offences_certificate type Constaia checks that it is that certificate, that it belongs
to that person, that it is not old and that it states there are no records.
This is especially sensitive data, so this guide processes it without storing anything at Constaia.
This guide explains how to automate the check, not what the law requires in your case. The maximum age you accept and how often you ask for it again are decided by your organisation with its legal counsel.
Options
{
"expect": "es_sexual_offences_certificate",
"checks": {
"max_age_days": 90,
"holder": { "full_name": "María García López", "document_number": "12345678Z" }
},
"storage": "none",
"keep_results": false,
"metadata": { "volunteer_id": "311" }
}| Option | Why |
|---|---|
expect | If another document arrives (for example the criminal record certificate, which is es_criminal_record_certificate), the verdict is invalid with type_mismatch. |
checks.max_age_days | Maximum age since the issue date. 90 is an example: use the window your organisation has set. |
checks.holder | That the certificate holder is the person you have on record. |
storage: "none" | The file is processed in memory and never written to any storage. |
keep_results: false | Extracted data isn't stored either. You get the response once and afterwards GET /v1/analyses/{id} returns 404. |
The has_records reason is always added, without asking:
has_records | severity | Message |
|---|---|---|
| The certificate states there are no records | info | "The certificate states that there are no records." |
| The certificate states there are records | error → invalid | "The certificate states that there are records." |
The verification code (CSV)
These certificates carry a secure verification code (CSV, "código seguro de verificación"). Constaia extracts it in
csv_code and runs the csv_format validation.
csv_format only checks the format
csv_format checks that the code has a valid format. Constaia does not ask the Ministry of Justice whether the code
exists or matches this certificate. If your procedure requires checking authenticity, enter the CSV in the document
verification service of the Ministry of Justice's electronic office (sede electrónica del Ministerio de Justicia) and
compare the result with the document you received.
If the format is invalid, checks[] contains csv_format with passed: false and the verdict becomes invalid with a
csv_format reason of severity error.
The PDF's electronic signature
The certificate downloaded from the Ministry of Justice's e-government site is a PDF with an electronic signature (the
Ministry's seal). Constaia verifies it at no extra cost and without contacting anyone: that the content hasn't been
touched since it was signed and that the signer's certificate chains to a trusted authority. The result goes in
signature and in verdict.reasons:
| What arrives | Reason | Without require_valid_signature | With require_valid_signature: true |
|---|---|---|---|
| The original PDF, intact | signature_valid | info | info |
| A PDF modified after signing | document_modified_after_signing | warning → review | error → invalid |
| A broken signature | signature_invalid | error → invalid | error → invalid |
| An unsigned PDF, a photo or a scan | signature_missing | warning → review for a PDF; not added for a photo | error → invalid |
If you only accept the original PDF, add "require_valid_signature": true to checks: a photo or a "print to PDF"
becomes invalid and the person knows they must upload the downloaded file. If you accept photos, leave it off and
review the review cases by hand. Also compare signature.signer with the expected body: a valid signature only
proves who signed. Details and limits in Digital signatures in PDF.
The code
import { Constaia, ConstaiaError } from "@constaia/sdk";
import { fromPath } from "@constaia/sdk/node";
const constaia = new Constaia(); // reads CONSTAIA_API_KEY
export async function checkSexualOffencesCertificate(path, person) {
const analysis = await constaia.analyze(await fromPath(path), {
expect: "es_sexual_offences_certificate",
checks: {
maxAgeDays: 90,
holder: { fullName: person.fullName, documentNumber: person.documentNumber },
},
storage: "none",
keepResults: false,
language: "en",
metadata: { volunteer_id: String(person.id) },
});
return {
analysisId: analysis.id,
checkedAt: analysis.completed_at,
status: analysis.verdict?.status ?? "review",
problems: (analysis.verdict?.reasons ?? [])
.filter((r) => r.severity !== "info")
.map((r) => r.message),
};
}
try {
const result = await checkSexualOffencesCertificate("./sexual_offences_certificate.pdf", {
id: 311,
fullName: "María García López",
documentNumber: "12345678Z",
});
console.log(result);
} catch (err) {
if (err instanceof ConstaiaError) console.error(err.code, err.message, err.requestId);
else throw err;
}What it returns
With the test file sexual_offences_certificate.pdf and the options of the curl example (messages in Spanish, the
default language), if the PDF is the signed original (otherwise you'll also see signature_missing with warning and
a review verdict; see Test it):
{
"expected": ["es_sexual_offences_certificate"],
"match": true,
"status": "valid",
"reasons": [
{ "code": "type_match", "severity": "info", "message": "El documento es Certificado de delitos de naturaleza sexual." },
{ "code": "max_age_days", "severity": "info", "message": "Emitido hace 14 días (máximo 90)." },
{ "code": "holder", "severity": "info", "message": "Los datos del titular coinciden (full_name)." },
{ "code": "has_records", "severity": "info", "message": "El certificado indica que no constan antecedentes." }
]
}And in checks[]: nif_check_digit (the holder's DNI check letter, 12345678Z) and csv_format with passed: true.
The fields are holder_name (MARÍA GARCÍA LÓPEZ), holder_id (12345678Z), issue_date (2026-09-15),
has_records (false) and csv_code (MJU4-7K2P-9QXA-3ZTR). The number of days depends on the date you run it.
What to store in your system
Since Constaia keeps nothing (keep_results: false), your record is the only trace. Store the minimum:
| Data | What for |
|---|---|
analysis.id | Reference for incidents or support requests. You won't be able to retrieve the result with it. |
Check date (completed_at) | Know when it expires under your window and when to ask again. |
Result (valid, invalid, review) | The decision taken. |
Don't keep the PDF or the other fields unless you need them. If you keep csv_code to verify it later at the electronic
office, treat it as sensitive data: it can be used to locate the certificate.
Decide
| Verdict | Action |
|---|---|
| Válido | Record the date and allow the person to work with minors. |
| No válido | If the reason is has_records, follow your internal protocol. If it's age, holder or type, ask for a new certificate. |
| Revisar | Human review: poor photo, unreadable issue date or low confidence. |
For review you need your own copy of the document, because with storage: "none" Constaia doesn't keep it. See
Human review.
Test it
With a ck_test_… key, a PDF named sexual_offences_certificate.pdf (or any name containing sexual, delitos or
penales) returns the no-records certificate of MARÍA GARCÍA LÓPEZ issued on 2026-09-15. Change holder.full_name to
see invalid, or lower max_age_days to 7 to see the max_age_days reason with error. More in
Test mode.
The signature is not simulated: with any unsigned PDF you'll get signature_missing (warning) and a review verdict.
Use a JPEG or PNG image with that name to see the valid in the table, or your own certificate downloaded from the
e-government site to see signature_valid.
Next steps
Sport medical certificate
Check that a sport medical certificate declares the athlete fit, is signed and stamped, belongs to the athlete and is not older than your window.
Reconcile bank-transfer receipts
Check that each bank-transfer receipt has the amount, your account's IBAN and the registration reference, one by one or in batches.