Create bank transaction

Creates a bank transaction that transfers money from one of your accounts to a recipient
enrolled in it.

Workflow

  • Individual transfer (no bank_payroll_id): created with status draft and
    transition enqueue, automatically enqueued for processing at the bank, which approves
    or declines it. Email notifications are sent based on company settings.
  • Payroll transfer (bank_payroll_id set): created as draft with no transition,
    dispatched only when the whole payroll is authorized — never individually.
  • transition: null creates a draft that stays put (see the transition field).
  • Split transfer (split: true): an amount over the bank's per-transfer cap is broken
    into the tranches the bank accepts and created atomically, and the response is an
    array
    . Read the cap first with
    GET /v1/banking/bank_transactions/transfer_limit. Without split, an amount over the
    cap is still a 422 and the response is still a single object.

Prerequisites

  1. sender_id — an account reachable through a bank key, with sufficient funds
    (GET /v1/banking/bank_accounts).
  2. recipient_id — a recipient enrolled in the sending account
    (recipient.owner_id == sender_id): GET /v1/banking/bank_recipients?owner_id={sender_id}.
  3. bank_key_id (optional) — defaults to the account's highest-priority approved
    key (GET /v1/banking/bank_keys).

Every validation rule is documented on its request-body field — read amount (minor unit
per currency, per-bank interbank ceilings), description (base character set, per-bank
caps) and recipient_id (status requirements) before building the payload. CLP has no
subunit — send 1000 for $1.000
. See
Amounts and currency.

Recent Requests
Log in to see full request history
TimeStatusUser Agent
Retrieving recent requests…
LoadingLoading…
Body Params

Transaction details including sender, recipient, amount, and optional parameters

uuid
required

The source bank account for the transfer. Must be an account you have access to through
a bank key. Find available accounts: GET /v1/banking/bank_accounts

uuid
required

The bank recipient who will receive the transfer. Must be enrolled in the sending
account
recipient.owner_id == sender_id. Recipients belong to a bank account,
not to the company: the same person enrolled in another of your accounts is a different
recipient with a different id, rejected with
422 "Recipient must belong to same account as transaction". Find recipients of the
sending account: GET /v1/banking/bank_recipients?owner_id={sender_id}.

A transfer that enqueues (the default) needs the recipient in approved; a payroll
transfer (bank_payroll_id) and a transition: null draft accept any status. A
deleted recipient is always rejected. See
Tutorial: Create and manage bank recipients.

integer
required
≥ 1

Transfer amount in the minor unit of the sending account's currency.
CLP has no subunit: the minor unit IS the peso — to transfer $1.000 send 1000,
not 100000. MXN (STP) and PEN (BCP) do take the hundredth. See
Amounts and currency.

Must be greater than 0. Interbank transfers (sender and recipient at different
banks) are additionally capped per bank — 7.000.000 CLP at most Chilean banks,
5.000.000 CLP at Banco de Chile; same-bank transfers are not capped. Only two things
lift the ceiling ("being in a payroll" is not by itself one): lbtr: true where
supported (Banco de Chile and Santander), or a deferred payroll (online: false) —
at Banco de Chile and BICE an online payroll keeps the ceiling, while at BCI any
payroll lifts it (BCI payrolls are deferred by construction).

Banco BCP (PEN) is the exception to the CLP numbers: minimum 100 (S/ 1,00) and
maximum 99999999 (S/ 999.999,99).

string
required
length ≥ 1
^[A-zÀ-ú0-9 ]+$

Description shown in bank statements and notification emails, as the reference for both
sender and recipient.

Base rule (all banks): validated against /\A[A-zÀ-ú0-9 ]+\Z/ — letters (accented
included), digits and spaces; read literally, the class also admits a few stray symbols
(_, [, ], ^, `, \, ×, ÷ — exact ranges in the guide linked below).
No length limit. Punctuation (-, #, ., ,, /,
(, ), :, +, &) is rejected:
422 "Validation failed: Description is invalid". Write Pago servicios Factura 1234,
not Payment for services - Invoice #1234.

The sending bank narrows it — the base rule (and the pattern above, which only
mirrors it) is necessary, not sufficient:

Sending bankMax lengthNarrower character set
Banco BICE14letters/digits only: spaces, accents and punctuation are stripped silently, then cut to 14 — BICE rewrites instead of rejecting
BCI30no accents
Banco de Chile, Santander, Scotiabank, Itaú, Cardda wallet70base rule
Banco Security, Banco Internacional, STP, BCPno capbase rule

Over the cap →
422 "Validation failed: Description is too long (maximum is N characters)". BICE never
returns that error (it rewrites), but rejects a description with nothing left after the
strip: 422 "Validation failed: Description only allows alphanumeric characters".

BCI supplier payrolls (category: "suppliers"): must be exactly FACT + space +
1–12 digits (FACT 1234), else
422 "Validation failed: Description must be FACT, followed by a space and 1 to 12 digits".

Banco Security, the BICE Connect path and STP forward only the first 40 characters. Full
detail (exact character ranges, BICE rewrite examples):
Bank transfer description rules.

uuid

Optional selector for the bank key (crawler credential) to use. When omitted, Cardda
picks the sending account's highest-priority approved key for your user (priority
asc, then most recent); passing it explicitly makes the request deterministic when more
than one key reaches the account. Find keys: GET /v1/banking/bank_keys

uuid | null

Optional payroll batch to attach this transaction to. When provided, the transaction is
created as a draft without transition (see transition) and processed when the payroll
is authorized; the recipient may have any status except deleted. Cannot be combined
with budget_id. The payroll itself must be in draft with no transition, else
422 "Validation failed: Payroll status must be draft" /
422 "Validation failed: Payroll transition must be null".
Find payrolls: GET /v1/banking/bank_payrolls?sender_id={account_id}

uuid | null

Optional UUID to safely retry without creating duplicates. Uniqueness is scoped to the
owning account (owner_id), which here always equals sender_id (the database
enforces it for locally initiated transfers). Reusing the pair fails with
422 "Validation failed: Idempotency has already been taken".

metadata
object | null

Optional JSON object for custom data (cost centers, reference numbers). Preserved but
not processed by the banking system.

string | null

Optional internal notes, stored but not sent to the bank — unlike description, any
characters, no length limit.

boolean | null
Defaults to false

Enable LBTR (Liquidación Bruta en Tiempo Real, Chile's RTGS) for high-value
transfers, lifting the per-bank interbank amount cap. Only Banco de Chile
(cl_banco_de_chile) and Santander (cl_banco_santander_chile) support it
(422 "Lbtr is not yet implemented at bank {bank_id}" elsewhere), and it is
interbank only (422 "Lbtr cannot create lbtr transaction if the sender and recipient belong to the same bank").

boolean | null
Defaults to false

Ask the API to break an amount over the per-transfer cap into the tranches the bank
accepts — as many of max_amount as fit plus the remainder — instead of you POSTing
one tranche at a time. Opt-in: without it an amount over the cap is still rejected
with 422 "Amount must be less than or equal to N", and the response is still the
single object it has always been.

With split: true the 201 body is an ARRAY, one object per row created, even
when the amount fitted in a single transfer (an array of one). Read the cap the
tranching will use from GET /v1/banking/bank_transactions/transfer_limit: the
transfer it answers max_amount: null for has no cap and is never split. Ask it
instead of inferring the cap — in particular lbtr: true does not by itself mean
"no cap"
. LBTR lifts the cap only at the two banks that implement it, Banco de Chile
and Santander (see lbtr above); out of any other bank the ordinary cap still applies
and the transfer is rejected at creation anyway.

The whole batch is created in one database transaction: either every row lands or
none does. Two rejections, both before anything is written:

  • more than max_transactions tranches (see transfer_limit) →
    422 "Amount would be split into N transactions, over the maximum of M";
  • combined with payable_transactions422. Splitting an invoice payment is
    POST /v1/erp/payouts/payables/{id}/create_transactions, which also links each row
    to the payable and checks it is not overpaid.

The remainder is not redistributed, and every tranche is validated as the ordinary
transfer it is — including the sending bank's minimum. So a remainder under that
minimum fails, and because the batch is atomic the whole thing is rejected and nothing
is created. The tranching is amount / max_amount full tranches plus amount % max_amount, so the test is on the remainder and it repeats after every full
tranche
— it is not a one-off window just above the cap.

The one bank with a floor today is BCP (amount >= 100, the PEN floor of S/ 1,00,
cap 99999999): amount: 100000000 tranches into 99999999 + 1, and the 1 is a
422 "Amount must be greater than or equal to 100". So does amount: 199999999,
which tranches into 99999999 + 99999999 + 1 — and so does every window
k × 99999999 + 1k × 99999999 + 99, for any k >= 1. What goes through is a
remainder of exactly 0 or of at least 100, and anything that still fits one
transfer. The batch remains capped at max_transactions tranches on top of this.

idempotency_id identifies the batch: it is stamped on the first row only, and a
retry with the same id is refused before any row is created.

Note each row is a transfer of its own, so a split of N produces N transfer
commissions — the same as creating the N transfers by hand.

uuid | null

Optional balance group ("presupuesto") to charge instead of the company's global pool.
Frozen at creation. Only valid on CLP wallet (Cardda) transfers — on a real-bank
transfer: 422 "Validation failed: Budget can only be imputed to CLP wallet transfers".
Must be a well-formed UUID (checked before authorization: malformed is 422, never a
misleading 403) and a budget you may operate for the sending account's company
the sender's holder, not the company-id header. Cannot be combined with
bank_payroll_id. Find budgets: GET /v1/finance/budgets.

string | null
enum
Defaults to enqueue

What to do right after creating. Defaults to enqueue — a plain create is sent to the
bank immediately. enqueue and null are the only usable values: transition: null
creates a draft that stays put (nothing sent, approved-recipient rule not
applied); send it later with POST /v1/banking/bank_transactions/{id}/retry_enqueue.
The rest are rejected — preauthorize/authorize are invalid for a draft
(422 "Transition preauthorize is not valid for status draft"), dequeue needs a
decline_motive this endpoint does not accept (422 "Decline motive can't be blank");
use the dedicated lifecycle endpoints.

With bank_payroll_id, the server silently rewrites enqueue — and only
enqueue — to null: payroll transfers are dispatched when the payroll is authorized.
Other values are rejected as above, not rewritten.

Allowed:
payable_transactions
array of objects | null

Optional array linking this transaction to invoices/payables (requires Pay product):
each item allocates a portion of the amount to a payable for reconciliation.

payable_transactions
uuid | null
deprecated

Deprecated — use sender_id. Not stored; a legacy fallback to resolve the acting
account when sender_id is absent, and that path then fails validation for lack of a
sender.

Headers
uuid
required

UUID of the company on whose behalf the request is made. Send it on banking
requests to identify the acting company. Note that user-scoped list endpoints
(e.g. the recipients/transactions indexes) resolve results across all companies
the authenticated user is a member of, rather than strictly from this header.
See The company-id header for details on how to obtain
a value for this header and the error responses to expect when it is missing or invalid.

Responses

Language
Credentials
Bearer
LoadingLoading…
Response
Click Try It! to start a request and see the response here! Or choose an example:
application/json