You send the image and get back the name, CURP, elector key, CIC and OCR already separated. And the address —which on the INE comes abbreviated and on a single line— arrives broken into fields and validated against the official catalog.
Technical questions? Talk to an engineer on WhatsApp — 0% bots.
It comes in three lines of free text, in caps, with abbreviations —C for calle, AV for avenida, FRACC for fraccionamiento, COL for colonia— and often with no separation between where the street ends and the number begins. Most OCRs hand it back to you exactly like that, and taking it apart is your job.
And it doesn't hand it over blindly: the geocodingStatus field tells you how far the match got —VERIFIED at street or house level, PARTIAL when only the locality or postal code could be confirmed, UNVERIFIED when there was no confident match—. With that your code decides whether the address is good enough for the file or whether it's worth asking for a proof of address.
The front is required; the back is optional and adds the keys and the cross-checks. Both images go in Base64 (JPG or PNG).
personalInfoFirst and last names separately, date of birth, sex, CURP and elector key. No manual transcription and no splitting name strings by hand.addressNormalizedThe address broken into fields and validated against the catalog, with its geocodingStatus. Alongside the original printed text in address, in case you need it as-is.securityThe keys from the back: cic, ocr, citizenIdentifier, the full mrz and the qr when the model carries one. These are what you need to validate against the Lista Nominal.validityRegistration, emission and validity years, plus the emission number. What's printed on the document — the real validity is confirmed by the Lista Nominal.electoralGeographyElectoral state, municipality, section and locality. Useful to cross-check against the Lista Nominal response.files[]The holder's photograph cropped from the document, in Base64. It's what you pass to face match to confirm the person presenting it is the one on the document.The credential model also comes back —D, E, F, G, H or I— because each model carries different security features and different keys. Your code doesn't have to guess which one it's dealing with.
A credential stores the same data in three places: printed on the front, encoded in the machine-readable zone on the back and, depending on the model, in the QR code. When you send the back, we read all three and contrast them.
| Field | What it contrasts |
|---|---|
mrzValidation.dateOfBirth | The printed date of birth against the one encoded in the MRZ. |
mrzValidation.sex | The printed sex against the one in the MRZ. |
mrzValidation.validity | The printed validity year against the one in the MRZ. |
mrzValidation.emission | The printed emission number against the one in the MRZ. |
mrzValidation.cic | The printed CIC against the one in the MRZ. |
mrzValidation.name | The printed name against the one in the MRZ. |
qrValidation.* | CIC, OCR and citizen identifier from the QR against what's printed. Arrives as null if the model has no readable QR. |
Each flag comes back true or false. A false doesn't by itself rule the credential fake —an OCR can get a blurry surname wrong— but it points at exactly which field doesn't line up so you can review it instead of rejecting the whole document blindly.
There are two categories, and the difference matters for your invoice: hard limits are rejected before any processing and aren't charged; recommendations aren't enforced —an imperfect photo still gets processed—.
| What | Limit | If it isn't met |
|---|---|---|
| Format | JPEG · PNG | INVALID_REQUEST, no charge |
| Size per image | up to 12 MB in Base64 | INVALID_REQUEST, no charge |
| Total request size | up to 28 MB | INVALID_REQUEST, no charge |
| Resolution, framing and lighting | recommendation, not enforced | processed anyway; if it can't be read, IMAGE_UNREADABLE and it is charged |
Watch the size: the 12 MB limit applies to the string already encoded in Base64, and Base64 inflates a file by about a third. A 9 MB JPG on disk lands near 12 MB once encoded. In practice you won't get close: a well-taken phone photo weighs between 1 and 3 MB, and the lighter it is, the faster the round trip.
None of this is enforced —a photo that doesn't comply still gets processed— but it's what raises the first-try read rate.
| Parameter | Target | Why |
|---|---|---|
| Resolution | 1280 px wide or more minimum 1024 px | At that width the text on the credential reads cleanly. Any modern phone exceeds it easily. |
| The credential in frame | more than 90% | What matters isn't the photo's pixels, it's the ones that land on the credential. A 4000 px photo where the credential takes up a fifth of the frame has less usable detail than a 1280 px one where it fills it. |
| Format and size | JPG · 1–3 MB | The JPG straight out of the camera is enough. Re-saving it degrades it at every step; send it exactly as it came. |
IMAGE_UNREADABLE, not invented data. That case did consume compute and is charged — with the response flags you can ask the user for exactly that photo again.Same envelope as every OrigoID service: status, type, data. The front is required; send the back too if you want the keys and the cross-checks.
const res = await fetch("https://api.origoid.com/mex/id/v1/voter-id-extractions", { method: "POST", headers: { "x-api-key": process.env.ORIGOID_API_KEY, "content-type": "application/json", }, body: JSON.stringify({ front: frontBase64, back: backBase64, // optional: adds keys and cross-checks }), }); const { data } = await res.json(); const dom = data.addressNormalized; if (dom.geocodingStatus === "VERIFIED") saveToFile(dom);
import os, requests res = requests.post( "https://api.origoid.com/mex/id/v1/voter-id-extractions", headers={ "x-api-key": os.environ["ORIGOID_API_KEY"], "content-type": "application/json", }, json={"front": front_base64, "back": back_base64}, timeout=30, ) data = res.json()["data"] address = data["addressNormalized"]
curl -X POST https://api.origoid.com/mex/id/v1/voter-id-extractions \ -H "x-api-key: $ORIGOID_API_KEY" \ -H "content-type: application/json" \ -d '{"front": "<base64>", "back": "<base64>"}'
{
"status": "OK",
"type": "SUCCESS",
"data": {
"model": "E",
"address": { "addressLine1": "AV LAGO ALBERTO 320 TORRE CENTRAL 503", … },
"addressNormalized": {
"geocodingStatus": "VERIFIED",
"street": "Avenida Lago Alberto",
"exteriorNumber": "320",
"interiorNumber": "Torre Central 503",
"neighborhood": "Anáhuac",
"zipCode": "11320",
"municipality": "Miguel Hidalgo",
"state": "Ciudad de México"
},
"mrzValidation": { "cic": false, "name": true, … }
},
"billable": true
}
| type | What it means | Decision |
|---|---|---|
SUCCESS | The document was read. Check geocodingStatus and the MRZ/QR flags to decide whether the file is complete. | Proceed |
IMAGE_UNREADABLE | The image is blurry or obstructed. Ask for just that photo again, with better light and no glare. | Review |
DOCUMENT_NOT_IDENTIFIED | What arrived isn't a recognized voter credential. Usually another ID or the wrong photo. | Reject |
Technical errors arrive separately with status: "ERROR": an image that isn't JPG/PNG, a file over 12 MB or a body past 28 MB are rejected as INVALID_REQUEST with billable: false — no charge. On the other hand, if the image was processed and came out unreadable, it did consume compute and is billed: we'd rather tell you now than surprise you on the invoice.
The OCR digitizes the document and solves the address; the Lista Nominal confirms the credential is valid; face match confirms it's the person. Same envelope on all three.
From the front: first and last names separately, date of birth, sex, CURP, elector key, the address and the holder's photograph. From the back: cic, ocr, citizenIdentifier, the MRZ and the QR depending on the model. Plus registration, emission and validity years, the electoral geography and the detected model (D, E, F, G, H or I).
Twice over: the printed text as-is in address, and in addressNormalized split into street, exterior and interior number, neighborhood, ZIP, municipality, state and country, with abbreviations expanded and validated against the official catalog. The geocodingStatus tells you the match confidence: VERIFIED, PARTIAL or UNVERIFIED. These are address components, not coordinates.
It isn't required. With the front you already get the printed data and the photo. The back adds the keys (CIC, OCR, identifier) and enables the MRZ and QR cross-checks against what's printed.
It flags inconsistencies. With the back, it contrasts what's printed against the MRZ and the QR and marks it field by field in mrzValidation and qrValidation. A false tells you what doesn't line up; it isn't a ruling of forgery by itself, it's the signal to review.
No. They're processed for the extraction and discarded on response: no document database. Zero retention across all our regions.
One extraction = 1 credit, whether you send just the front or front and back. You start free with courtesy credits, no card, and the price drops with your volume. All plans published.
Courtesy credits to try the OCR with real credentials in production. Access is enabled after a brief validation of your company.