# Fincore's API Documentation v1.2

> **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](mailto: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](mailto: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):**

```json
{
  "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:**

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

**Response (200 OK):**

```json
{
  "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:

```json
{
  "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):**

```json
{
  "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`.

```json
{
  "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):**

```json
{
  "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:**

```json
{
  "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):**

```json
{
  "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

| Action | Method | Endpoint |
|  --- | --- | --- |
| List all | GET | `/v1/clients/{client_id}/webhooks` |
| Get one | GET | `/v1/clients/{client_id}/webhooks/{id}` |
| Create | POST | `/v1/clients/{client_id}/webhooks` |
| Update | PATCH | `/v1/clients/{client_id}/webhooks/{id}` |
| Delete | DELETE | `/v1/clients/{client_id}/webhooks/{id}` |


### 5.3 What a Money In notification looks like

```json
{
  "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)

```json
{
  "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)

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

#### How to tell SPEI apart from internal

| `sub_category` | `payer_institution` | Origin |
|  --- | --- | --- |
| `SPEI_CREDIT` | Banxico bank code (e.g. `40002`) | External - SPEI |
| `INT_CREDIT` | `90734` (Monato internal code) | Internal - book-to-book |


#### Body fields

| Field | Description |
|  --- | --- |
| `id` | Monato's internal transaction ID |
| `beneficiary_account` | Beneficiary CLABE |
| `payer_account` | Payer CLABE |
| `payer_name` | Payer's name |
| `payer_rfc` | Payer's RFC (`"ND"` if not provided) |
| `payer_institution` | Payer's bank code (Banxico) or `90734` for internal |
| `amount` | Amount credited as a string with 2 decimal places |
| `tracking_key` | SPEI tracking key or internal tracking ID |
| `sub_category` | `SPEI_CREDIT` or `INT_CREDIT` |
| `owner_id` | ID of the owner of the destination instrument |


### 5.4 Accepting or rejecting a Money In

| Your response | Effect |
|  --- | --- |
| `HTTP 201 Created` | You accept the deposit. Funds stay in your account. |
| `HTTP 422 Unprocessable Entity` | You reject the deposit. It is automatically refunded to the payer. |


```json
{
  "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
```

```json
{
  "description": "Refund due to incorrect amount",
  "amount": "123.00"
}
```

```json
{
  "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}
```

| Parameter | Description | Example |
|  --- | --- | --- |
| `transaction_status` | Filter by status | `LIQUIDATED` |
| `tracking_id` | Filter by SPEI tracking key | `20250520FINCHARNJK5NHQG` |
| `transaction_category` | Filter by category | `DEBIT_TRANS` |
| `bank_id` | Filter by bank | `1953a92c-...` |


## 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
```

```json
{
  "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)
```

```json
{
  "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:**

| Field | Constraint |
|  --- | --- |
| `amount` | Numeric string with exactly 2 decimal places. Greater than 0. |
| `currency` | Only `"MXN"` |
| `description` | Maximum 40 characters |
| `external_reference` | Numeric only, maximum 7 digits |


**Response (200 OK):**

```json
{
  "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
```

```json
{
  "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

| # | Condition | Detail |
|  --- | --- | --- |
| 1 | Valid UUID | The `instrument_id` must be a well-formed UUID |
| 2 | Belongs to the client | The instrument must be registered under your `client_id` |
| 3 | Limit not exceeded | Maximum of **10 active instruments** on the whitelist **per client** (not per instrument) |
| 4 | Minimum age | The instrument must be at least **72 hours** old since creation |
| 5 | Minimum transaction history | The 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
```

| Parameter | Type | Description |
|  --- | --- | --- |
| `client_id` | UUID | Your client identifier |
| `instrument_id` | UUID | The instrument you want to add to the whitelist |


**Body:** no body required.

```shell
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"
```

| Scenario | HTTP Status |
|  --- | --- |
| First time adding the instrument | `201 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

| HTTP | Cause |
|  --- | --- |
| `400 Bad Request` | `instrument_id` is not a valid UUID |
| `404 Not Found` | Instrument doesn't exist or doesn't belong to your client |
| `422 Unprocessable Entity` | Limit of 10 whitelisted instruments already reached (client-level) |
| `422 Unprocessable Entity` | Instrument is less than 72 hours old |
| `422 Unprocessable Entity` | Instrument doesn't have 3 previous successful transactions |


```json
HTTP/1.1 422 Unprocessable Entity
{
  "error": {
    "code": "WHITELIST_LIMIT_EXCEEDED",
    "message": "Instrument whitelist exceeds the limit of 10 active records."
  }
}
```

#### Limits and constraints

| Constraint | Value |
|  --- | --- |
| Max whitelisted instruments per client | 10 |
| Minimum instrument age | 72 hours since creation |
| Minimum prior successful transactions | 3 |
| 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
```

```json
{
  "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"
  }
}
```

```json
{
  "id": "09c9caac-...",
  "trackingId": "20250925FINCHCUCHFGMRLZ",
  "transactionStatus": "LIQUIDATED",
  "category": "INTER_TRANS",
  "subCategory": "INT_DEBIT"
}
```

### Validations

| Field | Rule |
|  --- | --- |
| `amount` | String with 2 decimal places, greater than 0 |
| `currency` | Only `"MXN"` |
| `description` | Less than 40 characters |
| `external_reference` | Numeric only, maximum 7 digits |
| Source instrument | Must exist, be active, and have sufficient funds |
| Destination instrument | Must be internal to Monato |


### When do you receive a Money In webhook?

| Situation | Webhook? |
|  --- | --- |
| Destination belongs to a different `owner_id` | Yes - `MONEY_IN` with `sub_category = "INT_CREDIT"` |
| Destination belongs to the same `owner_id` | No - 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
```

```json
{
  "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"
}
```

```json
{
  "id": "1eb4b5ac-...",
  "transactionStatus": "INITIALIZED",
  "metadata": {
    "dataCep": {
      "status": "PENDING",
      "cepUrl": "https://www.banxico.org/...",
      "validationId": "f4ebe9af-..."
    }
  }
}
```

### CEP statuses

| Status | What it means |
|  --- | --- |
| `INITIALIZED` | Transient initial state. Treat the same as `PENDING`. |
| `PENDING` | Waiting for the beneficiary's bank to respond. |
| `DELAYED` | Taking longer than expected, retries continue. |
| `COMPLETED` | CEP available and validated. |
| `FAILED` | CEP 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.


| Attempt | Status | Frequency | Elapsed time |
|  --- | --- | --- | --- |
| 1 | `INITIALIZED` |  | 0:00:00 |
| 2 - 5 | `PENDING` | Every 1 min | 0:01:00 - 0:04:00 |
| 6 | `DELAYED` | Every 1 min | 0:05:00 |
| 7 - 9 | `DELAYED` | Every 5 min | 0:10:00 - 0:20:00 |
| 10 - 37 | `DELAYED` | Every 15 min | 0: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
```

```json
{
  "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.


```json
{
  "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:**

| Error | Cause |
|  --- | --- |
| `400` - Account is already cancelled | Already cancelled |
| `400` - Only Private Accounts can be cancelled | Tried to cancel the Centralizing Account |
| `400` - Invalid account balance | Balance is not zero |
| `403` - Account does not belong to the specified client | `clientId` 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
```

```json
{
  "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:**

```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))
```

| Scenario | Response |
|  --- | --- |
| No `Idempotency-Key` | Normal flow, no protection |
| Invalid key format | `400 Bad Request` |
| Same key, same body | `200 OK` - cached response |
| Same key, different body | `409 Conflict` |
| Two simultaneous requests, same key | Second returns `409` |


## 12. Reports

* **Daily** and **Monthly** transaction reports and account statements.


```
POST /v1/reports/clients/{client_id}/report/download
```

| `report_type` | Description |
|  --- | --- |
| `DAILY` | Day's transactions |
| `MONTHLY` | Month's transactions |
| `DAILY_ACCOUNT_STATEMENT` | Daily account statement |
| `MONTHLY_ACCOUNT_STATEMENT` | Monthly account statement |


## 13. Error catalog

### Error structure

```json
{
  "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

| HTTP | Cause | Solution |
|  --- | --- | --- |
| `401 Unauthorized` | Token expired or invalid | Generate a new token |


### Money Out

| `error_detail` | HTTP | Cause | Solution |
|  --- | --- | --- | --- |
| `The account does not have sufficient funds.` | 400 | Insufficient balance | Check `availableBalance` |
| `The account is not currently active.` | 400 | Account blocked | Check account status |
| `Transaction Amount must be higher than 0.` | 400 | Amount 0 or negative | Validate amount |
| `Transaction currency unsupported.` | 400 | Only `"MXN"` accepted | Use `"MXN"` |
| `Transaction description must have less than 40 characters length.` | 400 | Exceeds 40 characters | Truncate description |
| `External reference should be numeric and have a maximum length of 7 digits.` | 400 | Non-numeric or >7 digits | Use digits only |
| `destination_not_found` | 404 | Instrument doesn't exist | Verify `instrument_id` |


### Internal transfers (deprecated)

| `error_detail` | HTTP | Cause |
|  --- | --- | --- |
| `external_transfer_not_allowed` | 409 | Internal to external SPEI account |


### Idempotency

| `error_detail` | HTTP | Cause |
|  --- | --- | --- |
| `Idempotency key does not match the request payload` | 409 | Same key, different body |
| `Operation money_out in progress` | 409 | Duplicate request in flight |
| Invalid key format | 400 | Not a valid UUID v5 |


### Private accounts - cancellation

| `error_detail` | HTTP | Cause |
|  --- | --- | --- |
| `Account is already cancelled` | 400 | Already cancelled |
| `Only Private Accounts can be cancelled` | 400 | Tried to cancel Centralizing Account |
| `Invalid account balance, account balance must be equal to 0` | 400 | Balance not zero |
| `Account does not belong to the specified client` | 403 | `clientId` mismatch |


### Instrument whitelist

| Cause | HTTP |
|  --- | --- |
| `instrument_id` not a valid UUID | 400 |
| Instrument doesn't exist or doesn't belong to client | 404 |
| Limit of 10 (client-level) reached | 422 |
| Less than 72 hours old | 422 |
| No 3 previous successful transactions | 422 |


### Penny Validation / CEP

| Situation | Cause |
|  --- | --- |
| `FAILED` after attempt 37 | No confirmation within 7h 20min |
| `DELAYED` for a long time | Destination bank slow; retries continue |


## Questions or issues?

* **Technical support:** [support@monato.com](mailto:support@monato.com)
* **OpenAPI reference:** [docs.monato.com/products/fincore/fincore-openapi](https://docs.monato.com/products/fincore/fincore-openapi)