A beneficiary is a destination account saved against the API key's user_id — a per-user allow-list, never shared across accounts. Persisting beneficiaries gives you three concrete behaviors: 1) auto-fill of bank and type on later validations, 2) a priority source for resolving masked PANs that the OCR cannot complete on its own, and 3) the bilingual alias that exports and the account statement in Finance carry.
The three account types
MexCep detects account_type from digit count (src/Support/CardBins.php::detectAccountType):
clabe— exactly 18 digits. Bank derived from the 3-digit prefix viaBanxicoBanks::bankFromClabe.bank_codeis not required in the body.card— 13 to 19 digits, excluding 18 (reserved for CLABE) and 10 (reserved for phone). Bank inferred from BIN/dictionary.bank_codeis not required.phone— exactly 10 digits. This is a SPEI DiMo handle.bank_codeis mandatory in the body because no public phone-to-bank mapping exists; the endpoint replies422 bank_code_required_for_phonewhen it is missing. The catalog of codes lives atGET /v1/public/banks.
To validate structure without persisting (CLABE checksum or card Luhn) use GET /v1/beneficiaries/validate-account.
Masked PAN and OCR
When POST /v1/validate-ocr extracts an account from a receipt in the shape ••••5678 or 4111 •••• •••• 1111, the pipeline routes through NormalizationService::resolveMaskedAccount. The lookup uses the visible trailing digits in this order:
- User allow-list —
BeneficiaryRepository::findByPartialAccountfilters the user's active beneficiaries by suffix +length_hint+visible_first. Single match → resolved. - Global account directory by suffix — only when the allow-list does not resolve. Returns
masked_catalog_uniqueon a single match,masked_probe_pendingwith candidates for 2..N matches (N =clabe.probe_max_candidates, default 3),masked_ambiguous_in_catalogabove that, ormasked_unresolvablewhen no row carries that suffix.
Without registered beneficiaries a masked receipt drops straight to the global suffix lookup and may stay ambiguous. Registering the accounts you validate often turns these cases into deterministic resolutions.
Create, archive, import
POST /v1/beneficiariescreates one. Accepts an optionalaliasused in exports.GET /v1/beneficiarieslists with the tri-state?with_archived=filter (0/null = active, 1 = archived, omitted = both).DELETE /v1/beneficiaries/{id}archives — a soft-delete (UPDATE user_beneficiaries SET status='inactive'). The public API does not expose hard-delete; archived rows can be restored from the admin panel when the deploy operator needs to.PATCH /v1/beneficiaries/{id}edits the alias orbank_code(mandatory when the type isphoneand the field is included in the edit).
For bulk creation the endpoint is POST /v1/beneficiaries/imports — see Bulk imports for the preview → commit flow and the manual+Gemini hybrid parser.