# Generate Bearer Token Source: https://docs.origoid.com/en/api-reference/authentication/generate-bearer-token /openapi.json post /auth/token **Credits:** Free — authentication endpoint. Issues a short-lived Bearer JWT from valid API Key or Basic credentials. The token can then be sent as `Authorization: Bearer ` on subsequent requests instead of resending your long-lived API Key. Useful for handing access to a downstream client without sharing your primary credentials. **Auth:** API Key (`x-api-key`) or HTTP Basic. Bearer tokens cannot issue new tokens (no token chaining). **Body:** entirely optional. Send `{}` to get a token with default lifetime. **Lifetime:** controlled by `expireAfter` (seconds). When omitted, the gateway uses its configured default (currently 3600). When present, it must be between 1 and the configured maximum (currently 3600). Values outside that range are rejected with `OUT_OF_RANGE`. **Revocation:** tokens are stateless and self-expiring. There is no revocation endpoint — if a token is compromised, rotate the underlying API Key. The next snapshot reload propagates the rotation across all gateway instances within ~5 minutes. # CEP / SPEI Payment Validation Source: https://docs.origoid.com/en/api-reference/banking/cep-spei-payment-validation /openapi.json post /mex/banking/v1/cep-validations **Credits:** 1 per call. Validates a Mexican SPEI transfer against Banco de México's official **CEP** service ("Comprobante Electrónico de Pago") and returns the verified transfer details together with Banxico's cryptographic proof. Use it to confirm that a SPEI payment actually settled, for reconciliation, payout confirmation, or fraud checks on a claimed transfer. # Global Biometric Face Match Source: https://docs.origoid.com/en/api-reference/biometrics/global-biometric-face-match /openapi.json post /global/biometrics/v1/face-matches **Credits:** 1 per call. Compares two facial images and returns a similarity score (0–100) plus a binary match/no-match decision. Typical use is 1:1 verification between a live selfie and the photograph on an ID document. Use this endpoint to confirm that the person presenting an ID is the same person depicted on it. # Liveness Detection Source: https://docs.origoid.com/en/api-reference/biometrics/liveness-detection /openapi.json post /global/biometrics/v1/liveness-checks **Credits:** 1 per call. Determines whether a selfie shows a real, live person in front of the camera (`isLive: true`) or a spoofing attempt (`isLive: false`). Returns: - **`isLive`**: the decision. `livenessScore` (0–100) is the fused output of the anti-spoofing models; the decision threshold is **62**. - **`confidence`**: how far the score landed from the threshold — `HIGH` (25 points or more), `MEDIUM` (10 or more) or `LOW`. - **`selfieAnalysis`**: quality and attribute labels of the evaluated face (image quality, orientation, eyes open, glasses, face covered, actionable `issues[]`). Same object as `matchFaces`, so both endpoints share one integration. **Multiple people in frame.** By default a selfie with another person present returns `MULTIPLE_FACES_DETECTED`. Send `allowMultipleFaces: true` to evaluate the largest face in the image instead (the person holding the phone); `selfieAnalysis` describes that same face. Only faces of at least 25% of the main face's area count as another person — people in the background do not. **Decision rule.** Accept only `type: SUCCESS` with `isLive: true`. `NO_FACE_DETECTED`, `MULTIPLE_FACES_DETECTED` and `IMAGE_UNREADABLE` also return `isLive: false`, but nothing was evaluated — `data` always carries the full object so you can read `data.isLive` without branching on `type`. Detecting a spoofing attempt is a `SUCCESS`: the service did its job. **Images.** JPG or PNG. The image is downscaled internally; no client-side resizing is needed. For best results send at least 1600 px on the longest side at JPEG quality 80 or better. The result is a liveness decision only; it does not verify identity — pair it with `matchFaces` for that. `INVALID_REQUEST` returns every problem found in `errors[]` at once (cumulative), so you can fix them in one pass. **`selfieAnalysis` reference.** Describes the evaluated face. Labels are derived from the face-analysis provider as follows: | Field | Values | Rule | |---|---|---| | `imageQuality` | `excellent` · `good` · `poor` | `excellent`: brightness ≥ 75 and sharpness ≥ 75 · `good`: both ≥ 50 · `poor`: otherwise. In practice `poor` almost always means a dark image, not an out-of-focus one. | | `detectionConfidence` | `high` · `medium` · `low` | `high` ≥ 95 · `medium` ≥ 80 · `low` below 80. | | `orientation` | `sideways` · `looking_left` · `looking_right` · `looking_up` · `looking_down` · `tilted` · `front` | Evaluated in that order, first match wins: `sideways` abs(yaw) ≥ 25° · `looking_left` yaw ≥ 10° · `looking_right` yaw ≤ −10° · `looking_up` pitch ≥ 20° · `looking_down` pitch ≤ −20° · `tilted` abs(roll) ≥ 15° · otherwise `front`. | | `eyesOpen`, `mouthOpen`, `wearingGlasses`, `wearingSunglasses`, `faceCovered` | `true` · `false` · `null` | `null` when the attribute could not be determined. `mouthOpen` is informational and never produces an issue. | `issues[]` lists every condition detected on the face. All possible values: | Value | Condition | |---|---| | `poor_image_quality` | `imageQuality` is `poor` | | `not_facing_camera` | `orientation` is not `front` | | `eyes_closed` | `eyesOpen` is `false` | | `face_covered` | `faceCovered` is `true` — mask, scarf, hand, hair over the eyes, face partly out of frame | | `wearing_glasses` | `wearingGlasses` is `true` — informational, not a capture problem | | `wearing_sunglasses` | `wearingSunglasses` is `true` | | `low_detection_confidence` | `detectionConfidence` is `low` | In production about 4 in 10 legitimate selfies carry at least one issue — treat `issues[]` as retry hints, not as rejection criteria. # OFAC Sanctions List Check Source: https://docs.origoid.com/en/api-reference/compliance/ofac-sanctions-list-check /openapi.json post /global/compliance/v1/ofac-searches **Credits:** 1 per call. Searches the consolidated OFAC sanctions lists for the provided name. Coverage includes: - **SDN consolidated** — Specially Designated Nationals (general SDN, plus sub-programs `SDGT` Specially Designated Global Terrorists and `SDNTK` Specially Designated Narcotics Traffickers / Kingpin Act). - **Non-SDN consolidated** — Foreign Sanctions Evaders (`FSE`), Sectoral Sanctions Identifications (`SSI`), Correspondent Account / Payable-Through Account restrictions (`CAPTA`), Menu-Based Sanctions (`NS-MBS`), Iran Sanctions Act non-SDN (`NS-ISA`), Palestinian Legislative Council (`NS-PLC`). - **UN consolidated sanctions** — surfaced as `OFAC_UN` for clients who want a single endpoint covering both US and UN screening. Each match carries the originating list (`listType`), the sanction programs that apply, entity type (individual, entity, vessel, aircraft), full aliases and any compliance remarks published with the record. **Risk Level Matrix (`riskLevel`):** - `NONE` (envelope `type: SUCCESS`): no matches at or above `minSimilarityScore`. Safe for automated approval from an OFAC perspective. - `CRITICAL` (envelope `type: SUCCESS`): one or more matches found in **any** OFAC or UN sanctions list. Treat as a hard block, reject the relationship, and file the SAR (Suspicious Activity Report) required by your jurisdiction. There are intentionally only two levels. AML best practice treats any sanctions list hit — SDN, sectoral, informational, anywhere — as a binding stop. Surfacing intermediate gradations (HIGH / MEDIUM / LOW) misleads the client into believing some hits are merely advisory and is the most common cause of regulatory findings against KYC systems. The `listType` and `complianceDetails.programs` of every match are still surfaced so your compliance team can apply finer policy on top of the binary verdict. **Multi-match handling:** common names can produce dozens of fuzzy hits. The response returns **every record at or above `minSimilarityScore`**, sorted by `similarityScore` descending. There is no fixed truncation, so a genuine hit will never be hidden by a cap. If the volume of matches is higher than your review process can absorb, raise `minSimilarityScore` to tighten the match. `data.totalMatchesFound` mirrors `matches[].length` and is provided for convenience. **Which variant matched:** every entry in `matches[]` carries a `matchedOn` object indicating whether the query matched the canonical `entityName` or one of the record's aliases: - `matchedOn.type` is either `entityName` (the query matched the canonical name) or `alias` (the query matched an alias on the record). - `matchedOn.value` is the alias text that produced the hit when `type` is `alias`, and `null` when `type` is `entityName`. Why this matters: OFAC sometimes lists a person under a family member's record (e.g. `OSEGUERA CERVANTES, Nemesio` — better known as El Mencho — is published as an alias of the canonical `OSEGUERA CERVANTES, Ruben`). Without `matchedOn` a reviewer scanning the response would see `Ruben` with score 100 and discount the hit. With `matchedOn` the alias is surfaced explicitly so the reviewer can confirm the right person was matched. **Multi-identifier matching:** `name` is required, but you can pass `passportNumber` and/or `nationalIdNumber` alongside it to tighten the match. Each extra identifier you provide is used to: - **Boost the score** when it agrees with the record (record's passport or national ID matches the one you sent → +5 added to `similarityScore`, capped at 100). - **Downgrade or filter the match** when it contradicts the record (different passport or national ID → −20 points). If the final score falls below `minSimilarityScore`, the record is excluded from `matches[]` entirely. - **Stay neutral** when the record does not publish that identifier (most OFAC records don't have a passport number, for example). Missing data never penalises a match — only contradicting data does. A `matchedOn` entry per match always carries an `identifiersMatched` array — for example `name` and `passportNumber` — listing exactly which fields agreed, so the reviewer can audit the confidence behind a hit. **Practical coverage of the identifier boost:** OFAC publishes passport numbers and national IDs on a minority of records — most often on non-Mexican subjects (Iranian, Russian, Venezuelan, Cuban records carry passports more frequently). For purely Mexican counterparties the boost rarely applies in practice: OFAC does not publish CURP, RFC appears only on a handful of records, and most Mexican subjects are sanctioned with name + aliases only. Use these identifiers for cross-border screening where the upside is real; for MX-only KYC the `name` field is what does the work and the optional identifiers stay neutral. # PEP (Politically Exposed Person) Check Source: https://docs.origoid.com/en/api-reference/compliance/pep-politically-exposed-person-check /openapi.json post /mex/compliance/v1/peps-searches **Credits:** 2 per call. Searches the consolidated PEP (Politically Exposed Persons) database for a subject — covering active PEPs, former PEPs (`EX_PEP`), and their immediate family and close associates (`PEP_AFFINITY`, `EX_PEP_AFFINITY`). **Input flexibility — three valid invocation forms:** The endpoint accepts a search in any of three forms — pick the one that matches the data you have on hand. You may include `nationalIdNumber` alongside any of them to tighten the match. 1. **Single name (`name`)** — pass the full name as one string. Quick and convenient when you only have the full name as a single value. 2. **Separated name (`givenNames` + `firstSurname` + optional `secondSurname`)** — Mexican-style split. **Strongly recommended for best match quality**, because the matching engine can apply per-component logic that produces fewer false positives on compound first names and compound surnames. When using this form, both `givenNames` and `firstSurname` are required. 3. **Identifier only (`nationalIdNumber`)** — pass a CURP or RFC by itself when that is all you have. The search runs against records that publish the identifier. If more than one form is sent (for example `name` plus separated fields), the separated fields take precedence over `name`. The `nationalIdNumber` is normalised (trim, uppercase) and reported back per match in `matchedOn.identifiersMatched` and in the standalone `identifierMatch` field. **Risk Level Matrix (`riskLevel`):** Each match in `matches[]` has a base severity per its `listType`: | `listType` | base severity | |---|---| | `PEP_ACTIVE` | HIGH | | `EX_PEP` | MEDIUM | | `PEP_INACTIVE` | MEDIUM | | `PEP_AFFINITY` | MEDIUM | | `EX_PEP_AFFINITY` | LOW | The endpoint `riskLevel` is the **maximum** severity across `matches[]`: - `NONE` — no matches at or above `minSimilarityScore`. Safe for automated approval from a PEP perspective. - `LOW` — only `EX_PEP_AFFINITY` hits found (family or close associate of a former PEP). - `MEDIUM` — at least one `EX_PEP`, `PEP_INACTIVE`, or `PEP_AFFINITY` hit. - `HIGH` — at least one `PEP_ACTIVE` hit. Aligned with the categories that FATF Recommendation 12 and Mexican LFPIORPI flag for Enhanced Due Diligence (EDD). `riskLevel` is a screening signal intended to feed your compliance workflow. Combine it with `identifierMatch`, `similarityScore`, and the per-match `complianceDetails` we surface so your team can apply its own internal policy. **Which variant matched:** every entry in `matches[]` carries a `matchedOn` object identifying the canonical record that was hit and an `identifiersMatched` array — for example `name` and `nationalIdNumber` — listing which fields agreed. The standalone `identifierMatch` field exposes the precision of the identifier comparison (`EXACT`, `PARTIAL`, `MISMATCH`, or `NOT_PROVIDED` when no identifier was sent). Use these together to audit the confidence behind a hit. **How `similarityScore` is computed:** the score reflects **combined name + identifier confidence**, not name alone. A match's `similarityScore` is the higher of (a) the name match quality and (b) the strength of the identifier comparison — an `EXACT` `nationalIdNumber` match contributes a full-confidence score, a `PARTIAL` match contributes a strong-but-not-certain score, and `MISMATCH` / `NOT_PROVIDED` contribute nothing (the name drives the score in those cases). This means a search by `nationalIdNumber` alone still returns a high-confidence hit even though no name was supplied to compare — a CURP or RFC coincidence is a deterministic identity signal. Inspect `identifierMatch` and `matchedOn.identifiersMatched` to see *why* a given match scored the way it did. **Multi-match handling:** the response returns every match at or above `minSimilarityScore`, sorted by `similarityScore` descending. `data.totalMatchesFound` mirrors `matches[].length` and is provided for convenience. Use this endpoint as part of AML programs where screening counterparties against PEPs is part of your KYC workflow — common in regulated financial services, cross-border payments, and onboarding pipelines. # SAT Article 69-B Check (EFOS) Source: https://docs.origoid.com/en/api-reference/compliance/sat-article-69-b-check-efos /openapi.json post /mex/compliance/v1/sat-69b-searches **Credits:** 1 per call. Validates if an individual or legal entity is listed in the Mexican Tax Authority (SAT) Article 69-B blacklist. This list is specifically for EFOS (Empresas que Facturan Operaciones Simuladas), commonly known as 'Factureros' or shell companies involved in tax fraud and money laundering. **Business Rules (Search Priority):** 1. The client must provide EITHER a name (`name` / Razón Social) OR an exact identifier (`rfc`). 2. If `rfc` is provided, the backend performs a strict exact match. If only `name` is provided, the backend performs a highly restrictive text search. **Risk Level Matrix (`riskLevel` mapped to SAT Status):** - `NONE`: No matches found in the SAT 69-B list. Safe for automated approval. - `LOW`: The SAT status is **'Desvirtuado'** (Investigated but successfully proved innocence) or **'Sentencia Favorable'** (Won in court / cleared). Provided for audit trails and historical record. - `MEDIUM`: Reserved for intermediate risk states. Currently not produced by the SAT 69-B classification. - `HIGH`: The SAT status is **'Presunto'** (Currently under investigation for simulated operations). Extreme caution advised; usually triggers Enhanced Due Diligence (EDD) or temporal blocks. - `CRITICAL`: The SAT status is **'Definitivo'** (Confirmed shell company / EFOS). Legally binding block required for AML compliance. **Multi-match selection:** A taxpayer may appear in more than one record (e.g., first listed as `PRESUNTO`, later reclassified to `DEFINITIVO` or `SENTENCIA_FAVORABLE`). The top-level `riskLevel` reflects only the record with the most recent `publicationDateSat` (falling back to `publicationDateDof`). All historical records are still returned in `matches[]` so you can audit the full timeline. # SAT Article 69 Check Source: https://docs.origoid.com/en/api-reference/compliance/sat-article-69-check /openapi.json post /mex/compliance/v1/sat-69-searches **Credits:** 1 per call. Validates if an individual or legal entity is listed in the Mexican Tax Authority (SAT) Article 69 blacklist. This endpoint covers all sub-lists of Art. 69. Note: This endpoint does NOT evaluate Article 69-B (EFOS/simulated operations). **Business Rules (Search Priority):** 1. The client must provide EITHER a name (`name` / Razón Social) OR an exact identifier (`rfc`). 2. If `rfc` is provided, the backend performs a strict exact match. If only `name` is provided, the backend performs a highly restrictive text search. **Risk Level Matrix (`riskLevel`):** - `NONE`: No matches found. Safe for automated approval. - `LOW` (Informativo / Sin Riesgo Operativo): The subject has historical or administrative records but is legally operating. **Lists:** Condonados (Todos los decretos/artículos), Reducción Art. 74 CFF, Retorno de Inversiones, Entes Públicos y de Gobierno Omisos. - `MEDIUM` (Riesgo Financiero / Morosidad): The subject has active enforceable debts or the SAT declared them insolvent/uncollectible. **Lists:** Firmes, Exigibles, Cancelados (Incosteabilidad / Insolvencia). - `HIGH` (Riesgo Operativo Grave): The subject cannot be found by authorities or their digital billing seals (CSD) have been revoked, halting their operations. **Lists:** No Localizados, CSD Sin Efectos. - `CRITICAL` (Riesgo Legal / Fraude Penal): The subject has criminal convictions related to tax crimes. **Lists:** Sentencias. **Multi-match selection:** When the subject appears in more than one SAT list, the top-level `riskLevel` reflects only the record with the most recent `publicationDate`. All historical records are still returned in `matches[]` so you can audit the full timeline. # Email Deliverability & Fraud Validation Source: https://docs.origoid.com/en/api-reference/email/email-deliverability-&-fraud-validation /openapi.json post /global/email/v1/email-validations **Credits:** 1 per call. Validates an email address for deliverability and risk. Returns the normalized address, deliverability verdict (`deliverable`, `risky`, `undeliverable`), a quality score (0–100), a toxicity score, and a set of boolean verdicts (`isFree`, `isDisposable`, `isRoleAccount`, `isFull`, `isCatchAll`, `isToxic`). Use this endpoint at signup time to reject typos and disposable addresses before they enter your database, reducing bounce rates on transactional email and fraud signals from throwaway accounts. **`riskLevel` reference** — standardized scoring you can branch on: | Level | When | |---|---| | `NONE` | Mailbox is deliverable and not flagged as disposable or toxic. | | `MEDIUM` | Domain is catch-all — accepts every address, so the specific mailbox cannot be confirmed to exist. | | `HIGH` | Deliverability is `risky` or `unknown` (e.g. mail server rejects probes), OR the domain belongs to a disposable / temporary email provider (Mailinator, 10minutemail, Guerrillamail, etc.). | | `CRITICAL` | Mailbox is `undeliverable` (does not exist or is full), OR the address scores above 40 on the toxicity index (associated with spam / abuse). | The `verdicts` object always carries the underlying signals (`isFree`, `isDisposable`, `isRoleAccount`, `isFull`, `isCatchAll`, `isToxic`) so you can apply your own scoring on top if you need finer granularity. # CSF Data Extraction & Validation (Multi-input) Source: https://docs.origoid.com/en/api-reference/fiscal/csf-data-extraction-&-validation-multi-input /openapi.json post /mex/fiscal/v1/csf-extractions **Credits:** 1 per call. Extracts structured data from a Constancia de Situación Fiscal (CSF) — the official PDF document issued by SAT that proves a taxpayer's fiscal situation. You can submit the CSF as a base64-encoded file (PDF/PNG/JPG) and get back the full content as JSON, or alternatively pass RFC + CIF (the tax-certificate code) to retrieve the same data directly from SAT's public QR validator. Returns the legal name, address, fiscal regime, economic activities, registration date, and tax obligations. Use this endpoint to automate vendor onboarding and to keep your records of partners' fiscal data continuously up to date. # RFC Status and LCO Validation Source: https://docs.origoid.com/en/api-reference/fiscal/rfc-status-and-lco-validation /openapi.json post /mex/fiscal/v1/rfc-validations **Credits:** 1 per call. Validates the structure and current status of a Mexican RFC (Registro Federal de Contribuyentes) against the SAT registry. Returns the taxpayer type (individual or legal entity), registration status, and the official SAT message. Use this endpoint to confirm that the RFC your customer provided is real, well-formed, and currently active with SAT before extending credit, issuing invoices, or signing contracts. **Risk Level Matrix (`riskLevel`):** - `NONE` (envelope `type: SUCCESS`): RFC exists in the SAT padrón and is authorized to issue / receive invoices (`isBillable: true`). Safe for automated approval. - `LOW` (envelope `type: RFC_NOT_INVOICEABLE`): RFC exists in the SAT padrón but is **restricted** for invoicing — typically a legal entity that has not completed its fiscal-status onboarding or has been suspended. The taxpayer is real, just not currently invoiceable. - `CRITICAL` (envelope `type: RFC_NOT_FOUND`): RFC is **not registered** in the SAT padrón. Either it was never issued, or the value provided is a typo or fabricated. Do not extend credit, issue invoices, or accept as a counterparty without further verification. `MEDIUM` and `HIGH` are not produced by this endpoint — the SAT padrón only distinguishes the three outcomes above. # Validate CFDI (Electronic Invoice) Source: https://docs.origoid.com/en/api-reference/fiscal/validate-cfdi-electronic-invoice /openapi.json post /mex/fiscal/v1/cfdi-validations **Credits:** 1 per call. Validates a CFDI (Comprobante Fiscal Digital por Internet) — Mexico's mandatory electronic invoice — by checking its current status with SAT. Returns whether the CFDI is currently valid (`VALID`) or cancelled (`CANCELED`), the cancellation status (e.g. requires receiver acceptance), and the fiscal effect (`INCOME`, `EXPENSE`, `TRANSPORT`, `PAYROLL`, `PAYMENT`). Use this endpoint when reconciling supplier invoices, processing expense reports, or ensuring that the invoices you receive are real and not later cancelled by the issuer without your knowledge. # ID Data Extraction (OCR & Geocoding) Source: https://docs.origoid.com/en/api-reference/ine/id-data-extraction-ocr-&-geocoding /openapi.json post /mex/id/v1/voter-id-extractions **Credits:** 1 per call. Performs OCR on the front and back of a Mexican voter ID (INE / IFE) and returns the structured data printed on the credential: full name, CURP, voter key (CIC / OCR), address, photograph metadata, the document model variant (D, E, F, G, H, I — current and recent INE designs), and the MRZ read from the back when present. What sets this endpoint apart is **integrated address normalization + geocoding**: the address printed on the INE is rarely clean — abbreviations, missing colonia, inconsistent casing. We normalize and enrich it automatically. You get back not only the raw address text, but also: - **`addressNormalized`**: the printed INE address, normalized and enriched (corrected casing, expanded abbreviations, validated postal code, and neighborhood / municipality / state matched from the official catalog). The `geocodingStatus` field reports the match confidence: `VERIFIED` (house- or street-level match), `PARTIAL` (locality or postal-code match), or `UNVERIFIED` (no confident match). - **`electoralGeography`**: derived electoral district, federal entity, and polling section — useful for cross-checking with `validateVoterList`. - **Document model detection** (D, E, F, G, H, I) and per-model security feature validation. - **MRZ + QR cross-validation**: when the back contains MRZ and QR, we read both and confirm they agree with the printed fields. Mismatches are flagged. Use this endpoint to digitize voter ID capture without manual transcription, and to obtain a geo-enriched address record in a single call — eliminating a separate geocoding step in your KYC flow. # INE QR Code Data Extraction Source: https://docs.origoid.com/en/api-reference/ine/ine-qr-code-data-extraction /openapi.json post /mex/id/v1/qr-extractions **Credits:** 2 per call. Decrypts and parses the QR codes printed on Mexican voter IDs (INE models G, H, I and J). The two QRs on the back contain RSA-signed payloads with the holder's full record (name, CURP, voter key, address, signature). This endpoint decrypts both QRs and merges the result. Use this endpoint as a tamper-evidence check: if the QR decrypts successfully and matches the printed data, the credential is highly likely to be authentic. **Models I and J (2026+)** additionally carry a self-identified `gender` (may be `NB`), `selfIdentification` (autoadscripción, e.g. `INDÍGENA`), `ethnicGroup` (indigenous people), and the printed `address`. These are `null` on models G/H. `sex` (from the CURP) and `dateOfBirth` are present on all models. # Validate Voter List Source: https://docs.origoid.com/en/api-reference/ine/validate-voter-list /openapi.json post /mex/id/v1/voter-list-validations **Credits:** 1 per call. Validates that a Mexican voter ID (INE / IFE) credential exists in INE's Lista Nominal — the official roll of registered voters — by sending CIC, OCR or ID number depending on the credential model. Returns a confirmation, the voter's polling section, and validity dates. Use this endpoint as part of KYC to verify that the voter ID presented by your customer is registered and valid (not stolen, not lost, not cancelled). # Universal Extraction (Proof of Address) Source: https://docs.origoid.com/en/api-reference/proof-of-address/universal-extraction-proof-of-address /openapi.json post /mex/documents/v1/proof-of-address-extractions **Credits:** 1 per call. Performs OCR on a Mexican proof-of-address document — utility bills (water, electricity, gas, internet, telephone) and bank statements — and returns the structured data printed on it. Returns: - **`provider`**: the issuing utility or institution (CFE, Telmex, Agua, etc.), so you can apply provider-specific business rules and recognize legitimate document layouts. - **`personalInfo`**: holder name as printed on the document. - **`address`**: full address as printed (street, exterior / interior number, neighborhood, municipality, state, postal code). - **`billing`**: issuance date, account number, period covered. - **`validations`**: flags about document age, document type detection confidence, and structural consistency checks. Use this endpoint to automate address verification in KYC flows. The extracted address can be cross-checked against the address your customer submitted at signup. # CURP Official Document (PDF) Source: https://docs.origoid.com/en/api-reference/renapo/curp-official-document-pdf /openapi.json post /mex/renapo/v1/curp-documents **Credits:** 2 per call. Retrieves the official RENAPO CURP document ("Constancia de la CURP") as a PDF, together with the full validated record and the CURP's RENAPO status — the **same status matrix as `curp-validations`** (active, homonymy, deceased, apocryphal, judicial suspension, inactive). Use it when you need the citizen's official, printable certificate, not just the validated data. The PDF is returned **inline as base64** in `data.files[0].content`, alongside the parsed identity fields. This is a **synchronous** call. Optionally pass `generateRfc: true` to also receive the deterministic `personalInfo.rfc` (computed from the CURP, no SAT call) — identical to `curp-validations`. **Why it differs from `curp-validations`:** this endpoint retrieves the actual document from RENAPO, so a response takes a little longer to return. It is priced at **2 credits** and has a lower rate limit than the high-volume `curp-validations`. # Retrieve CURP via Demographics Source: https://docs.origoid.com/en/api-reference/renapo/retrieve-curp-via-demographics /openapi.json post /mex/renapo/v1/curp-lookups **Credits:** 2 per call. Reconstructs a CURP from the four official input fields: given names, first surname, second surname, gender, date of birth, and birth state code. Calls RENAPO and returns the matching CURP plus the full personal record (same shape as `validateCurp`). Use this endpoint when your KYC form collects names and date of birth but not the CURP, and you need the CURP to file a financial product or report to regulators. The lookup uses RENAPO's strict matching — if any field is misspelled, no match is returned (`CURP_NOT_FOUND`). # Validate CURP Source: https://docs.origoid.com/en/api-reference/renapo/validate-curp /openapi.json post /mex/renapo/v1/curp-validations **Credits:** 1 per call. Validates a CURP (Clave Única de Registro de Población) against the official RENAPO registry and returns the full personal record associated with it: given names, surnames, gender, date of birth, birth state, document status (active, deceased, apocryphal, judicial suspension), and registration metadata. Optionally generates the associated 13-character RFC (Registro Federal de Contribuyentes) when `generateRfc: true` is sent. RFC generation is deterministic from CURP and does not call SAT. Use this endpoint when you have a CURP and need to confirm it is genuine, find out who owns it, or detect if the holder is deceased before extending a financial product. # Employment Status Source: https://docs.origoid.com/en/api-reference/social-security/employment-status /openapi.json post /mex/social-security/v1/imss-employment-status **Credits:** 1 per call. Returns the current IMSS employment status of a worker (identified by CURP + NSS): whether they are currently registered as employed, inactive (no current registration), the modality of registration, the registered employer's RFC, employer name, state, base salary, contributed days, and the period the IMSS report covers. Use this endpoint for income verification (lending, leasing), employment confirmation (background checks), or to detect overlapping employment when complying with employment regulations. # Get NSS Source: https://docs.origoid.com/en/api-reference/social-security/get-nss /openapi.json post /mex/social-security/v1/imss-nss-lookups **Credits:** 1 per call. Retrieves a worker's NSS (Número de Seguridad Social) from IMSS based on CURP. Returns the 11-digit NSS. Use this endpoint when onboarding employees for payroll or social-security registration: a CURP is far easier to collect than asking the candidate for their NSS card, which is frequently misplaced. # ISSSTE Record Source: https://docs.origoid.com/en/api-reference/social-security/issste-record /openapi.json post /mex/social-security/v1/issste-records **Credits:** 1 per call. Retrieves a government worker's full ISSSTE record by CURP — personal data, affiliation, pension regime, positions, contribution history, address, assigned clinic, and the official PDF (always included as base64 in `data.files[]` (kind `document`)). A retrieved record returns `type: SUCCESS` for an active worker. `PENSIONER`, `SCHOLAR`, `INACTIVE`, and `DECEASED` flag KYC-material standings; when more than one applies, precedence is `DECEASED` > `PENSIONER`/`SCHOLAR` > `INACTIVE`. The raw affiliation status and beneficiary type are always in `data.affiliation`, so new statuses ISSSTE may report never break the contract. `CURP_NOT_FOUND` when the CURP has no ISSSTE record. Dates are ISO `YYYY-MM-DD`; monetary amounts are strings (MXN). **Field presence:** every field is always present (stable contract) — absent values are `null` (objects/scalars, including empty source strings normalized to `null`) or `[]` (arrays), never omitted. Examples: `pensions` and `family` are `[]` when the worker has none; `terminationDate` and a history row's `endDate` are `null` while ongoing; binary assets in `data.files[]` follow array semantics — only assets actually produced are listed (absent ones omitted, not `null`). Unmigrated placeholder values from ISSSTE (e.g. address `POR ACTUALIZAR`, clinic `FAVOR DE ATENDER A ESTE TRABAJADOR` / clave `0409999`, state `ENTIDAD DESCONOCIDA`) are normalized to `null` (data not yet migrated). ISSSTE only exposes records for titulares (TRABAJADOR/PENSIONISTA/BECARIO); a CURP that is only a dependant (not a titular) returns `CURP_NOT_FOUND`. To retrieve a dependant, query the titular CURP and match `data.family[]` by the dependant CURP. `beneficiaryType` is derived authoritatively from the record (a DIRECT pension means PENSIONISTA even if the document labels them TRABAJADOR); each pension carries a derived `category` (DIRECT/SURVIVOR/INSURANCE/DISABILITY). # Authentication Source: https://docs.origoid.com/en/authentication Three supported methods. Send ONE per request. OrigoID accepts three authentication methods. Use whichever fits your stack — all are equally secure. ## API Key The simplest option. Send your key in a header: ```http theme={null} x-api-key: YOUR_API_KEY ``` Use it for server-to-server integrations. Never expose the key in front-end code. ## Basic auth Send credentials encoded in standard HTTP Basic format: ```http theme={null} Authorization: Basic BASE64_OF_USERNAME_AND_PASSWORD ``` Useful for legacy systems or tools that already speak Basic. ## Bearer (JWT) Exchange your API Key or Basic credentials for a short-lived token via `POST /auth/token`: ```bash theme={null} curl -X POST https://api.origoid.com/auth/token \ -H "x-api-key: YOUR_API_KEY" \ -H "content-type: application/json" \ -d '{ "grant_type": "client_credentials" }' ``` Response: ```json theme={null} { "status": "OK", "type": "SUCCESS", "data": { "access_token": "", "token_type": "Bearer", "expires_in": 3600 } } ``` Then send the token on every subsequent request: ```http theme={null} Authorization: Bearer ``` Useful when you want to delegate access to a downstream client without sharing your API Key. ## Authentication failures When authentication fails the response is `HTTP 401` with the standard envelope: ```json theme={null} { "status": "ERROR", "type": "UNAUTHORIZED", "message": "Invalid credentials", "data": null, "transactionId": "...", "processedAt": "2026-03-19T10:00:00-06:00", "billable": false } ``` A failure can mean: missing header, invalid credentials, IP not allowed, or endpoint not available for your account. We use the same `type` for all of them to avoid leaking which credential was wrong. ## Good practices * Store your key in environment variables, never in source code. * One key per service or environment — simpler rotation and clearer audit. * Configure an IP allow-list if your traffic comes from fixed IPs. * If you suspect a leak, email [support@origoid.com](mailto:support@origoid.com) immediately to rotate. ## Browser-based integrations (CORS) If your application needs to call OrigoID directly from a browser (single-page app, widget), email [support@origoid.com](mailto:support@origoid.com) with the list of domains that should be allowed (`https://app.yourdomain.com`, etc.). We will configure the allowed origins for your account so cross-origin requests succeed. By default the API does not return CORS headers — server-to-server calls do not need them. # Reference catalogs Source: https://docs.origoid.com/en/catalogs Stable code tables used across OrigoID responses. This page consolidates the catalogs of stable codes that appear inside responses from various OrigoID endpoints. These codes do not change between versions — you can safely rely on them in your business logic. # Catalogs #### 1. CURP Status (`statusCurp`) Status code returned by RENAPO inside `personalInfo.statusCurp` and similar fields. | `statusCurp` | State | Description | | :----------- | :------- | :----------------------------------------------------------------------------- | | `AN` | Active | Normal registration (Alta Normal). Valid and current. | | `AH` | Active | Homonymy alert (Alta con Homonimia). First 16 chars match another CURP. | | `RCC` | Active | Change affecting CURP (Registro con Cambio). Data correction modified the key. | | `RCN` | Active | Change not affecting CURP. Minor data correction. | | `BAP` | Inactive | Apocryphal document (Baja por documento apócrifo). | | `BSU` | Inactive | Unused (Baja sin uso). Requires reactivation at a RENAPO module. | | `BD` | Inactive | Deceased (Baja por defunción). | | `BDM` | Inactive | Administrative cancellation (Baja administrativa). | | `BDP` | Inactive | Adoption-related cancellation (Baja por adopción). | | `BJD` | Inactive | Judicial cancellation (Baja judicial). | #### 2. Probatory Document (`docProbatorio`) Code identifying which civil document supports the CURP registration. | `docProbatorio` | Document | | :-------------- | :--------------------------------------------------------------------- | | `1` | Birth Certificate (Acta de Nacimiento) | | `3` | Migration Document (Documento Migratorio) | | `4` | Naturalization Certificate (Carta de Naturalización) | | `7` | Mexican Nationality Certificate (Certificado de Nacionalidad Mexicana) | | `8` | SEGOB Processing (Trámite ante SEGOB) | #### 3. Mexican State Codes (`entidad` / `claveEntidad`) Two-letter codes used in CURP, RFC composition, INE registration, and addresses. State names preserved in Spanish (proper nouns). | `entidad` | State | `entidad` | State | | :-------- | :------------------ | :-------- | :------------------------------------ | | `AS` | Aguascalientes | `QR` | Quintana Roo | | `BC` | Baja California | `SP` | San Luis Potosí | | `BS` | Baja California Sur | `SL` | Sinaloa | | `CC` | Campeche | `SR` | Sonora | | `CL` | Coahuila | `TC` | Tabasco | | `CM` | Colima | `TS` | Tamaulipas | | `CS` | Chiapas | `TL` | Tlaxcala | | `CH` | Chihuahua | `VZ` | Veracruz | | `DF` | Ciudad de México | `YN` | Yucatán | | `DG` | Durango | `ZS` | Zacatecas | | `GT` | Guanajuato | `NE` | Born Abroad (Nacido en el Extranjero) | | `GR` | Guerrero | `MC` | Estado de México | | `HG` | Hidalgo | `MN` | Michoacán | | `JC` | Jalisco | `MS` | Morelos | | `NT` | Nayarit | `NL` | Nuevo León | | `OC` | Oaxaca | `PL` | Puebla | | `QT` | Querétaro | | | #### 4. IMSS Coverage Modalities (`modalidad`) IMSS modality codes describe the worker's affiliation type. Official descriptions preserved in Spanish for legal traceability with the institute. | `modalidad` | Description | | :---------- | :--------------------------------------------------------------------------------------------------------------- | | `10` | Urban permanent and temporary workers (Trabajadores permanentes y eventuales de la ciudad) | | `13` | Rural temporary workers (Trabajadores eventuales del campo) | | `14` | Rural permanent workers (Trabajadores permanentes del campo) | | `17` | Re-entry rural temporary workers (Reingreso de trabajadores eventuales del campo) | | `18` | Re-entry rural permanent workers (Reingreso de trabajadores permanentes del campo) | | `30` | Re-entry urban permanent and temporary workers (Reingreso de trabajadores permanentes y eventuales de la ciudad) | | `31` | Voluntary continuation in the mandatory regime (Continuación voluntaria al régimen obligatorio) | | `32` | Independent workers (Trabajadores independientes) | | `33` | Domestic workers (Trabajadores domésticos) | | `34` | Federal or state government workers (Trabajadores del gobierno federal o estatal) | | `35` | IMSS-affiliated students (Estudiantes afiliados al IMSS) | | `36` | Scholarship holders or social programs (Becarios o programas sociales) | | `40` | Voluntary continuation in the mandatory regime (alternate) | | `42` | Individual employer with domestic workers (Patrón persona física con trabajadores domésticos) | | `43` | International organization workers (Trabajadores de organismos internacionales) | | `44` | Seasonal or fixed-term workers (Trabajadores por temporada o tiempo determinado) | | `45` | Re-entered retirees in mandatory regime (Pensionados reincorporados al régimen obligatorio) | | `46` | Construction temporary workers (Trabajadores eventuales de la construcción) | | `47` | Trust workers with special regime (Trabajadores de confianza con régimen especial) | | `48` | Affiliated municipal public servants (Servidores públicos municipales afiliados) | | `50` | Voluntary continuation with extended coverage (Continuación voluntaria con cobertura extendida) | | `51` | Voluntary affiliation for students or apprentices (Afiliación voluntaria para estudiantes o aprendices) | | `60` | Special social incorporation regime — RESICO (Régimen especial de incorporación social) | | `70` | IMSS-Bienestar or community programs modality | | `72` | Inter-agency public agreement with IMSS (Convenio entre dependencias públicas y el IMSS) | #### 5. SAT Article 69 Sub-lists (`listType`) | `listType` | Sub-list (SAT) | Risk Level | | :----------------------------- | :---------------------------------------------- | :--------- | | `SAT_69_FIRMES` | Firmes | MEDIUM | | `SAT_69_CANCELADOS` | Cancelados (Insolvencia, general) | MEDIUM | | `SAT_69_CANCELADOS_07_15` | Cancelados 2007–2015 (Art. 146-A) | MEDIUM | | `SAT_69_EXIGIBLES` | Exigibles | MEDIUM | | `SAT_69_NO_LOCALIZADOS` | No Localizados | HIGH | | `SAT_69_CSD_SIN_EFECTOS` | Certificados de Sello Digital (CSD) sin Efectos | HIGH | | `SAT_69_SENTENCIAS` | Sentencias | CRITICAL | | `SAT_69_REDUCCION_74_CFF` | Reducción Art. 74 CFF | LOW | | `SAT_69_CONDONADOS_07_15` | Condonados 2007–2015 (Decreto) | LOW | | `SAT_69_CONDONADOS_146B_CFF` | Condonados Art. 146-B CFF | LOW | | `SAT_69_CONDONADOS_DECRETO` | Condonados por Decreto | LOW | | `SAT_69_CONDONADOS_21_CFF` | Condonados Art. 21 CFF | LOW | | `SAT_69_RETORNO_INVERSIONES` | Retorno de Inversiones | LOW | | `SAT_69_ENTES_PUBLICOS_OMISOS` | Entes Públicos y de Gobierno Omisos | LOW | #### 6. SAT Article 69-B Status (`complianceDetails.status`) The SAT 69-B endpoint always returns `listType: SAT_69B`. The granular state lives in `complianceDetails.status`: | `status` | Description | Risk Level | | :-------------------- | :------------------------------------------------------------ | :--------- | | `PRESUNTO` | Currently under investigation for simulated operations (EFOS) | HIGH | | `DEFINITIVO` | Confirmed shell company / EFOS | CRITICAL | | `DESVIRTUADO` | Investigated but successfully proved innocence | LOW | | `SENTENCIA_FAVORABLE` | Won in court / cleared by judicial ruling | LOW | #### 7. PEPs Categories (`listType`) These are OrigoID's stable taxonomy codes. They remain consistent over time so client integrations don't need to change when data sources evolve. | `listType` | Description | Risk Level | | :---------------- | :------------------------------------------------ | :--------- | | `PEP` | Politically Exposed Person — currently in office | HIGH | | `EX_PEP` | Former PEP — held office in the past | MEDIUM | | `PEP_AFFINITY` | Family member or close associate of an active PEP | MEDIUM | | `EX_PEP_AFFINITY` | Family member or close associate of a former PEP | LOW | #### 8. OFAC Lists (`listType`) We query the official OFAC sanctions lists in real time and consolidate them under the `listType` codes below. Each entry also exposes `complianceDetails.programs[]` (sanction program codes such as CUBA, IRAN, RUSSIA, etc. — these evolve continuously and are not statically catalogued) and `complianceDetails.entityType` (`INDIVIDUAL` | `ENTITY` | `VESSEL` | `AIRCRAFT`). | `listType` | Official OFAC list | Risk Level | | :------------- | :------------------------------------------------------------------------------------------ | :--------- | | `OFAC_SDN` | Specially Designated Nationals & Blocked Persons | CRITICAL | | `OFAC_NON_SDN` | Consolidated Non-SDN List | HIGH | | `OFAC_FSE` | Foreign Sanctions Evaders | CRITICAL | | `OFAC_NS_ISA` | Non-SDN Iran Sanctions Act List | HIGH | | `OFAC_SSI` | Sectoral Sanctions Identifications List | MEDIUM | | `OFAC_CAPTA` | Foreign Financial Institutions subject to Correspondent / Payable-Through Account Sanctions | HIGH | | `OFAC_NS_PLC` | Non-SDN Palestinian Legislative Council | LOW | #### 9. CFDI Effect (`effect`) Fiscal purpose of the invoice as classified by SAT. Each CFDI has exactly one effect. | `effect` | SAT code | Description | | :---------- | :----------- | :-------------------------------------------------------------------------------------- | | `INCOME` | I (Ingreso) | Sales / revenue invoice. Issuer received payment for goods or services. | | `EXPENSE` | E (Egreso) | Refund, credit note, or discount. Reverses or reduces a previous income invoice. | | `TRANSPORT` | T (Traslado) | Goods transport waybill (Carta Porte). Does not reflect a sale, only movement of goods. | | `PAYROLL` | N (Nómina) | Payroll receipt issued by an employer to an employee. | | `PAYMENT` | P (Pago) | Payment receipt complement (REP) for invoices paid in installments (PPD). | #### 10. CFDI Status (`status`) Current fiscal status of the CFDI according to SAT's verification service. | `status` | Description | | :--------- | :--------------------------------------------------------------------- | | `VALID` | Vigente. The CFDI is current and fiscally valid. | | `CANCELED` | Cancelada. The CFDI has been canceled and is no longer fiscally valid. | #### 11. CFDI Cancellation Status (`cancellationStatus`) Reflects SAT's 2022 cancellation rules ("cancelación con aceptación"), which require receiver consent in some scenarios. This field shows both pre-cancellation eligibility and post-cancellation outcome. | `cancellationStatus` | Description | | :------------------------------ | :--------------------------------------------------------------------------------------------------------- | | `NOT_CANCELABLE` | Cannot be canceled (e.g. payment complements, payroll receipts, or invoices linked to other active CFDIs). | | `CANCELABLE_WITHOUT_ACCEPTANCE` | The issuer may cancel unilaterally (small amounts, payroll corrections, etc.). | | `CANCELABLE_WITH_ACCEPTANCE` | The issuer can request cancellation, but the receiver must accept it within 72 hours. | | `CANCELED_WITHOUT_ACCEPTANCE` | Already canceled; acceptance was not required. | | `CANCELED_WITH_ACCEPTANCE` | Already canceled; the receiver approved the cancellation. | # Credits and usage Source: https://docs.origoid.com/en/credits How each call counts against your plan. OrigoID bills by **credits consumed**. Each plan includes a monthly credit allowance; each endpoint consumes a number of credits according to its processing complexity. This model is **fair**: lightweight operations consume fewer credits than operations that require deeper processing. You spend in proportion to the work performed. ## Credits per endpoint Most endpoints consume **1 credit** per call. A small set consume **2 credits** because they require additional processing. The exact credit consumption for each endpoint is shown on its page in the [API reference](/en/api-reference). This table evolves as we add new endpoints. ## When credits are charged A call consumes credits **only when the request was actually processed**: | Outcome | Credits | | ---------------------------------------------------------- | ------- | | `SUCCESS` | Yes | | Business results (`CURP_NOT_FOUND`, `CFDI_CANCELED`, etc.) | Yes | | `INVALID_REQUEST` (bad body) | No | | `UNAUTHORIZED` (auth failed) | No | | `RATE_LIMIT_EXCEEDED` | No | | `SERVICE_UNAVAILABLE` (upstream down) | No | | `INTERNAL_ERROR` (our bug) | No | The `billable` field in every response tells you whether that specific call counts. ## Monthly reset Credits reset on the first day of each calendar month (Mexico City time, `-06:00`). Unused credits do not roll over. ## Going over your allowance If you exceed your monthly allowance, your service continues uninterrupted by default. Excess usage is billed at your plan's overage rate. You will receive a notification when you cross 80% and 100% of your allowance. If you prefer a hard limit instead of overage billing, email [support@origoid.com](mailto:support@origoid.com) and we will configure it for your account. ## Need a custom plan? If your volume or use case does not fit our published tiers, email [support@origoid.com](mailto:support@origoid.com). # Response envelope Source: https://docs.origoid.com/en/envelope One shape across every endpoint. Learn it once, ship faster. Every OrigoID endpoint returns the same envelope. This consistency is intentional — your parser is one helper that works for every operation. ## Shape ```json theme={null} { "status": "OK | ERROR", "type": "TYPE_CODE", "message": "Human-readable summary", "data": { /* payload, or null on errors */ }, "errors": [ /* optional, present only on INVALID_REQUEST */ ], "transactionId": "550e8400-e29b-41d4-a716-446655440000", "processedAt": "2026-03-15T12:35:00-06:00", "billable": true } ``` ## Fields | Field | Type | Description | | --------------- | -------------- | ---------------------------------------------------------------------------- | | `status` | enum | `OK` when the request was processed; `ERROR` when it was rejected or failed. | | `type` | string | Stable result code. Use this for your business logic, not `message`. | | `message` | string | Human summary in English. For display only. | | `data` | object \| null | Endpoint-specific payload. `null` on errors. | | `errors` | array | Present only on `INVALID_REQUEST`. Lists per-field issues. | | `transactionId` | uuid | Unique request identifier. Reference it when contacting support. | | `processedAt` | ISO 8601 | Timestamp with Mexico City offset (`-06:00`). | | `billable` | boolean | Whether this call counts against your plan. | ### ErrorDetail (entries in `errors[]`) When `type` is `INVALID_REQUEST`, `errors[]` lists per-field issues. Each entry is: | Field | Type | Description | | --------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `field` | string | Dot-notation path to the offending input (e.g. `curp`, `address.street`). `body` when the issue applies to the request as a whole (malformed JSON, payload exceeds size limit, oneOf with all alternatives present). | | `code` | string | Stable machine code. Branch on this, never on `message`. See the full list in [Errors → Validation error codes](/en/errors#validation-error-codes). | | `message` | string | Human-readable explanation in English. Display only. | ## HTTP status codes OrigoID uses HTTP status codes that match the nature of the response: | HTTP | When | | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `200` | Request was processed. The envelope tells you whether the business result was a success or a known business condition (e.g. `CURP_NOT_FOUND`). | | `401` | Authentication failed. Envelope `type` will be `UNAUTHORIZED`. | | `404` | Path does not exist. | | `405` | Wrong method for an existing path. | | `429` | Rate limit exceeded. Envelope `type` will be `RATE_LIMIT_EXCEEDED`. | In every case where you receive a JSON body, it follows the envelope shape above. ## Parsing pattern (any language) ```javascript theme={null} const response = await fetch(url, { ... }); const env = await response.json(); if (env.status === "OK" && env.type === "SUCCESS") { // Happy path — use env.data return env.data; } if (env.type === "INVALID_REQUEST") { // Your request did not pass validation // env.errors lists per-field issues throw new ValidationError(env.errors); } if (env.type === "UNAUTHORIZED") { // Auth failed — check your API key or IP allow-list throw new AuthError(); } if (env.type === "RATE_LIMIT_EXCEEDED") { // Implement exponential backoff and retry throw new RetryableError(); } // Otherwise it is a business result (e.g. CURP_NOT_FOUND) // Treat according to your domain logic return env; ``` ## Common `type` codes | `type` | Meaning | | --------------------- | -------------------------------------------------------------------------------- | | `SUCCESS` | Successful operation. Use `data`. | | `INVALID_REQUEST` | Your request body did not pass validation. See `errors`. | | `UNAUTHORIZED` | Authentication failed. | | `RATE_LIMIT_EXCEEDED` | You exceeded your rate limit. | | `SERVICE_UNAVAILABLE` | Service temporarily unavailable. Retry with backoff. | | `INTERNAL_ERROR` | An unexpected error occurred. Reference `transactionId` when contacting support. | Each endpoint also defines **specific codes** (e.g. `CURP_NOT_FOUND`, `CFDI_CANCELED`, `INE_NOT_VALID`). See each operation in the [API reference](/en/api-reference) for the complete list. # Errors Source: https://docs.origoid.com/en/errors Common error types and how to handle them. OrigoID never returns opaque errors. Every error includes: * A **stable `type` code** that does not change between versions * A **human `message`** describing what happened * An actionable path to resolution ## Common error types | `type` | HTTP | What it means | What to do | | --------------------- | ---- | --------------------------------------------- | ------------------------------------------------------------------------------------ | | `INVALID_REQUEST` | 200 | Your request body did not pass validation | Inspect `errors[]`, fix the listed fields, retry | | `UNAUTHORIZED` | 401 | Authentication failed | Verify your API key. Check your IP allow-list. If you suspect a leak, rotate. | | `RATE_LIMIT_EXCEEDED` | 429 | You exceeded your rate limit | Implement exponential backoff. Consider upgrading your plan. | | `SERVICE_UNAVAILABLE` | 200 | An upstream source is temporarily unavailable | Retry with backoff. Check [status.origoid.com](https://status.origoid.com) | | `INTERNAL_ERROR` | 200 | An unexpected error occurred | Save the `transactionId` and email [support@origoid.com](mailto:support@origoid.com) | ## Endpoint-specific result codes Each endpoint can return additional `type` codes that represent valid business results (not errors per se). For example: * CURP endpoints can return `CURP_NOT_FOUND`, `CURP_APOCRYPHAL`, `CURP_DECEASED` * SAT endpoints can return `CFDI_CANCELED`, `TAX_PROFILE_NOT_FOUND` * Voter ID endpoints can return `INE_NOT_FOUND`, `INE_NOT_VALID` * Biometric endpoints can return `FACE_MISMATCH`, `MASK_ATTACK` The complete list of `type` codes for each endpoint is documented in that endpoint's page in the [API reference](/en/api-reference). Treat business results according to your domain logic. ## Validation error codes When `type` is `INVALID_REQUEST`, the `errors[]` array lists field-level failures (see [ErrorDetail](/en/envelope#errordetail-entries-in-errors)). Each entry has a `code` you can branch on. These codes are **cross-cutting** — they may appear on any endpoint when the corresponding validation fails: | `code` | When it appears | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `MALFORMED_JSON` | The request body is not valid JSON. The `field` is `body`. | | `PAYLOAD_TOO_LARGE` | Either the whole request body exceeds 28 MB (`field: "body"`), or a base64 image/PDF field exceeds 12 MB (`field: ""`). | | `MISSING_REQUIRED_FIELD` | A required field is absent. | | `MISSING_DEPENDENT_FIELD` | A field that depends on another required field is missing (e.g. `cif` provided without `rfc`). | | `INVALID_TYPE` | A field has the wrong primitive type (e.g. number where string is expected). | | `INVALID_FORMAT` | A field does not match its required pattern or format (CURP, RFC, NSS, ISO dates, base64, etc.). | | `INVALID_ENUM_VALUE` | A field's value is not in the allowed enum. See [Catalogs](/en/catalogs). | | `INVALID_LENGTH` | A string is shorter or longer than allowed. | | `OUT_OF_RANGE` | A numeric value is outside its allowed range. | | `INVALID_ARRAY` | An array has too few/many items, or duplicates that are not allowed. | | `UNKNOWN_FIELD` | The body contains a field the endpoint does not recognize. | | `SCHEMA_MISMATCH` | The body does not satisfy a `oneOf` / `anyOf` constraint — specifically, all alternatives were provided when only one was allowed. When some alternatives are missing, you receive `MISSING_REQUIRED_FIELD` per alternative instead. | | `INVALID_SCOPE` | The requested OAuth scope is not granted to the API key. Returned by `/auth/token` when the caller asks for scopes outside its grant. | | `INVALID_VALUE` | A value is invalid for reasons not covered above (typical fallback). | ### Endpoint-specific codes Some endpoints add extra codes that capture domain-specific validation. For example: * `INVALID_RFC_FORMAT` (`/mex/fiscal/v1/rfc-validations`, SAT 69 / 69-B searches) * `NAME_TOO_SHORT`, `NAME_ONLY_STOPWORDS` (SAT 69 / 69-B searches) * `QR_NOT_FOUND` (`/mex/fiscal/v1/csf-extractions`, `/mex/fiscal/v1/cfdi-validations`) * `IMAGE_UNREADABLE` (OCR endpoints) See each operation's page in the [API reference](/en/api-reference) for the full list. ### How errors aggregate Multiple field-level validation failures in the same request may come back together in `errors[]`. The array length depends on which kind of validation failed: * Failures detected by request-shape validation (missing required fields, wrong types, bad patterns, enum / length / range mismatches) are **aggregated** — all violations of this kind come back in one response. * Failures detected by domain-specific checks (e.g. `INVALID_RFC_FORMAT`, `NAME_TOO_SHORT`, `QR_NOT_FOUND`) may return only the **first** error encountered, even if other fields would also have failed. Always iterate `errors[]` rather than assuming a single entry. If your code receives one error, fixes it, and resubmits, the next response may surface additional errors that were latent. ## Reporting an issue If you hit an unexpected error, email [support@origoid.com](mailto:support@origoid.com) with: * The `transactionId` from the response * A brief description of what you were trying to do * The endpoint you called We do not log request or response bodies, so we cannot retrieve your call automatically. When reporting, please include the `transactionId` and the path you called, and we will work with you on next steps. # Working with images Source: https://docs.origoid.com/en/image-handling How to send images to OrigoID endpoints. Several endpoints (voter ID OCR, proof of address, face match, liveness, CSF extraction with PDF) accept images. Send them as **base64-encoded strings** in the relevant field. ## Format ```json theme={null} { "front": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAA..." } ``` Send the raw base64 string only. Do not include the `data:image/...;base64,` prefix. ## Limits | Constraint | Value | | ----------------------------- | --------------------------------- | | Maximum per image / PDF field | **12 MB** (base64-encoded length) | | Maximum total request body | **28 MB** | | Supported formats | PNG, JPG, PDF (where applicable) | Requests that exceed these limits are rejected with `INVALID_REQUEST` and `errors[].code = "PAYLOAD_TOO_LARGE"`. The `field` points either to the specific image field that exceeds 12 MB, or to `body` when the whole request exceeds 28 MB. Multi-image endpoints (e.g. voter ID OCR with `front` + `back`, face match with `face` + `front`) share the 28 MB body cap — each image still has its own 12 MB cap. ## Recommendations for best results These are guidelines, not hard limits. Suboptimal images are still processed; results just become less reliable. | Recommendation | Why | | --------------------------------- | ----------------------------------------------------- | | File size under 3 MB | Lower latency, room to spare under the 12 MB cap | | Resolution 1024×768 or higher | Better OCR accuracy | | Even lighting, minimal glare | Reduces false negatives | | Full document in frame (no crops) | Required fields stay readable | | JPG quality 85 or higher | Good balance of size and detail | | Compress before base64-encoding | Base64 inflates payload by \~33% over the binary size | If extraction fails because the image is unreadable, you receive a clear result code (`IMAGE_UNREADABLE`, `NO_FACE_DETECTED`, `DOCUMENT_NOT_IDENTIFIED`) so you can act on it. ## Privacy Images you send are processed in memory and **never persisted**. We do not store the images or any personally identifiable information extracted from them. Only operational metadata (timestamp, latency, result code) is kept for audit purposes. Read our full data handling policy at [origoid.com/legal](https://origoid.com/legal). # OrigoID Source: https://docs.origoid.com/en/index The single source of truth for identity verification and compliance infrastructure in Mexico.
OrigoID
## What is OrigoID OrigoID is the **identity verification and compliance infrastructure** for Mexico. Not a marketplace of disconnected APIs — a single, opinionated platform that standardizes responses from every official source into one clean, predictable JSON contract. We operate as a tunnel, not a vault: we validate against RENAPO, INE, IMSS, SAT, OFAC and PEPs in milliseconds and return the structured data your product needs. **We do not store the personal information we validate**. Your customers' data stays where it belongs — with you and the authorities of record. ### Why teams choose OrigoID * **One contract for every source.** Same envelope, same auth, same error handling across CURP, RFC, CFDI, INE, IMSS, OFAC and PEPs. * **Integration in minutes, not weeks.** RESTful API with copy-paste examples in 7 languages and a strict bilingual contract (English API surface, Spanish passthrough fields preserved from regulators). * **Built for regulated industries.** Compatible with LFPDPPP and designed to support our clients' CNBV obligations, with end-to-end TLS 1.3 and isolated infrastructure. * **Data sovereignty by design.** No persistent storage of PII (Personally Identifiable Information) in successful responses. Operational metadata only — for billing and auditability. * **Honest results.** Stable `type` codes for every outcome, no opaque scores, no surprises in your switch statements. Make your first call in under 10 minutes. Three supported methods: API Key, Basic, Bearer JWT. Same shape across every endpoint. Learn it once, use it everywhere. Browse every operation with request and response examples. ## Build faster First-party clients for TypeScript / Node, Python, and Go. Install one line, call any endpoint, get typed responses. Plug OrigoID into Claude, ChatGPT, Gemini, Cursor, Windsurf, and any other MCP-compatible client. Your AI assistant scaffolds full integrations without burning credits. ## Built developer-first Every endpoint returns the same response shape. Parse once, ship faster. Result `type` codes never change between versions. Your switch statements stay clean. No opaque scores. Every error tells you what happened and how to fix it. We do not store the personal data we validate. Operational metadata only. ## Mexican coverage | Source | Capability | | ----------- | --------------------------------------- | | RENAPO | CURP validate, lookup | | INE | Voter list, voter ID OCR, QR extraction | | IMSS | NSS lookup, employment status | | SAT | RFC, CSF, CFDI, lists 69 and 69-B | | OFAC + PEPs | Global sanctions + Mexican PEPs | | Biometrics | 1:1 face match, liveness | | Email | Deliverability scoring | | Documents | Proof of address OCR | We are adding more endpoints continuously. Check the [API reference](/en/api-reference) for the full catalog. ## Service status Live uptime and incident history at **[status.origoid.com](https://status.origoid.com)**. # Quickstart Source: https://docs.origoid.com/en/quickstart Make your first call in under 10 minutes. ## 1. Request access While our self-serve portal is in development, email [support@origoid.com](mailto:support@origoid.com) with: * Legal name and RFC * Use case * Estimated monthly volume You will receive your API key by a secure channel. Store it safely; it is shown only once and cannot be recovered. ## 2. Your first call — validate a CURP ```bash cURL theme={null} curl -X POST https://api.origoid.com/mex/renapo/v1/curp-validations \ -H "x-api-key: YOUR_API_KEY" \ -H "content-type: application/json" \ -d '{ "curp": "PELJ900101HDFRRN09" }' ``` ```javascript JavaScript theme={null} const response = await fetch( "https://api.origoid.com/mex/renapo/v1/curp-validations", { method: "POST", headers: { "x-api-key": process.env.ORIGOID_API_KEY, "content-type": "application/json", }, body: JSON.stringify({ curp: "PELJ900101HDFRRN09" }), }, ); const data = await response.json(); console.log(data); ``` ```python Python theme={null} import os, requests response = requests.post( "https://api.origoid.com/mex/renapo/v1/curp-validations", headers={ "x-api-key": os.environ["ORIGOID_API_KEY"], "content-type": "application/json", }, json={"curp": "PELJ900101HDFRRN09"}, ) print(response.json()) ``` ```go Go theme={null} package main import ( "bytes" "encoding/json" "fmt" "net/http" "os" ) func main() { body, _ := json.Marshal(map[string]string{"curp": "PELJ900101HDFRRN09"}) req, _ := http.NewRequest("POST", "https://api.origoid.com/mex/renapo/v1/curp-validations", bytes.NewBuffer(body)) req.Header.Set("x-api-key", os.Getenv("ORIGOID_API_KEY")) req.Header.Set("content-type", "application/json") resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() var out map[string]any json.NewDecoder(resp.Body).Decode(&out) fmt.Println(out) } ``` ## 3. Expected response Every endpoint returns the same [envelope](/en/envelope): ```json theme={null} { "status": "OK", "type": "SUCCESS", "message": "CURP found and validated", "data": { "personalInfo": { "curp": "PELJ900101HDFRRN09", "givenNames": "JUAN", "firstSurname": "PEREZ", "secondSurname": "LOPEZ", "gender": "H", "dateOfBirth": "1990-01-01", "birthState": "DISTRITO FEDERAL" }, "documentDetails": { "curpStatus": "AN", "probatoryDocumentCode": 1 } }, "transactionId": "550e8400-e29b-41d4-a716-446655440000", "processedAt": "2026-03-15T12:35:00-06:00", "billable": true } ``` ## Build with AI Using Claude, ChatGPT, Gemini, Cursor, Windsurf, or any other MCP-compatible client? Install our [MCP server](/en/sdks/mcp) and your AI assistant gains 26 tools — 7 for reading our spec (free, no API key needed) and 19 for calling the API. ```bash theme={null} claude mcp add origoid \ --env ORIGOID_API_KEY=your_api_key \ -- npx -y @origoid/mcp-server ``` The 7 docs tools work without an API key, so the assistant can scaffold a full integration before you have an account. When you say "now run it", the assistant calls the real API. ## Next steps One shape across every endpoint. Stable `type` codes and how to act on them. CURP status codes, IMSS modalities, SAT lists, and more. How usage counts against your plan. # Go SDK Source: https://docs.origoid.com/en/sdks/go Official Go client. Context-aware, idiomatic, distributed via Go modules. The Go SDK is distributed as a standard Go module. There is no separate registry — `go get` fetches the source directly from GitHub. The SDK provides one strongly-typed function per OrigoID endpoint and follows idiomatic Go patterns (`context.Context`, exported structs, explicit error returns). ## Install ```bash theme={null} go get github.com/origoid/sdk-go@v0.1.0 ``` Or add it to `go.mod`: ```go theme={null} require github.com/origoid/sdk-go v0.1.0 ``` Then run `go mod tidy`. Source on [github.com/origoid/sdk-go](https://github.com/origoid/sdk-go) (public, for auditing). Requires Go **1.21 or newer**. ## Initialize the client ```go theme={null} package main import ( "fmt" "os" "github.com/origoid/sdk-go/client" "github.com/origoid/sdk-go/option" ) func main() { c := client.NewClient( option.WithAPIKey(os.Getenv("ORIGOID_API_KEY")), ) _ = c fmt.Println("client ready") } ``` That's the whole setup — there is nothing else to configure. Never hardcode the key. Load it from `os.Getenv`, [`viper`](https://github.com/spf13/viper), or a secrets manager (HashiCorp Vault, 1Password, Doppler, etc.). ## Your first call The example below uses `PELJ900101HDFRRN09`, a **synthetic CURP from the OpenAPI examples** — not a real person's CURP. Replace it with the CURP you need to validate. ```go theme={null} package main import ( "context" "fmt" "log" "os" origoid "github.com/origoid/sdk-go" "github.com/origoid/sdk-go/client" "github.com/origoid/sdk-go/option" ) func main() { c := client.NewClient( option.WithAPIKey(os.Getenv("ORIGOID_API_KEY")), ) env, err := c.Renapo.ValidateCurp( context.Background(), &origoid.ValidateCurpRequest{ Curp: "PELJ900101HDFRRN09", // synthetic — replace with real input }, ) if err != nil { log.Fatal(err) } if env.Status == "OK" && env.Type == "SUCCESS" { // env.Data holds the RENAPO record. fmt.Println("CURP holder:", env.Data) } else { // Other result type — check the endpoint's response catalog to drive your logic. fmt.Println(env.Type, "—", env.Message) } } ``` Every method returns `(*origoid.Envelope, error)`. The envelope shape is `{ Status, Type, Message, Data, TransactionId, ProcessedAt, Billable, Errors }`. See [Response envelope](/en/envelope) for the contract. ## Methods by resource The client groups operations by regulatory domain. ### `c.Authentication` ```go theme={null} c.Authentication.IssueToken(ctx, &origoid.IssueTokenRequest{ExpireAfter: 1800}) ``` ### `c.Renapo` ```go theme={null} c.Renapo.ValidateCurp(ctx, &origoid.ValidateCurpRequest{ Curp: "PELJ900101HDFRRN09", }) c.Renapo.LookupCurp(ctx, &origoid.LookupCurpRequest{ GivenNames: "JUAN", FirstSurname: "PEREZ", SecondSurname: origoid.String("LOPEZ"), DateOfBirth: "1990-01-01", Gender: origoid.LookupCurpRequestGenderH, BirthStateCode: "DF", }) ``` `origoid.String(...)` is a helper for optional string fields (Go has no `Option`; optionality is modeled with pointers). ### `c.Sat` ```go theme={null} c.Sat.ValidateRfc(ctx, &origoid.ValidateRfcRequest{Rfc: "PEZJ811011KI1"}) c.Sat.ExtractCsf(ctx, map[string]any{ "rfc": "PEZJ811011KI1", "cif": "17060597619", }) c.Sat.ValidateCfdi(ctx, map[string]any{ "uuid": "7C8BD4EA-AE86-4CB5-88B8-C6E61E988A8B", "rfcEmisor": "PEZJ811011KI1", "rfcReceptor": "EMP170623KI3", "total": "999999.99", }) ``` `ExtractCsf` and `ValidateCfdi` accept a `map[string]any` because the underlying schemas allow alternative shapes (direct identifiers OR a document upload). ### `c.Imss` ```go theme={null} c.Imss.LookupNss(ctx, &origoid.LookupNssRequest{Curp: "PELJ900101HDFRRN09"}) c.Imss.GetEmploymentStatus(ctx, &origoid.GetEmploymentStatusRequest{ Curp: "PELJ900101HDFRRN09", Nss: "92038109713", }) ``` ### `c.Ine` ```go theme={null} c.Ine.ValidateVoterList(ctx, map[string]any{ "cic": "123456789", "citizenIdentifier": "987654321", }) c.Ine.ExtractVoterIdData(ctx, &origoid.ExtractVoterIdDataRequest{ Front: "", Back: origoid.String(""), }) c.Ine.ExtractQrData(ctx, &origoid.ExtractQrDataRequest{ Back: "", }) ``` ### `c.Compliance` ```go theme={null} c.Compliance.SearchSat69(ctx, map[string]any{"rfc": "PEZJ811011KI1"}) c.Compliance.SearchSat69B(ctx, map[string]any{"rfc": "PEZJ811011KI1"}) c.Compliance.SearchOfac(ctx, &origoid.SearchOfacRequest{ Name: "John Doe", MinSimilarityScore: origoid.Int(85), }) c.Compliance.SearchPeps(ctx, map[string]any{ "givenNames": "JUAN", "firstSurname": "PEREZ", "secondSurname": "LOPEZ", }) ``` ### `c.Biometrics` ```go theme={null} c.Biometrics.MatchFaces(ctx, &origoid.MatchFacesRequest{ Face: "", Front: "", Threshold: origoid.Int(80), DocumentType: origoid.MatchFacesRequestDocumentTypeIne.Ptr(), }) c.Biometrics.CheckLiveness(ctx, &origoid.CheckLivenessRequest{ Selfie: "", }) ``` ### `c.Email` ```go theme={null} c.Email.ValidateEmail(ctx, &origoid.ValidateEmailRequest{ Email: "user@example.com", }) ``` ### `c.ProofOfAddress` ```go theme={null} c.ProofOfAddress.ExtractProofOfAddress(ctx, &origoid.ExtractProofOfAddressRequest{ File: "", }) ``` ## Error handling The SDK distinguishes between **business errors** (returned inside the envelope) and **transport errors** (returned as a non-nil `error`). ### Business errors — inspect the envelope For any HTTP 200, including `INVALID_REQUEST`, the SDK returns a `*Envelope`. Check `Status` and `Type` before using `Data`: ```go theme={null} env, err := c.Renapo.ValidateCurp(ctx, &origoid.ValidateCurpRequest{Curp: "BAD"}) if err != nil { log.Fatal(err) } switch env.Type { case "SUCCESS": // env.Data holds the record case "CURP_NOT_FOUND": // no match case "INVALID_REQUEST": for _, e := range env.Errors { fmt.Printf(" %s: %s — %s\n", e.Field, e.Code, e.Message) } } ``` ### Transport errors — non-nil `error` For `401`, `429`, network failures, the SDK returns a typed error: ```go theme={null} import "errors" env, err := c.Renapo.ValidateCurp(ctx, req) if err != nil { var unauth *origoid.UnauthorizedError var rl *origoid.TooManyRequestsError switch { case errors.As(err, &unauth): // 401 case errors.As(err, &rl): // 429 — read rl.Body for the rate-limit envelope default: // network / timeout / unexpected log.Fatal(err) } } ``` ## Per-call configuration (advanced) Use `option.With*` helpers as additional arguments: ```go theme={null} import "time" env, err := c.Ine.ValidateVoterList( ctx, map[string]any{"cic": "123456789", "citizenIdentifier": "987654321"}, option.WithRequestTimeout(120 * time.Second), option.WithMaxAttempts(3), ) ``` Pass a `context.Context` to honor deadlines or cancellations from the caller; the SDK respects `ctx.Done()` and aborts in-flight requests. **Read this before tuning timeouts or retries.** The SDK only retries `5xx` and network failures, never successful business responses — so retries do **not** create duplicate billable calls when the API responded correctly. They **do** create extra calls when the request actually failed: a request that times out three times can consume three credits if the call eventually succeeded on a later attempt. * **Defaults (60 s, 2 retries) are right for almost every workload.** Change only with a specific reason. * Combining a long timeout with high `MaxAttempts` (e.g. 120 s × 5) means a single failing request can occupy a goroutine for up to 10 minutes — bad for your throughput and your infrastructure. * Override per-call only on endpoints with known slow cold starts. ## Pointers for optional fields Go has no `Option`, so optional fields in request structs are pointer types. The SDK exposes helper constructors to make this less verbose: ```go theme={null} origoid.String("optional value") // *string origoid.Int(42) // *int origoid.Bool(true) // *bool ``` For enums: ```go theme={null} origoid.MatchFacesRequestDocumentTypeIne.Ptr() ``` # MCP server Source: https://docs.origoid.com/en/sdks/mcp `@origoid/mcp-server` — official Model Context Protocol server. Lets any MCP-capable LLM (Claude, Gemini, GPT, Codex, …) read the OrigoID spec and call the API directly. The MCP server is OrigoID's official integration point for AI coding assistants and chat clients that speak the [Model Context Protocol](https://modelcontextprotocol.io). Install it once in your client and the LLM gains 29 tools: 10 for **building** an OrigoID integration (free, no API key needed) and 19 for **running** real calls (require your API key). ## Why it exists When you ask an AI assistant to "add CURP validation to my Express app," you want the assistant to write code against the current OrigoID contract, not an outdated copy of it that lived in its training set. The MCP server gives the LLM live, authoritative access to: * the full OpenAPI spec for every endpoint, * copy-paste SDK snippets in 8 languages, * the complete list of result `type` codes per endpoint, * and the option to actually invoke an endpoint once you provide a key. Everything except the actual API calls is offline and free — the LLM can design and write a full production integration without consuming a single credit. ## Compatibility MCP is an open protocol adopted by Anthropic (2024), OpenAI (2025), and Google (2025). The same server config works in every compliant client: * **Claude Desktop** (macOS app) * **Claude Code** (CLI) * **Cursor** * **Windsurf** * **ChatGPT Desktop** * **Codex CLI** * **Gemini CLI / Antigravity** * **Zed** * any other MCP-capable client ## Install with your AI (shortcut) Most of the time you can just ask your AI assistant to install it for you. Copy one of the prompts below into your client chat — the LLM will edit the right config file (and run the right CLI command where applicable) for you. **Heads-up:** desktop clients (Claude Desktop, Cursor, Windsurf) need to be restarted after the config is edited; the AI cannot do that for you. Quit the app and reopen it. **Path conventions:** the `~/` shorthand means the user's home directory on **macOS and Linux** (`/Users//...` and `/home//...` respectively). On **Windows**, replace `~/` with `%USERPROFILE%\` (CMD) or `$HOME\` (PowerShell). The Claude Desktop config uses a Windows-specific `%APPDATA%` path called out below. ### Claude Code (CLI) ```text theme={null} Run this in my terminal: claude mcp add origoid --env ORIGOID_API_KEY= -- npx -y @origoid/mcp-server Then verify with: claude mcp list ``` Works identically on macOS, Linux, and Windows. ### Claude Desktop ```text macOS theme={null} Install the OrigoID MCP server (@origoid/mcp-server) in my Claude Desktop. Edit: ~/Library/Application Support/Claude/claude_desktop_config.json Add "origoid" to mcpServers with command "npx", args ["-y", "@origoid/mcp-server"], and env ORIGOID_API_KEY=. Then remind me to quit and reopen Claude Desktop (Cmd+Q first). ``` ```text Windows theme={null} Install the OrigoID MCP server (@origoid/mcp-server) in my Claude Desktop. Edit: %APPDATA%\Claude\claude_desktop_config.json Add "origoid" to mcpServers with command "npx", args ["-y", "@origoid/mcp-server"], and env ORIGOID_API_KEY=. Then remind me to quit and reopen Claude Desktop. ``` ```text Linux theme={null} Claude Desktop is not officially distributed for Linux today. Use Claude Code (CLI) — works identically. See the snippet above. ``` ### Cursor ```text macOS / Linux theme={null} Install the OrigoID MCP server (@origoid/mcp-server) in my Cursor. Edit ~/.cursor/mcp.json and add "origoid" to mcpServers with command "npx", args ["-y", "@origoid/mcp-server"], env ORIGOID_API_KEY=. Then remind me to reload Cursor. ``` ```text Windows theme={null} Install the OrigoID MCP server (@origoid/mcp-server) in my Cursor. Edit %USERPROFILE%\.cursor\mcp.json and add "origoid" to mcpServers with command "npx", args ["-y", "@origoid/mcp-server"], env ORIGOID_API_KEY=. Then remind me to reload Cursor. ``` ### Windsurf ```text macOS / Linux theme={null} Install the OrigoID MCP server (@origoid/mcp-server) in my Windsurf. Edit ~/.codeium/windsurf/mcp_config.json and add "origoid" to mcpServers with command "npx", args ["-y", "@origoid/mcp-server"], env ORIGOID_API_KEY=. Then remind me to reload Windsurf. ``` ```text Windows theme={null} Install the OrigoID MCP server (@origoid/mcp-server) in my Windsurf. Edit %USERPROFILE%\.codeium\windsurf\mcp_config.json and add "origoid" to mcpServers with command "npx", args ["-y", "@origoid/mcp-server"], env ORIGOID_API_KEY=. Then remind me to reload Windsurf. ``` ### Codex CLI ```text macOS / Linux theme={null} Add the OrigoID MCP server (@origoid/mcp-server) to my Codex config. Edit ~/.codex/config.toml and add: [mcp_servers.origoid] command = "npx" args = ["-y", "@origoid/mcp-server"] [mcp_servers.origoid.env] ORIGOID_API_KEY = "" ``` ```text Windows theme={null} Add the OrigoID MCP server (@origoid/mcp-server) to my Codex config. Edit %USERPROFILE%\.codex\config.toml and add the same TOML block as the macOS / Linux example. ``` ### Gemini CLI / Antigravity ```text macOS / Linux theme={null} Install the OrigoID MCP server (@origoid/mcp-server) in my Gemini config. Edit ~/.gemini/settings.json and add "origoid" to mcpServers with command "npx", args ["-y", "@origoid/mcp-server"], env ORIGOID_API_KEY=. ``` ```text Windows theme={null} Install the OrigoID MCP server (@origoid/mcp-server) in my Gemini config. Edit %USERPROFILE%\.gemini\settings.json and add "origoid" to mcpServers with command "npx", args ["-y", "@origoid/mcp-server"], env ORIGOID_API_KEY=. ``` If you want to wire it up manually instead, the per-client config snippets are below. ## Install You do not install the package manually — your MCP client launches it on demand via `npx`. Pick the snippet for your client: ### Claude Code ```bash theme={null} claude mcp add origoid \ --env ORIGOID_API_KEY=your_api_key \ -- npx -y @origoid/mcp-server ``` ### Claude Desktop Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows) and add: ```json theme={null} { "mcpServers": { "origoid": { "command": "npx", "args": ["-y", "@origoid/mcp-server"], "env": { "ORIGOID_API_KEY": "your_api_key" } } } } ``` Restart Claude Desktop. ### Cursor Edit `~/.cursor/mcp.json`: ```json theme={null} { "mcpServers": { "origoid": { "command": "npx", "args": ["-y", "@origoid/mcp-server"], "env": { "ORIGOID_API_KEY": "your_api_key" } } } } ``` ### Windsurf Edit `~/.codeium/windsurf/mcp_config.json` with the same `mcpServers.origoid` block as above. ### ChatGPT Desktop Open **Settings → Tools → Model Context Protocol** and paste the `mcpServers.origoid` block above into the config editor. ### Codex CLI Edit `~/.codex/config.toml`: ```toml theme={null} [mcp_servers.origoid] command = "npx" args = ["-y", "@origoid/mcp-server"] [mcp_servers.origoid.env] ORIGOID_API_KEY = "your_api_key" ``` ### Gemini CLI / Antigravity Edit `~/.gemini/settings.json`: ```json theme={null} { "mcpServers": { "origoid": { "command": "npx", "args": ["-y", "@origoid/mcp-server"], "env": { "ORIGOID_API_KEY": "your_api_key" } } } } ``` ### Any other MCP client Use: * **Command:** `npx` * **Args:** `-y @origoid/mcp-server` * **Env:** `ORIGOID_API_KEY=` The server speaks MCP over standard stdio transport. ## API key is optional If you do not set `ORIGOID_API_KEY`, the server boots in **docs-only mode**. The 10 documentation tools work as usual; the 19 API tools return a clear error if invoked. This is the right mode for the design phase — the LLM can describe, scaffold, and write your integration without you having an account yet. ## Tools ### Docs tools (no API key, no credits) | Tool | What it returns | | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `list_endpoints({domain?})` | Catalog of every operation with `operationId`, method, path, and domain. Optional filter by domain (`renapo`, `sat`, `imss`, `ine`, `compliance`, `biometrics`, `email`, `documents`, `auth`). | | `get_endpoint({operationId})` | Full OpenAPI operation: description, request schema, response examples for every `type` code, credit cost. | | `get_sdk_example({operationId, language})` | Copy-paste snippet. Languages: `curl`, `javascript`, `typescript`, `python`, `php`, `go`, `java`, `ruby`, `csharp`. | | `get_response_types({operationId})` | Every possible result `type` for the endpoint with a sample envelope — feeds your `switch`/`match` logic. | | `get_api_overview()` | The complete `info.description` from the spec: authentication, envelope contract, rate-limit headers, language conventions, image-processing policy, and all catalogs (CURP status, IMSS modalities, SAT lists, OFAC lists, CFDI effects). | | `search_docs({query})` | Free-text search across summaries, descriptions, and examples. | | `validate_envelope_shape({envelopeJson})` | Offline checker — does the JSON match the canonical `Envelope` contract? Useful for unit-testing a custom parser. | | `get_typescript_types({operationId})` | Self-contained `.ts` file with `Request`, `Type` union, `Data` shape, and `Envelope` interfaces. Paste straight into a project — no SDK import required. | | `get_pydantic_model({operationId})` | Self-contained `.py` file with Pydantic v2 classes (`Request`, `Type` Literal, `Data`, `ErrorDetail`, `Envelope`). Only runtime dep is `pydantic>=2`. | | `get_full_integration_starter({scenario})` | A complete multi-file project the user (or their AI assistant) drops into a folder and runs. Scenarios: `express-curp` (Node + Express 5), `fastapi-curp` (Python + FastAPI async), `go-cli-curp` (Go CLI). Each handles every documented result type with idiomatic HTTP / exit codes. | ### API tools (require `ORIGOID_API_KEY`) One per endpoint. Calls are proxied through the official `@origoid/sdk` to `https://api.origoid.com`. Each successful call consumes credits per the [Credits page](/en/credits). | Tool | Endpoint | | -------------------------- | ----------------------------------------------------- | | `issue_token` | `POST /auth/token` | | `validate_curp` | `POST /mex/renapo/v1/curp-validations` | | `lookup_curp` | `POST /mex/renapo/v1/curp-lookups` | | `validate_rfc` | `POST /mex/fiscal/v1/rfc-validations` | | `extract_csf` | `POST /mex/fiscal/v1/csf-extractions` | | `validate_cfdi` | `POST /mex/fiscal/v1/cfdi-validations` | | `lookup_nss` | `POST /mex/social-security/v1/imss-nss-lookups` | | `get_employment_status` | `POST /mex/social-security/v1/imss-employment-status` | | `validate_voter_list` | `POST /mex/id/v1/voter-list-validations` | | `extract_voter_id_data` | `POST /mex/id/v1/voter-id-extractions` | | `extract_qr_data` | `POST /mex/id/v1/qr-extractions` | | `search_sat_69` | `POST /mex/compliance/v1/sat-69-searches` | | `search_sat_69b` | `POST /mex/compliance/v1/sat-69b-searches` | | `search_ofac` | `POST /global/compliance/v1/ofac-searches` | | `search_peps` | `POST /mex/compliance/v1/peps-searches` | | `match_faces` | `POST /global/biometrics/v1/face-matches` | | `check_liveness` | `POST /global/biometrics/v1/liveness-checks` | | `validate_email` | `POST /global/email/v1/email-validations` | | `extract_proof_of_address` | `POST /mex/documents/v1/proof-of-address-extractions` | Each API tool returns the full envelope as a JSON string so the LLM has access to `status`, `type`, `data`, `errors[]`, and the `transactionId` (useful for support tickets). ## Typical sessions ### Snippet-level integration > Add CURP validation to my Express API. Use the OrigoID MCP to verify > the schema and give me a TypeScript handler that covers every result > type. The LLM internally calls: 1. `list_endpoints({domain: "renapo"})` — finds `validateCurp` 2. `get_endpoint({operationId: "validateCurp"})` — reads the request schema and every response example 3. `get_response_types({operationId: "validateCurp"})` — gets the 10 possible `type` codes so the generated switch statement is complete 4. `get_sdk_example({operationId: "validateCurp", language: "typescript"})` — pulls a copy-paste snippet It then writes the handler. None of that consumed credits. When you say "now run it", the LLM calls `validate_curp({curp: "..."})` — that single call is billed. ### Whole-project starter (one prompt → runnable repo) > Scaffold a complete Express + TypeScript project that validates CURPs > via OrigoID. Use the integration starter from the MCP. The LLM calls `get_full_integration_starter({scenario: "express-curp"})` once. The tool returns six files (`package.json`, `tsconfig.json`, `.env.example`, `.gitignore`, `src/server.ts`, `README.md`), the setup commands, and the run command. The LLM writes them to disk; you run `npm install && npm run dev` and have a working server in under a minute. Same idea works for `fastapi-curp` (Python + FastAPI async) or `go-cli-curp` (Go CLI). ### Strict-typed integration without the SDK > I'd rather call OrigoID with raw fetch but I want the response type > safety. Give me the TypeScript types for validateCurp. The LLM calls `get_typescript_types({operationId: "validateCurp"})` and gets a self-contained `.ts` file (`Request`, `Type` union, `Data`, `Envelope` interfaces). For Python, the same pattern uses `get_pydantic_model({operationId: "validateCurp"})` and returns Pydantic v2 classes. ## Cost behavior * **Docs tools:** zero cost forever. Read from the OpenAPI spec bundled with the package. * **API tools:** billed per the public pricing. The LLM can run them repeatedly while iterating; review the call list before approving auto-execution. * We recommend a dedicated test key for AI-driven development, with a separate credit pool from your production traffic. ## Authentication The server reads `ORIGOID_API_KEY` from its environment. The host MCP client is responsible for injecting it via the `env` field in its configuration — never as a tool argument and never inside the chat. If you accidentally paste your key into a prompt, rotate it. ## Source and version * npm: [`@origoid/mcp-server`](https://www.npmjs.com/package/@origoid/mcp-server) * GitHub: [github.com/origoid/mcp-server](https://github.com/origoid/mcp-server) The source is public so you can audit every request the server sends to `https://api.origoid.com`. ## Support [support@origoid.com](mailto:support@origoid.com) — bugs, feature requests, or help wiring up a client we don't list above. # Node / TypeScript SDK Source: https://docs.origoid.com/en/sdks/node `@origoid/sdk` — official client for Node.js 18+ and TypeScript projects. The Node SDK ships with full TypeScript definitions, automatic retries, and one strongly-typed method per OrigoID endpoint. It works in any Node 18+ runtime and in Deno. ## Install ```bash npm theme={null} npm install @origoid/sdk ``` ```bash yarn theme={null} yarn add @origoid/sdk ``` ```bash pnpm theme={null} pnpm add @origoid/sdk ``` ```bash bun theme={null} bun add @origoid/sdk ``` Package on npm: [`@origoid/sdk`](https://www.npmjs.com/package/@origoid/sdk). Source on [github.com/origoid/sdk-node](https://github.com/origoid/sdk-node) (public, for auditing). ## Initialize the client ```typescript theme={null} import { OrigoidApiClient } from "@origoid/sdk"; const client = new OrigoidApiClient({ apiKey: process.env.ORIGOID_API_KEY!, }); ``` That's the whole setup — there is nothing else to configure. Never hardcode the API key in source. Load it from `process.env`, a secrets manager (Vault, 1Password, Doppler, etc.), or your platform's config layer. ## Your first call The example below uses `PELJ900101HDFRRN09`, a **synthetic CURP from the OpenAPI examples** — not a real person's CURP. Replace it with the CURP you need to validate. ```typescript theme={null} import { OrigoidApiClient } from "@origoid/sdk"; const client = new OrigoidApiClient({ apiKey: process.env.ORIGOID_API_KEY! }); const envelope = await client.renapo.validateCurp({ curp: "PELJ900101HDFRRN09", // synthetic — replace with real input }); if (envelope.status === "OK" && envelope.type === "SUCCESS") { // envelope.data holds the RENAPO record. console.log("CURP holder:", envelope.data); } else { // Other result type — check the endpoint's response catalog to drive your logic. console.log(envelope.type, "—", envelope.message); } ``` Every method returns the same `Envelope` shape: `{ status, type, message, data, transactionId, processedAt, billable, errors? }`. See [Response envelope](/en/envelope) for the full contract. ## Methods by resource The client groups operations under one property per regulatory domain. ### `client.authentication` ```typescript theme={null} await client.authentication.issueToken({ expireAfter: 1800 }); ``` ### `client.renapo` ```typescript theme={null} await client.renapo.validateCurp({ curp: "PELJ900101HDFRRN09" }); await client.renapo.lookupCurp({ givenNames: "JUAN", firstSurname: "PEREZ", secondSurname: "LOPEZ", dateOfBirth: "1990-01-01", gender: "H", birthStateCode: "DF", }); ``` ### `client.sat` ```typescript theme={null} await client.sat.validateRfc({ rfc: "PEZJ811011KI1" }); await client.sat.extractCsf({ rfc: "PEZJ811011KI1", cif: "17060597619" }); await client.sat.validateCfdi({ uuid: "7C8BD4EA-AE86-4CB5-88B8-C6E61E988A8B", rfcEmisor: "PEZJ811011KI1", rfcReceptor: "EMP170623KI3", total: "999999.99", }); ``` ### `client.imss` ```typescript theme={null} await client.imss.lookupNss({ curp: "PELJ900101HDFRRN09" }); await client.imss.getEmploymentStatus({ curp: "PELJ900101HDFRRN09", nss: "92038109713", }); ``` ### `client.ine` ```typescript theme={null} await client.ine.validateVoterList({ cic: "123456789", citizenIdentifier: "987654321" }); await client.ine.extractVoterIdData({ front: "", back: "" }); await client.ine.extractQrData({ back: "" }); ``` ### `client.compliance` ```typescript theme={null} await client.compliance.searchSat69({ rfc: "PEZJ811011KI1" }); await client.compliance.searchSat69B({ rfc: "PEZJ811011KI1" }); await client.compliance.searchOfac({ name: "John Doe", minSimilarityScore: 85 }); await client.compliance.searchPeps({ givenNames: "JUAN", firstSurname: "PEREZ", secondSurname: "LOPEZ", }); ``` Note: the method for the SAT 69-B list is `searchSat69B` (capital B). The other compliance methods follow the regular camelCase pattern. ### `client.biometrics` ```typescript theme={null} await client.biometrics.matchFaces({ face: "", front: "", threshold: 80, documentType: "INE", }); await client.biometrics.checkLiveness({ selfie: "" }); ``` ### `client.email` ```typescript theme={null} await client.email.validateEmail({ email: "user@example.com" }); ``` ### `client.proofOfAddress` ```typescript theme={null} await client.proofOfAddress.extractProofOfAddress({ file: "" }); ``` ## Error handling The SDK distinguishes between **business errors** (returned inside the envelope) and **transport errors** (thrown as typed exceptions). ### Business errors — read from the envelope For any HTTP 200, including `INVALID_REQUEST`, the SDK returns a normal `Envelope` object. Inspect `status` and `type` before using `data`: ```typescript theme={null} const env = await client.renapo.validateCurp({ curp: "BAD" }); if (env.status === "ERROR") { // INVALID_REQUEST, SERVICE_UNAVAILABLE, … console.error(env.type, env.message); } else { // status === "OK" — could still be a business-level NOT_FOUND. switch (env.type) { case "SUCCESS": /* env.data holds the record */ break; case "CURP_NOT_FOUND": /* no match */ break; // see /en/errors and /en/catalogs for the full catalog } } ``` ### Transport errors — caught with try/catch For `401`, `429`, and unrecoverable transport failures the SDK throws typed errors: ```typescript theme={null} import { OrigoidApiClient, OrigoidApiError, OrigoidApiTimeoutError, } from "@origoid/sdk"; try { const env = await client.renapo.validateCurp({ curp: "PELJ900101HDFRRN09" }); // … use env } catch (err) { if (err instanceof OrigoidApiTimeoutError) { // request did not complete within the configured timeout } else if (err instanceof OrigoidApiError) { // err.statusCode — 401, 429, etc. // err.body — the envelope returned by the server } else { throw err; // unexpected } } ``` ## Per-call configuration (advanced) Every method accepts an optional second argument: ```typescript theme={null} await client.ine.validateVoterList( { cic: "123456789", citizenIdentifier: "987654321" }, { timeoutInSeconds: 120, // default 60 maxRetries: 3, // default 2 abortSignal: ac.signal, // standard AbortController headers: { "x-trace-id": "..." }, }, ); ``` **Read this before tuning timeouts or retries.** The SDK only sends a retry for `5xx` and network failures, never for successful business responses — so retries do **not** create duplicate billable calls when the API responded correctly. They **do** create extra calls when the request actually failed: a request that times out three times can consume three credits if the request eventually succeeded on a later attempt. * **Defaults (`timeoutInSeconds: 60`, `maxRetries: 2`) are right for almost every workload.** Change them only with a specific reason. * Combining a long timeout with high `maxRetries` (e.g. `120s` × `5`) means a single failing request can occupy a client thread for up to 10 minutes — bad for your own throughput and infrastructure. * Set per-call overrides only on endpoints with known slow cold starts (some compliance and INE-list calls). ## TypeScript Every request and response type is exported under the `OrigoidApi` namespace: ```typescript theme={null} import { OrigoidApi } from "@origoid/sdk"; function handleResult(env: OrigoidApi.Envelope) { if (env.status === "OK" && env.type === "SUCCESS" && env.data) { // env.data is typed } } ``` ## CommonJS The package ships both ESM and CJS entry points. In a CommonJS project: ```javascript theme={null} const { OrigoidApiClient } = require("@origoid/sdk"); const client = new OrigoidApiClient({ apiKey: process.env.ORIGOID_API_KEY }); ``` ## Browser usage **Do not call OrigoID directly from a browser.** API keys are long-lived credentials that grant billable access to your account; the moment a key reaches a browser bundle, browser console, or local storage, it is effectively public and at risk of abuse — the same way you would never put a credit-card processor's secret key in client-side code. The correct pattern is **backend-for-frontend (BFF)**: your browser talks to your server, your server holds the API key and calls OrigoID from a trusted environment (Node, Python, Go). If your use case truly requires browser-direct calls (partner widget, embedded form, etc.) we can enable CORS for your specific origins. The key stays out of the browser only if you scope it carefully and pair it with referrer/origin restrictions — we will help you design that flow. Reach out and we will work through the architecture with you. # SDKs overview Source: https://docs.origoid.com/en/sdks/overview Official client libraries in TypeScript, Python, and Go. One method per endpoint, idiomatic to each ecosystem. We publish first-party SDKs for the three languages most used by OrigoID customers. They are the official, supported clients — there is no community variant to choose from. ## Why use an SDK instead of raw HTTP You can absolutely call our REST endpoints with `curl`, `fetch`, `requests`, or `net/http`. The SDKs add: * **Typed request and response models** — your IDE autocompletes every field, your compiler catches typos, your linter flags missing required values. * **Nothing to configure beyond the API key.** Production base URL is built in; pass your key at construction time and start calling. * **Automatic retries** — transient `5xx` and network errors are retried with exponential backoff (configurable per call). * **Strongly-typed enums** — `type` codes, `cancellationStatus`, `riskLevel`, etc. surface as enums or string literal unions, never raw strings. * **Helpful errors** — a `401 UNAUTHORIZED` raises a typed error with the envelope already deserialized, not a generic `HTTPError`. If you only need to call one endpoint occasionally from a script, `curl` is fine. For any production integration we recommend the SDK. ## Pick your language `npm install @origoid/sdk`. Works in Node 18+ and Deno. Ships TypeScript definitions out of the box. `pip install origoid`. Python 3.9+. Sync `OrigoID` plus an async `AsyncOrigoID` for FastAPI / aiohttp use cases. `go get github.com/origoid/sdk-go`. Context-aware, idiomatic Go style, generated types for every request and response. `npx @origoid/mcp-server`. Works with Claude, ChatGPT, Gemini, Cursor, Windsurf, and any other MCP-compatible client. Lets the AI scaffold integrations in any language without burning credits. ## Source and distribution | SDK | Source | Install | | ----------------- | ---------------------------------------------------------------------- | ---------------------------------- | | TypeScript / Node | [github.com/origoid/sdk-node](https://github.com/origoid/sdk-node) | `npm install @origoid/sdk` | | Python | [github.com/origoid/sdk-python](https://github.com/origoid/sdk-python) | `pip install origoid` | | Go | [github.com/origoid/sdk-go](https://github.com/origoid/sdk-go) | `go get github.com/origoid/sdk-go` | All three repositories are public. The published packages on npm and PyPI are the official distribution channels; the GitHub source is provided so you can audit the code before installing. ## Versioning SDKs follow [semver](https://semver.org/). * **Major** (`1.0.0` → `2.0.0`) — breaking changes to the SDK shape. Always accompanied by a migration guide in the release notes. * **Minor** (`0.1.0` → `0.2.0`) — new endpoints, new fields, new helpers. Backwards-compatible. * **Patch** (`0.1.0` → `0.1.1`) — bug fixes and improvements. While the SDKs are under `0.x.y` we bump the **minor** version for breaking changes (per semver pre-1.0 guidance). Once an SDK reaches `1.0.0` the classic semver rules apply. ## Support * API questions, feature requests, or SDK bug reports: [support@origoid.com](mailto:support@origoid.com) * Status: [status.origoid.com](https://status.origoid.com) # Postman collection Source: https://docs.origoid.com/en/sdks/postman Official OrigoID Postman collection — every endpoint, with one request per invocation form. Import the official collection into Postman to try every endpoint without writing code. It's generated from our OpenAPI specification, so it always mirrors the production API. [**Download `OrigoID.postman_collection.json`**](https://raw.githubusercontent.com/origoid/postman/main/OrigoID.postman_collection.json) — 22 endpoints, 35 requests (includes every option of the multi-form endpoints). ## Import 1. In Postman → **Import**: drop the downloaded file, **or** paste this URL directly (Postman imports from a link): `https://raw.githubusercontent.com/origoid/postman/main/OrigoID.postman_collection.json` 2. Every request already points to `https://api.origoid.com` (literal URL, ready to copy into your code). 3. **Add your API key manually.** It is not bundled. On each request, under the **Headers** tab, add `x-api-key` with your key (`ogid_live_...`). To avoid repeating it, set it once under the collection's **Authorization** tab → type **API Key**, Key `x-api-key`, *Add to* **Header**. ## Multi-option endpoints Endpoints that accept several invocation forms ship **one request per option**, for example: * **PEPs** — by single name, separated name, name + CURP, name + RFC, or identifier only. * **OFAC** — name only, name + passport, or name + national ID. * **Voter list (INE)** — CIC + voter key, or CIC + OCR. * **CFDI** — by data (UUID + RFCs + total) or by document (QR). ## JWT token (optional) `POST /auth/token` exchanges your API key for a short-lived JWT. If you prefer authenticating with `Authorization: Bearer ` instead of `x-api-key`, copy the `token` from the response and add it manually. ## Image fields OCR and biometric endpoints (INE, proof of address, face match, liveness) expect the image as base64 — replace the `` placeholder with your real content. See [Image handling](/en/image-handling). # Python SDK Source: https://docs.origoid.com/en/sdks/python `origoid` — official client for Python 3.9+. Sync, async, typed response models. The Python SDK ships with both synchronous and asynchronous clients, typed response models (built on [Pydantic](https://docs.pydantic.dev/), the standard Python data-validation library), full type hints, and one strongly-typed method per OrigoID endpoint. ## Install ```bash theme={null} pip install origoid ``` Or with [`uv`](https://docs.astral.sh/uv/) / [`poetry`](https://python-poetry.org/) / [`pdm`](https://pdm-project.org/): ```bash uv theme={null} uv add origoid ``` ```bash poetry theme={null} poetry add origoid ``` ```bash pdm theme={null} pdm add origoid ``` Package on PyPI: [`origoid`](https://pypi.org/project/origoid/). Source on [github.com/origoid/sdk-python](https://github.com/origoid/sdk-python) (public, for auditing). Requires Python **3.9 or newer**. ## Initialize the client ```python theme={null} import os from origoid import OrigoID client = OrigoID(api_key=os.environ["ORIGOID_API_KEY"]) ``` That's the whole setup — there is nothing else to configure. Never hardcode the API key. Load from `os.environ`, a `.env` file via [`python-dotenv`](https://pypi.org/project/python-dotenv/), or a secrets manager (HashiCorp Vault, 1Password, Doppler, etc.). ## Your first call The example below uses `PELJ900101HDFRRN09`, a **synthetic CURP from the OpenAPI examples** — not a real person's CURP. Replace it with the CURP you need to validate. ```python theme={null} from origoid import OrigoID client = OrigoID(api_key="...") envelope = client.renapo.validate_curp( curp="PELJ900101HDFRRN09", # synthetic — replace with real input ) if envelope.status == "OK" and envelope.type == "SUCCESS": # envelope.data holds the RENAPO record. print("CURP holder:", envelope.data) else: # Other result type — check the endpoint's response catalog to drive your logic. print(envelope.type, "—", envelope.message) ``` Every method returns an `Envelope` instance: `{ status, type, message, data, transaction_id, processed_at, billable, errors? }`. See [Response envelope](/en/envelope) for the contract. ## Methods by resource Methods are grouped by regulatory domain. Names use Python `snake_case`. ### `client.authentication` ```python theme={null} client.authentication.issue_token(expire_after=1800) ``` ### `client.renapo` ```python theme={null} client.renapo.validate_curp(curp="PELJ900101HDFRRN09") client.renapo.lookup_curp( given_names="JUAN", first_surname="PEREZ", second_surname="LOPEZ", date_of_birth="1990-01-01", gender="H", birth_state_code="DF", ) ``` ### `client.sat` `extract_csf` and `validate_cfdi` accept a `request=` dict because the underlying schemas allow alternative shapes (direct identifiers OR a document upload). Inside that dict use the JSON wire names (camelCase): ```python theme={null} client.sat.validate_rfc(rfc="PEZJ811011KI1") client.sat.extract_csf(request={ "rfc": "PEZJ811011KI1", "cif": "17060597619", }) client.sat.validate_cfdi(request={ "uuid": "7C8BD4EA-AE86-4CB5-88B8-C6E61E988A8B", "rfcEmisor": "PEZJ811011KI1", "rfcReceptor": "EMP170623KI3", "total": "999999.99", }) ``` ### `client.imss` ```python theme={null} client.imss.lookup_nss(curp="PELJ900101HDFRRN09") client.imss.get_employment_status( curp="PELJ900101HDFRRN09", nss="92038109713", ) ``` ### `client.ine` ```python theme={null} client.ine.validate_voter_list(request={ "cic": "123456789", "citizenIdentifier": "987654321", }) client.ine.extract_voter_id_data( front="", back="", ) client.ine.extract_qr_data(back="") ``` ### `client.compliance` ```python theme={null} client.compliance.search_sat69(request={"rfc": "PEZJ811011KI1"}) client.compliance.search_sat69b(request={"rfc": "PEZJ811011KI1"}) client.compliance.search_ofac(name="John Doe", min_similarity_score=85) client.compliance.search_peps(request={ "givenNames": "JUAN", "firstSurname": "PEREZ", "secondSurname": "LOPEZ", }) ``` ### `client.biometrics` ```python theme={null} client.biometrics.match_faces( face="", front="", threshold=80, document_type="INE", ) client.biometrics.check_liveness(selfie="") ``` ### `client.email` ```python theme={null} client.email.validate_email(email="user@example.com") ``` ### `client.proof_of_address` ```python theme={null} client.proof_of_address.extract_proof_of_address(file="") ``` ## Async client For applications built on [FastAPI](https://fastapi.tiangolo.com/), [aiohttp](https://docs.aiohttp.org/), [Starlette](https://www.starlette.io/), or other async frameworks, use `AsyncOrigoID`. Same method names, same arguments, same return types — you just `await` each call so it does not block the event loop while the HTTP request is in flight. ```python theme={null} import asyncio from origoid import AsyncOrigoID async def main(): client = AsyncOrigoID(api_key="...") env = await client.renapo.validate_curp(curp="PELJ900101HDFRRN09") if env.status == "OK": print(env.type) asyncio.run(main()) ``` To fire several calls in parallel (e.g. validating multiple CURPs at once), use `asyncio.gather` — the same pattern as `Promise.all` in JavaScript: ```python theme={null} results = await asyncio.gather( client.renapo.validate_curp(curp="PELJ900101HDFRRN09"), client.sat.validate_rfc(rfc="PEZJ811011KI1"), client.compliance.search_ofac(name="John Doe"), ) ``` If your application is not built on an async stack, use `OrigoID` — async only adds value when you need to handle many concurrent operations without spawning threads. ## Error handling The SDK distinguishes between **business errors** (returned inside the envelope) and **transport errors** (raised as exceptions). ### Business errors — inspect the envelope For any HTTP 200, including `INVALID_REQUEST`, the SDK returns an `Envelope`. Check `status` and `type` before using `data`: ```python theme={null} env = client.renapo.validate_curp(curp="BAD") if env.status == "ERROR": # INVALID_REQUEST, SERVICE_UNAVAILABLE, … print(env.type, env.message) for err in (env.errors or []): print(f" {err.field}: {err.code} — {err.message}") else: # status == "OK" — could still be a business-level NOT_FOUND match env.type: case "SUCCESS": print("CURP holder:", env.data) case "CURP_NOT_FOUND": print("No match") ``` See [Errors](/en/errors) and [Catalogs](/en/catalogs) for the full catalog of `type` codes. ### Transport errors — `try`/`except` For `401`, `429`, and unrecoverable failures the SDK raises typed exceptions: ```python theme={null} from origoid import OrigoID from origoid.core.api_error import ApiError client = OrigoID(api_key="...") try: env = client.renapo.validate_curp(curp="PELJ900101HDFRRN09") except ApiError as e: # e.status_code — 401, 429, etc. # e.body — envelope returned by the server print(e.status_code, e.body) ``` ## Per-call configuration (advanced) Pass `request_options` to override timeout, retries, or headers per call: ```python theme={null} client.ine.validate_voter_list( request={"cic": "123456789", "citizenIdentifier": "987654321"}, request_options={ "timeout_in_seconds": 120, # default 60 "max_retries": 3, # default 2 "additional_headers": {"x-trace-id": "..."}, }, ) ``` **Read this before tuning timeouts or retries.** The SDK only retries `5xx` and network failures, never successful business responses — so retries do **not** create duplicate billable calls when the API responded correctly. They **do** create extra calls when the request actually failed: a request that times out three times can consume three credits if the call eventually succeeded on a later attempt. * **Defaults (60 s, 2 retries) are right for almost every workload.** Change only with a specific reason. * Combining a long timeout with high `max_retries` (e.g. 120 s × 5) means a single failing request can occupy a thread for up to 10 minutes — bad for your throughput and your infrastructure. * Override per-call only on endpoints with known slow cold starts.