Skip to content
Last updated

First time here? Start with What is Fincore? and follow the guide in order. Each section assumes you've read the one before it.

Looking for something specific? Use the table of contents below.


Table of contents

  1. What is Fincore?
  2. How it works: the data model
  3. Authentication
  4. Your first transfer (Quickstart)
  5. Receiving money (Money In)
  6. Sending money (Money Out)
    • 6.1 Send to a CLABE
    • 6.2 Send to a debit card
    • 6.3 Instrument whitelist for high-value transactions
  7. Internal transfers (book-to-book) - deprecated
  8. Validating a bank account (Penny Validation)
  9. Private accounts
  10. Business Units (sub-accounts)
  11. Idempotency: how to retry safely
  12. Reports
  13. Error catalog

1. What is Fincore?

Fincore is Monato's API for processing payments in Mexico. With it you can:

  • Receive money via SPEI from any bank in the Mexican financial system.
  • Send money to CLABE accounts or debit cards, with a whitelist for high-value payments without friction.
  • Move funds internally between accounts within Monato (no SPEI, near real-time).
  • Validate bank accounts before sending money to them.
  • Create sub-accounts for your own customers or business units.
  • Download reports of transactions and account statements. Fincore is not a bank. It's the layer that connects your system to SPEI and the Mexican financial system, using Finco Pay's infrastructure.

Ready to try it? Write to support@monato.com to get your staging API Key. That's all you need to get started.


2. How it works: the data model

Before making your first API call, it's worth understanding three concepts. Once you have them clear, everything else makes sense.

Centralizing Account

This is your main account at Monato. It acts as the hub for all your operations: it can send and receive money. Monato creates it for you when you activate your account - you don't need to create it yourself.

Think of it as your company's central vault: all money coming in or going out passes through here, unless you've created private accounts (see below).

Private Account

A private account is an independent CLABE you can assign to one of your customers or business units. By default, private accounts only receive money - they can't send it. When someone deposits into a private account, the funds arrive and are automatically swept into your Centralizing Account.

Think of it as a collection inbox: your customer deposits there, the money lands in your central vault.

Can a private account send money (Money Out)? Yes, but only if explicitly enabled at creation time, by sending "sender_receiver_type": true in the creation request (see section 9.1). This setting cannot be changed after the account is created - decide the correct behavior before creating it.

Instrument

An instrument is a reference to a bank account - either yours or a payment recipient's. Before sending money to someone, you need to register their account as an instrument.

Think of it as an entry in your payment contacts list: you save the account details once and then reference it by ID in every transaction.

There are two instrument types based on direction:

  • RECEIVER - only receives funds (e.g. a beneficiary's debit card).
  • SENDER_RECEIVER - can send and receive (your Centralizing Account has this type, and a private account can also have it if configured that way at creation).

Visual summary

Your company


Centralizing Account (SENDER_RECEIVER)
    │                        ▲
    │ sends money            │ receives money from private accounts
    ▼                        │
Destination                Private Accounts of your customers
Instrument                   (RECEIVER by default; SENDER_RECEIVER
(CLABE or card)               if configured that way at creation)

Customer (Business Unit)

If you need each sub-account to appear as a distinct legal entity on payment receipts (CEP), you can model it as a Customer. Each Customer has its own RFC, CLABE, and independent balance. More detail in the Business Units section.


3. Authentication

Fincore uses a two-step scheme: first you obtain a client_secret, then exchange it for an access_token that you use in every request.

Before you start: you need a clientId and an x-api-key. Write to support@monato.com if you don't have them yet.

Step 1 - Get the client_secret

GET /v1/clients/{clientId}/credentials/

Headers:

x-api-key: {your-api-key}

Response (200 OK):

{
  "data": [
    {
      "id": "e981c6d8-4d49-45f2-a7ee-f956dca15500",
      "client_id": "c2d1d1e3-3340-4170-980e-e9269bbbc551",
      "client_secret": "Ui9gx7AX...MLQ29SbsvsXI1...",
      "environment": "staging",
      "status": "ACTIVE"
    }
  ]
}

Save the client_secret value. You'll need it in the next step.

Step 2 - Generate an access token

POST /v1/clients/{clientId}/auth/credential-tokens

Headers:

x-api-key: {your-api-key}
Content-Type: application/json

Body:

{
  "client_id": "c2d1d1e3-3340-4170-980e-e9269bbbc551",
  "client_secret": "Ui9gx7AX...MLQ29SbsvsXI1..."
}

Response (200 OK):

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "status": "ACTIVE",
  "expires_at": "2025-03-06 11:16:59.491631"
}

Step 3 - Use the token

Authorization: Bearer {token}

Note: Tokens expire. If you receive a 401 Unauthorized error, generate a new token by repeating Step 2.


4. Your first transfer (Quickstart)

This section takes you from zero to your first outbound transfer in as few steps as possible. It assumes you already have your authentication token.

What you'll do

[Retrieve your account]  ->  [Register the recipient]  ->  [Send money]

Step 1 - Retrieve your Centralizing Account

GET /v1/clients/{clientId}/accounts

Headers:

Authorization: Bearer {token}

Response: look for the object where accountType = "CENTRALIZING_ACCOUNT" and note:

{
  "id": "24a726ac-...",
  "instrumentId": "709448c3-...",
  "bankId": "9d84b03a-...",
  "clientBankAdapterId": "5b3a-...",
  "clabeNumber": "734180000001000004",
  "availableBalance": "0.00"
}

(id -> your accountId · instrumentId -> use as source · bankId -> your bankId · clientBankAdapterId -> needed to create private accounts)

Step 2 - Register the recipient as an Instrument

POST /v1/clients/{clientId}/instruments

Body (transfer to a CLABE):

{
  "source_bank_id": "{your bankId}",
  "client_id": "{your clientId}",
  "type": "SENDER_RECEIVER",
  "rfc": "XAXX010101000",
  "alias": "Supplier ABC",
  "virtual_clabe": {
    "account_number": "064871131141",
    "clabe_number": "021180064871131141",
    "holder_name": "Beneficiary Full Name",
    "destination_bank_id": "{bankId of the destination bank}"
  }
}

How do I find the destination_bank_id? Use GET /v1/banks and search by name. For example, Banamex has ID 3114d179-8a10-40b9-b040-20ff464b50e0.

Don't have the RFC? You can send "ND" in the rfc field.

holder_name has a maximum of 40 characters.

Response (200 OK): save the id of the created instrument. This is your destination_instrument_id.

{
  "id": "d3fdb481-2058-46c8-807d-4eaf866ae1ec",
  "alias": "Supplier ABC",
  "type": "SENDER_RECEIVER"
}

Step 3 - Send money

You now have everything you need: your source_instrument_id (from your centralizing account) and the destination_instrument_id (from the recipient). The endpoint, the full body, and the field constraints are documented in detail in section 6.1 - Send to a CLABE - it's exactly the same flow, so we don't repeat it here.

Expected response (200 OK):

{
  "id": "16811ee8-...",
  "trackingId": "20250306FINCHVLIKQ5SKUM",
  "transactionStatus": "INITIALIZED",
  "amount": "1500.00",
  "currency": "MXN"
}

What does INITIALIZED mean? The transaction was accepted by Fincore and is being sent to SPEI. It does not mean the funds have arrived at the recipient yet. Final statuses are LIQUIDATED (successful), FAILED, or REFUNDED (if the payment was accepted and later refunded - see section 5.5).

Want to send to a debit card instead of a CLABE? Go to Send to a debit card.

Is the recipient also on Finco Pay? The API detects it automatically and routes the transaction as an internal transfer (no SPEI, near real-time). No extra configuration needed.


5. Receiving money (Money In)

When someone sends you money - whether via SPEI from another bank or from another account within Monato - Fincore notifies you through a webhook with msg_name = "MONEY_IN".

5.1 Register your endpoint for notifications

POST /v1/clients/{client_id}/webhooks

Body:

{
  "client_id": "{your clientId}",
  "url": "https://your-server.com/money-in-webhook",
  "token": "your-secret-token",
  "webhook_type": "MONEY_IN",
  "auth_type": "AUTH"
}

What is token for? Fincore includes it in every notification it sends you, so you can verify the request is genuinely from us and not from a third party.

How do I generate a good token? We recommend a random secret of at least 32 bytes - for example, a UUID v4, or a SHA-256 hash of a secure random source. Avoid predictable values (your company name, dates, etc.) and store it securely in your backend, just as you would any other authentication secret.

Response (200 OK):

{
  "id": "0c2d358f-...",
  "webhookType": "MONEY_IN",
  "webhookStatus": "ACTIVE"
}

Note on format: the request body uses snake_case (e.g. webhook_type) but the response uses camelCase (e.g. webhookType). This is consistent throughout the API.

5.2 Managing your webhooks

ActionMethodEndpoint
List allGET/v1/clients/{client_id}/webhooks
Get oneGET/v1/clients/{client_id}/webhooks/{id}
CreatePOST/v1/clients/{client_id}/webhooks
UpdatePATCH/v1/clients/{client_id}/webhooks/{id}
DeleteDELETE/v1/clients/{client_id}/webhooks/{id}

5.3 What a Money In notification looks like

{
  "id_msg": "a7a126e8-fa74-411c-ad2b-b000f277bb0d",
  "msg_name": "MONEY_IN",
  "msg_date": "2025-04-02",
  "body": { }
}

Example: incoming money via SPEI (from another bank)

{
  "id_msg": "a7a126e8-...",
  "msg_name": "MONEY_IN",
  "msg_date": "2025-04-02",
  "body": {
    "id": "0196da9a-...",
    "beneficiary_account": "734180123045603216",
    "beneficiary_name": "John Smith",
    "payer_account": "137180210044008609",
    "payer_name": "Juan Perez",
    "payer_institution": "40002",
    "amount": "123.00",
    "transaction_date": "2025-04-02 10:14:05",
    "tracking_key": "50118609TBRNZ00I07219647",
    "payment_concept": "Payment for invoice 4567",
    "sub_category": "SPEI_CREDIT",
    "owner_id": "24f1e5d5-..."
  }
}

Example: internal incoming money (between Monato accounts)

{
  "body": {
    "payer_institution": "90734",
    "sub_category": "INT_CREDIT"
  }
}

How to tell SPEI apart from internal

sub_categorypayer_institutionOrigin
SPEI_CREDITBanxico bank code (e.g. 40002)External - SPEI
INT_CREDIT90734 (Monato internal code)Internal - book-to-book

Body fields

FieldDescription
idMonato's internal transaction ID
beneficiary_accountBeneficiary CLABE
payer_accountPayer CLABE
payer_namePayer's name
payer_rfcPayer's RFC ("ND" if not provided)
payer_institutionPayer's bank code (Banxico) or 90734 for internal
amountAmount credited as a string with 2 decimal places
tracking_keySPEI tracking key or internal tracking ID
sub_categorySPEI_CREDIT or INT_CREDIT
owner_idID of the owner of the destination instrument

5.4 Accepting or rejecting a Money In

Your responseEffect
HTTP 201 CreatedYou accept the deposit. Funds stay in your account.
HTTP 422 Unprocessable EntityYou reject the deposit. It is automatically refunded to the payer.
{
  "refundReason": "Unexpected amount"
}

Internal Money In: if sub_category = "INT_CREDIT", the funds have already moved by the time you receive the webhook. Your HTTP response has no effect. If you need to reverse it, create a new internal transaction in the opposite direction (see section 7).

5.5 Refunding an already accepted Money In

POST /v1/clients/{clientId}/transactions/{transactionId}/refund
{
  "description": "Refund due to incorrect amount",
  "amount": "123.00"
}
{
  "id": "e43171ad-...",
  "transactionStatus": "LIQUIDATED",
  "originalTransactionId": "a1392ef1-..."
}

After the refund you'll have two transactions:

  • The original with transactionStatus = "REFUNDED".
  • The refund transaction with transactionStatus = "LIQUIDATED".

SPEI only. Internals have no /refund endpoint.

5.6 Retrieve a transaction by ID

GET /v1/clients/{clientId}/transactions/{transactionId}
ParameterDescriptionExample
transaction_statusFilter by statusLIQUIDATED
tracking_idFilter by SPEI tracking key20250520FINCHARNJK5NHQG
transaction_categoryFilter by categoryDEBIT_TRANS
bank_idFilter by bank1953a92c-...

6. Sending money (Money Out)

Money Out is the endpoint for moving funds from your Centralizing Account (or from a private account configured as SENDER_RECEIVER, see section 2) outward. It works for two destination types:

  • CLABE - a standard bank account at any bank in the system.
  • Debit card - when you only have the 16-digit card number.

6.1 Send to a CLABE

What is a CLABE? The Clave Bancaria Estandarizada (CLABE) is the 18-digit identifier used by the Mexican financial system to route SPEI transfers to a specific bank account - it's the Mexican equivalent of an IBAN.

This is the standard flow, including from the Quickstart (section 4).

Register instrument:

POST /v1/clients/{clientId}/instruments
{
  "source_bank_id": "{your bankId}",
  "client_id": "{your clientId}",
  "type": "SENDER_RECEIVER",
  "rfc": "XAXX010101000",
  "alias": "Supplier ABC",
  "virtual_clabe": {
    "account_number": "064871131141",
    "clabe_number": "021180064871131141",
    "holder_name": "Beneficiary Name",
    "destination_bank_id": "{bankId of the destination bank}"
  }
}

Execute the transaction:

POST /v1/transactions/money_out

Headers:

Authorization: Bearer {token}
Content-Type: application/json
Idempotency-Key: {uuid-v5}   <- optional but strongly recommended (see section 11)
{
  "client_id": "{your clientId}",
  "source_instrument_id": "{your centralizing account instrumentId}",
  "destination_instrument_id": "{destination instrumentId}",
  "transaction_request": {
    "external_reference": "1234567",
    "description": "Supplier payment",
    "amount": "5000.00",
    "currency": "MXN"
  }
}

Body field constraints:

FieldConstraint
amountNumeric string with exactly 2 decimal places. Greater than 0.
currencyOnly "MXN"
descriptionMaximum 40 characters
external_referenceNumeric only, maximum 7 digits

Response (200 OK):

{
  "id": "16811ee8-...",
  "trackingId": "20250306FINCHVLIKQ5SKUM",
  "transactionStatus": "INITIALIZED",
  "amount": "1500.00",
  "currency": "MXN"
}

Possible statuses: INITIALIZED (transient, being sent to SPEI) -> LIQUIDATED (successful), FAILED, or REFUNDED (if accepted and later refunded - see section 5.5).

6.2 Send to a debit card

POST /v1/clients/{clientId}/instruments
{
  "source_bank_id": "{your bankId}",
  "client_id": "{your clientId}",
  "type": "RECEIVER",
  "rfc": "XAXX010101000",
  "alias": "Pedro's card",
  "debit_card": {
    "destination_bank_id": "{bankId of the issuing bank}",
    "card_number": "5579072268574100",
    "holder_name": "Pedro Navajas"
  }
}

Key difference: debit cards are RECEIVER only - they can receive money but not send it.

Once the instrument is created, the Money Out transaction uses exactly the same endpoint and body as for a CLABE.

6.3 Instrument whitelist for high-value transactions

By default, Money Out transactions above $500,000 MXN go through a manual review before being executed.

If you have recurring, trusted recipients, you can add them to a whitelist. Instruments on the whitelist bypass manual review.

Important: every bypass transaction is logged internally with reason: WHITELIST_BYPASS for traceability. Monato also receives an internal notification for every bypass.

The limit of 10 is at the client level, not per instrument. The maximum of 10 whitelisted instruments is counted across the total instruments of your client_id - not 10 "per instrument," but 10 total across all your instruments.

Requirements to add an instrument to the whitelist

#ConditionDetail
1Valid UUIDThe instrument_id must be a well-formed UUID
2Belongs to the clientThe instrument must be registered under your client_id
3Limit not exceededMaximum of 10 active instruments on the whitelist per client (not per instrument)
4Minimum ageThe instrument must be at least 72 hours old since creation
5Minimum transaction historyThe instrument must have at least 3 previous successful transactions

Endpoint

POST /api/v1/{client_id}/instruments/{instrument_id}/whitelist

Headers:

Authorization: Bearer {token}
Content-Type: application/json
ParameterTypeDescription
client_idUUIDYour client identifier
instrument_idUUIDThe instrument you want to add to the whitelist

Body: no body required.

curl -X POST \
  "https://api.fincopay.com/api/v1/{client_id}/instruments/{instrument_id}/whitelist" \
  -H "Authorization: Bearer {your_token}" \
  -H "Content-Type: application/json"
ScenarioHTTP Status
First time adding the instrument201 Created
Instrument was already whitelisted (retry)200 OK

The response body schema is pending confirmation.

This endpoint is idempotent

Sending the same request twice with the same client_id and instrument_id will not create duplicate records.

Possible errors

HTTPCause
400 Bad Requestinstrument_id is not a valid UUID
404 Not FoundInstrument doesn't exist or doesn't belong to your client
422 Unprocessable EntityLimit of 10 whitelisted instruments already reached (client-level)
422 Unprocessable EntityInstrument is less than 72 hours old
422 Unprocessable EntityInstrument doesn't have 3 previous successful transactions
HTTP/1.1 422 Unprocessable Entity
{
  "error": {
    "code": "WHITELIST_LIMIT_EXCEEDED",
    "message": "Instrument whitelist exceeds the limit of 10 active records."
  }
}

Limits and constraints

ConstraintValue
Max whitelisted instruments per client10
Minimum instrument age72 hours since creation
Minimum prior successful transactions3
Transaction amount threshold for bypass$500,000 MXN

6.4 Idempotency in Money Out

Money Out supports the Idempotency-Key header. Always use it in production. See section 11 - Idempotency.


7. Internal transfers (book-to-book) - deprecated

⚠️ This dedicated endpoint is deprecated for new integrations. POST /v1/transactions/money_out automatically detects when the destination is also a Finco Pay account and routes it as an internal transfer, with no added friction. Use Money Out for all new cases; this section is kept only for compatibility with existing integrations that already use this endpoint.

Internal transfers move funds between two accounts within Monato without going through SPEI. They settle instantly and do not generate a CEP.

Endpoint

POST /v1/transactions/internal_transaction
{
  "client_id": "{your clientId}",
  "source_instrument_id": "{origin instrumentId}",
  "destination_instrument_id": "{destination instrumentId}",
  "transaction_request": {
    "amount": "1.90",
    "currency": "MXN",
    "description": "Movement between BUs",
    "external_reference": "1238766"
  }
}
{
  "id": "09c9caac-...",
  "trackingId": "20250925FINCHCUCHFGMRLZ",
  "transactionStatus": "LIQUIDATED",
  "category": "INTER_TRANS",
  "subCategory": "INT_DEBIT"
}

Validations

FieldRule
amountString with 2 decimal places, greater than 0
currencyOnly "MXN"
descriptionLess than 40 characters
external_referenceNumeric only, maximum 7 digits
Source instrumentMust exist, be active, and have sufficient funds
Destination instrumentMust be internal to Monato

When do you receive a Money In webhook?

SituationWebhook?
Destination belongs to a different owner_idYes - MONEY_IN with sub_category = "INT_CREDIT"
Destination belongs to the same owner_idNo - the POST response is the source of truth

Known bug: the dashboard shows a "resend webhook" option for same-owner internal transfers, but since no event is created, there is nothing to resend.

How to reverse an internal transfer

No /refund endpoint exists. Create a new internal transaction in the opposite direction.


8. Validating a bank account (Penny Validation)

How it works:

  1. You send $0.01 MXN to the account you want to verify.
  2. Banxico generates a CEP with the account holder's verified details.
  3. Fincore delivers that CEP to you via webhook.

Prerequisites

  • Have your Centralizing Account active.
  • Have already registered the account to validate as an Instrument.
  • Have registered a webhook of type CEP.

Execute the validation

POST /v1/transactions/penny_validation
{
  "client_id": "{your clientId}",
  "source_instrument_id": "{your centralizing account instrumentId}",
  "destination_instrument_id": "{instrumentId of the account to validate}",
  "description": "Account validation",
  "external_reference": "1234567"
}
{
  "id": "1eb4b5ac-...",
  "transactionStatus": "INITIALIZED",
  "metadata": {
    "dataCep": {
      "status": "PENDING",
      "cepUrl": "https://www.banxico.org/...",
      "validationId": "f4ebe9af-..."
    }
  }
}

CEP statuses

StatusWhat it means
INITIALIZEDTransient initial state. Treat the same as PENDING.
PENDINGWaiting for the beneficiary's bank to respond.
DELAYEDTaking longer than expected, retries continue.
COMPLETEDCEP available and validated.
FAILEDCEP could not be obtained after all retries.

CEP webhook retry policy

If your server doesn't respond, Fincore retries up to 37 times over a total window of 7h 20min.

Important - the scraper runs in parallel: the process that polls the CEP status runs in parallel to Penny Validation - it is not a sequential phase that starts afterward. The PENDING and DELAYED statuses are emulated by Monato between polls; the scraper only reports terminal states.

AttemptStatusFrequencyElapsed time
1INITIALIZED0:00:00
2 - 5PENDINGEvery 1 min0:01:00 - 0:04:00
6DELAYEDEvery 1 min0:05:00
7 - 9DELAYEDEvery 5 min0:10:00 - 0:20:00
10 - 37DELAYEDEvery 15 min0:35:00 - 7:20:00

If by attempt 37 there is still no CEP confirmation -> status is set to FAILED.


9. Private accounts

Private accounts are CLABEs you can assign to your customers so they can deposit to you - unless configured as SENDER_RECEIVER (see below), in which case they can also send money.

9.1 Create a private account

POST /v1/clients/{clientId}/private_accounts
{
  "bank_id": "{your bankId}",
  "owner_id": "{your clientId}",
  "client_bank_adapter_id": "{your clientBankAdapterId}",
  "client_id": "{your clientId}",
  "account_id": "{your centralizing account accountId}",
  "sender_receiver_type": false
}

What does sender_receiver_type do? If sent as true, the private account is enabled to send money (Money Out), in addition to receiving. If omitted or false (default), the account can only receive. This decision is permanent - it cannot be changed after the account is created.

{
  "id": "8d3e1f2a-...",
  "clabeNumber": "734180000700000004",
  "accountType": "PRIVATE_ACCOUNT",
  "accountStatus": "ACTIVE"
}

9.2 Private account lifecycle

ACTIVE ---- block ---- BLOCKED
  │                       │
  │                    activate
  │                       │
  │<----------------------┘

  └---- cancel ---- CANCELLED (permanent)

Common errors when cancelling:

ErrorCause
400 - Account is already cancelledAlready cancelled
400 - Only Private Accounts can be cancelledTried to cancel the Centralizing Account
400 - Invalid account balanceBalance is not zero
403 - Account does not belong to the specified clientclientId mismatch

10. Business Units (sub-accounts)

Business Units let you create sub-accounts that appear as independent legal entities on payment receipts, modeled in the API as Customers.

10.1 Create an account for a Business Unit

POST /v1/clients/{clientId}/customers/{customersId}/private_accounts
{
  "client_id": "{your clientId}",
  "client_bank_adapter_id": "{your clientBankAdapterId}",
  "bank_id": "{your bankId}",
  "owner_id": "{customerId of the Business Unit}"
}

10.2 List your Business Units

GET /v1/clients/{clientId}/customers

Important: only use Customers with customerValidationStatus = "VALIDATED" in production.


11. Idempotency: how to retry safely

Idempotency-Key: 66c0b04f-97d6-592d-8396-199819064afa

TTL: 24 hours.

body_hash = SHA-256(canonical JSON of body with keys sorted alphabetically)
name = client_id + "money_out" + body_hash
Idempotency-Key = UUIDv5(NAMESPACE, name)

Python:

import json, hashlib, uuid
 
NAMESPACE = uuid.UUID("086fc9ec-d591-4045-bde4-3f9439506b08")  # staging
 
def generate_key(client_id: str, body: dict) -> str:
    canonical = json.dumps(body, sort_keys=True, separators=(',', ':'))
    body_hash = hashlib.sha256(canonical.encode()).hexdigest()
    return str(uuid.uuid5(NAMESPACE, client_id + "money_out" + body_hash))
ScenarioResponse
No Idempotency-KeyNormal flow, no protection
Invalid key format400 Bad Request
Same key, same body200 OK - cached response
Same key, different body409 Conflict
Two simultaneous requests, same keySecond returns 409

12. Reports

  • Daily and Monthly transaction reports and account statements.
POST /v1/reports/clients/{client_id}/report/download
report_typeDescription
DAILYDay's transactions
MONTHLYMonth's transactions
DAILY_ACCOUNT_STATEMENTDaily account statement
MONTHLY_ACCOUNT_STATEMENTMonthly account statement

13. Error catalog

Error structure

{
  "code": 9,
  "message": "API Error",
  "details": [
    {
      "reason": "FAILED_PRECONDITION",
      "domain": "CORE",
      "metadata": {
        "error_detail": "The account does not have sufficient funds.",
        "http_code": "400",
        "error_code": "10-E4120"
      }
    }
  ]
}

Only domain: "CORE" / error_code: "10-E4120" is confirmed, for insufficient funds.

Authentication

HTTPCauseSolution
401 UnauthorizedToken expired or invalidGenerate a new token

Money Out

error_detailHTTPCauseSolution
The account does not have sufficient funds.400Insufficient balanceCheck availableBalance
The account is not currently active.400Account blockedCheck account status
Transaction Amount must be higher than 0.400Amount 0 or negativeValidate amount
Transaction currency unsupported.400Only "MXN" acceptedUse "MXN"
Transaction description must have less than 40 characters length.400Exceeds 40 charactersTruncate description
External reference should be numeric and have a maximum length of 7 digits.400Non-numeric or >7 digitsUse digits only
destination_not_found404Instrument doesn't existVerify instrument_id

Internal transfers (deprecated)

error_detailHTTPCause
external_transfer_not_allowed409Internal to external SPEI account

Idempotency

error_detailHTTPCause
Idempotency key does not match the request payload409Same key, different body
Operation money_out in progress409Duplicate request in flight
Invalid key format400Not a valid UUID v5

Private accounts - cancellation

error_detailHTTPCause
Account is already cancelled400Already cancelled
Only Private Accounts can be cancelled400Tried to cancel Centralizing Account
Invalid account balance, account balance must be equal to 0400Balance not zero
Account does not belong to the specified client403clientId mismatch

Instrument whitelist

CauseHTTP
instrument_id not a valid UUID400
Instrument doesn't exist or doesn't belong to client404
Limit of 10 (client-level) reached422
Less than 72 hours old422
No 3 previous successful transactions422

Penny Validation / CEP

SituationCause
FAILED after attempt 37No confirmation within 7h 20min
DELAYED for a long timeDestination bank slow; retries continue

Questions or issues?