Abstra Cash Connect
Cash Connect is Abstra's bank integrator: one interface over any bank. Instead of learning each bank's file layouts and APIs, your project speaks a single contract for payroll, supplier payments, boletos, taxes, utility bills, file transmission, issuing and managing collections (boletos you charge), and account inquiries. Cash Connect translates that into whatever each bank actually requires and normalizes the answers back.
The first release integrates Itaú and Banco do Brasil (BB). You choose the bank per call
with the bank parameter (itau or bb). More banks widen the same contract without changing the
calls you already wrote.
What you can do
| Family | What it is | Example actions |
|---|---|---|
| Payments (cash-out) | Send a remittance and the bank pays it | post_payments_payroll, post_payments_suppliers, post_payments_boletos, post_payments_taxes, post_payments_utilities |
| Collections (cash-in) | Issue and manage a boleto you charge | post_collections, patch_collections_by_collection, post_collections_by_collection_write_off |
| Synchronous inquiries | Ask a question answered in the same call | post_accounts_statements, post_payments_inquiries, post_collections_inquiries |
| File exchange | You build the bank file, Cash Connect only transports it | post_files_upload_authorizations, post_files_transmissions, post_files_return_inquiries |
| Remittance drafts | Assemble a large remittance item by item, then close it | nine post/get/patch/delete_remittance_drafts... actions |
| Returns | Read what became of what you sent | get_returns, get_operations_by_operation |
Prerequisites and onboarding
Cash Connect follows a "set it up, use it" rule: once the bank integration exists and the connection is bound to your project, there is no separate commercial enablement, no allow-list of projects, and no per-action grant. What must exist is:
-
A bank integration for your organization. Before any call reaches a bank, your organization completes that bank's onboarding: an e-CNPJ A3 (ICP-Brasil) certificate delivered to the bank (A1 is not accepted), OAuth2 + mTLS credentials, and the bank's homologation (which is what
sandboxruns against). BB uses a single registration; Itaú registers per service. A given integration is fixed to one mode — internal build (uses remittances, drafts, collections and inquiries) or file exchange (uses only thepost_files_*actions). If you need both, create a second connection. -
The
cash-connectconnection, bound to your project. Create it under Connections, then bind it to the project — the bind is the only per-project condition. The one setting isenvironment:Setting Meaning Sandbox Runs against each bank's own homologation environment. Use it to develop. Production Moves real money. -
No credentials in the call. You never pass bank credentials or an integration id. The platform reads your organization identity from headers it writes itself, and addresses the bank by
bank+ the service (the action you call) + your organization context. If no integration backs that combination, the call is refused withintegration_absent.
A production connection cannot be executed from the Web Editor or from Smart Chat — real money only moves from the published project. Develop against a sandbox connection.
Calling actions
Every action is called from your project code with run_connection_action, giving the connection
name, the action name, and a params dict. See the
run_connection_action reference. Actions can also be
invoked from the platform's Smart Chat.
from abstra.connectors import run_connection_action
result = run_connection_action(
"cash-connect", # the connection name you bound to the project
"post_payments_payroll", # the action name
{ ... }, # the params for that action
)
The action_name is the action's name, e.g. post_payments_payroll for POST /payments/payroll or
get_returns for GET /returns. Path parameters are passed inside the params dict by their name —
for example get_operations_by_operation takes {"operation": "op_..."}.
Every monetary value crosses this connector as a whole number of cents in a string — "350000"
is R$ 3,500.00. A fraction ("3500.00") is refused at the door.
A worked example: paying a payroll
from abstra.connectors import run_connection_action
result = run_connection_action(
"cash-connect",
"post_payments_payroll",
{
"reference": "payroll-2026-01", # your code; this is the idempotency key
"bank": "itau",
"bank_account": {"branch": "0001", "number": "12345-6"}, # the company account debited
"items": [
{
"reference": "emp-00042",
"amount": "350000", # R$ 3,500.00, in cents
"settlement_reference": {
"kind": "pix_key",
"key": "maria@example.com",
},
"beneficiary": {"name": "MARIA SILVA", "document": "12345678901"},
},
{
"reference": "emp-00043",
"amount": "500000", # R$ 5,000.00
"settlement_reference": {
"kind": "bank_account",
"bank": "341", # 3-digit FEBRABAN code of the destination bank
"branch": "0001",
"number": "98765-4",
},
"beneficiary": {"name": "JOAO SOUZA", "document": "98765432100"},
},
],
},
)
The response is a canonical order:
{
"order_id": "ord_...",
"deduplicated": false,
"operations": [
{ "operation_id": "op_...", "kind": "mass_payment", "status": "submitted", "item_count": 1, "settlement_route": "pix" },
{ "operation_id": "op_...", "kind": "mass_payment", "status": "submitted", "item_count": 1, "settlement_route": "ted" }
]
}
One remittance can fan out into several operations — one per derived settlement route
(same_institution, ted, pix). deduplicated: true means the same reference was submitted
before, so the original order is returned instead of creating a new one.
The settlement_reference
Each item names who it settles to with a settlement_reference, a shape chosen by its kind:
| kind | fields | for |
|---|---|---|
bank_account | bank (3-digit FEBRABAN), branch, number | a transfer to any bank account |
pix_key | key (1–77 chars) | a PIX transfer |
boleto_barcode | code (44 digits) | paying a boleto (the barcode, never the typed line) |
convenio_barcode | code (44 digits) | paying a utility/tax guide |
An item may carry an optional detail — boleto, transfer or convenio — but only the one that
matches its settlement_reference kind; mixing them is refused.
The remittance draft flow
For a remittance too large to build in one call, a draft and the order it becomes are the same
record at different stages. You start it, fill it item by item, then close it — and closing is what
creates the order and its operations. Idempotency is keyed by the draft's reference.
POST /remittance-drafts → start: { draft_id }
├─ POST .../{draft}/items append items (each item's `reference` is required)
├─ GET .../{draft}/items read items (paginated: after, most)
├─ PATCH .../{draft}/items overwrite / edit items
├─ DELETE .../{draft}/items remove items by id
├─ GET .../{draft} read the header (item_count, running amount, expires_at)
├─ PATCH .../{draft} correct the header (bank_account only)
└─ DELETE .../{draft} throw the draft away
POST /remittance-drafts/{draft}/closing → close: creates & submits the order → order answer
# 1. Start
draft = run_connection_action("cash-connect", "post_remittance_drafts", {
"reference": "suppliers-2026-w03",
"service": "supplier_payment", # one of: payroll, supplier_payment, boleto_payment, tax_payment, utility_payment
"bank": "itau",
"bank_account": {"branch": "0001", "number": "12345-6"},
})
draft_id = draft["draft_id"]
# 2. Append items (repeat as needed)
run_connection_action("cash-connect", "post_remittance_drafts_by_draft_items", {
"draft": draft_id,
"items": [
{
"reference": "inv-771", # required inside a draft
"amount": "128000",
"settlement_reference": {"kind": "pix_key", "key": "fornecedor@example.com"},
},
],
})
# 3. Close → the order is created and submitted
order = run_connection_action("cash-connect", "post_remittance_drafts_by_draft_closing", {
"draft": draft_id,
})
Every call reads the draft first: a draft that does not exist (or whose environment does not match
the connection) is refused with draft_absent; a draft already closed with draft_closed; closing
an empty draft with draft_empty. Appending items returns a per-item verdict — either
{reference, item_id} (accepted) or {reference, problem} where problem is
duplicate_reference, amount_not_positive or item_absent. Drafts also have an expires_at.
Action families
Payments (cash-out)
Five remittance actions, one per service; all share the remittanceRequest body
(reference, bank, bank_account, items) and answer a canonical order.
post_payments_payroll— pay a payroll (see the worked example above).post_payments_suppliers— pay suppliers.post_payments_boletos— pay boletos (cash-out), each item settling a boleto by its barcode.post_payments_taxes— collect taxes.post_payments_utilities— pay utility (consumption) bills.
post_payments_boletos pays a boleto and its returns are payment.* events. Issuing a boleto
to charge someone is post_collections, whose returns are boleto.* events. They are different
directions of money.
Collections (cash-in)
The only cash-in family — you issue a boleto for someone to pay you.
run_connection_action("cash-connect", "post_collections", {
"reference": "invoice-8842",
"bank": "itau",
"bank_account": {"branch": "0001", "number": "12345-6"}, # the account the money lands in
"amount": "150000",
"due_on": "2026-10-31",
"own_number": "INV-8842", # optional: your reconciliation reference (one is made if absent)
"reconcile": True, # optional: follow the boleto and get told when it is paid/canceled/expired
"payer": {
"name": "CLIENTE EXEMPLO LTDA",
"document": "12345678000199",
"address": {"street": "Av. Paulista 1000", "city": "Sao Paulo", "state": "SP", "postal_code": "01310100"},
},
})
Issuing is synchronous, returning {collection_id, operation_id, own_number, deduplicated, status?, digitable_line?, barcode?}. The status, digitable_line and barcode are absent until
the bank answers the registration. patch_collections_by_collection changes an issued collection
(reference, bank, and at least one of amount/due_on); post_collections_by_collection_write_off
writes it off (reference, bank). Both take the collection id as the collection path parameter.
Synchronous inquiries
Questions answered in the same call; they keep nothing (except the collection they read back).
# Account statement over a period
run_connection_action("cash-connect", "post_accounts_statements", {
"bank": "itau",
"bank_account": {"branch": "0001", "number": "12345-6"},
"period": {"from": "2026-09-01T00:00:00Z", "to": "2026-09-30T23:59:59Z"},
})
# → { operation_id, entries: [{ on, amount, direction: credit|debit, description }] }
post_payments_inquiries— what became of payments already sent ({bank, bank_account, period?}→ per-itemoutcome: settled|scheduled|rejected|undecided).post_collections_inquiries— what became of one collection ({bank, bank_account, collection_id | own_number}— exactly one — → the collection read back).
Some banks answer these later in a file rather than in the call; when that happens the request is
refused with not_in_the_call, and you read the answer through the returns instead.
File exchange
For the file-exchange mode, you build the bank file and Cash Connect only transports it — the bytes never travel through this connector's action surface.
# 1. Ask where to put the file (15-minute window)
auth = run_connection_action("cash-connect", "post_files_upload_authorizations", {"bank": "itau"})
# auth → { file_id: "art_...", url: "<presigned PUT>", expires_at }
# ... your project PUTs the file bytes straight to auth["url"] and computes its SHA-256 ...
# 2. Transmit the uploaded file
run_connection_action("cash-connect", "post_files_transmissions", {
"reference": "rem-2026-01-15",
"bank": "itau",
"file": {"file_id": auth["file_id"], "fingerprint": "<64 hex sha-256>"},
})
# 3. Ask for what the bank returned for a transmitted file
run_connection_action("cash-connect", "post_files_return_inquiries", {
"reference": "rem-2026-01-15",
"bank": "itau",
"bank_account": {"branch": "0001", "number": "12345-6"},
})
The server checks the fingerprint against the uploaded bytes: a mismatch is refused with
fingerprint_mismatch, a missing upload with file_absent.
Reading returns
Whatever a bank does later is read through two actions. get_returns is the thin feed — it only
says what changed; the rich data is read from get_operations_by_operation.
# The thin feed of notices for a service
returns = run_connection_action("cash-connect", "get_returns", {
"service": "payroll", # required
"bank": "itau", # required
# optional filters: event, operation, since, after (cursor), most (1-1000, default 100)
})
# → { returns: [{ return_id, operation_id, operation_kind, event, occurred_at }], next_cursor? }
# The rich read of one operation and every item's outcome
op = run_connection_action("cash-connect", "get_operations_by_operation", {
"operation": returns["returns"][0]["operation_id"],
})
# → { operation_id, order_id, reference?, service, bank, kind, status,
# items: [{ item_id, amount, status: pending|scheduled|settled|rejected, bank_item_id, settled_at?, settled_amount?, rejection? }],
# collection? }
payload fieldA return never carries the data inline. The pattern is always: read get_returns to learn what
changed, then read get_operations_by_operation (and, for a collection, the collection it holds)
for the actual figures. Wildcard events (ending in .all) are only for subscribing — they are not
searchable in get_returns.
Receiving return events
To be told when something changes instead of polling, subscribe with a hook. In your project's
abstra.json, declare a hook whose path is <connection-name>/<event>, with each segment in
kebab-case — so the payment.settled event of a connection named cash-connect is the hook path
cash-connect/payment-settled. Deploying the project registers the subscription (idempotently). See
Receiving Connector Events.
The events, grouped by operation kind:
| Family (kind) | Events | Wildcard |
|---|---|---|
payment.* (mass_payment) | payment.waiting_release, payment.waiting_payment, payment.updated, payment.settled, payment.rejected, payment.canceled, payment.failed | payment.all |
file.* (file_transmission) | file.settled, file.rejected, file.failed | file.all |
boleto.* (collection_issuance) | boleto.paid, boleto.canceled, boleto.rejected, boleto.expired | boleto.all |
Fourteen concrete events plus three wildcards. Some notes:
payment.updatedis item-scoped and incremental: one wave moved one or more items but the batch has not finished — go read the operation.payment.settledfires once every item has finished;payment.rejectedis the whole batch refused;payment.failedis a transport failure after retries are exhausted.file.settledmeans there is a return to fetch viapost_files_return_inquiries.boleto.*are cash-in (a boleto you issued), not thepayment.*events of paying a boleto.- Subscribing to both a specific event and its wildcard delivers once, on the specific event.
- Synchronous inquiries (statement, payment/collection inquiry) do not emit events.
What the hook receives is a thin, signed envelope — a reference to what changed, never the data:
{
"operation_id": "op_...",
"operation_kind": "mass_payment",
"event": "payment.settled",
"occurred_at": "2026-01-15T12:34:56.000Z"
}
The signature travels in the X-CC-Signature / X-CC-Key-Id headers over the raw body, and the
platform verifies it automatically before your hook runs. Then follow the same pattern: read
get_returns and get_operations_by_operation for the figures.
Errors
A refused call comes back in a single shape:
{ "error": "draft_closed", "message": "...", "fields": ["..."] }
fields is present only when the refusal points at specific inputs. Common error codes:
| Code | Meaning |
|---|---|
integration_absent | No bank integration backs this bank + service for your organization |
mode_mismatch | The connection's environment does not match what is being addressed |
order_empty | A remittance with no items |
draft_absent / draft_closed / draft_empty | The draft does not exist / is already closed / is empty at closing |
collection_absent / own_number_taken | Unknown collection / that own_number is already used |
file_absent / fingerprint_mismatch | The upload is missing / its bytes do not match the fingerprint |
operation_absent | Unknown operation id |
not_in_the_call | The bank answers this inquiry later in a file, not synchronously |
inquiry_not_asked / inquiry_unanswered | Nothing was asked / the answer has not arrived yet |
unavailable | The bank could not be reached |
Reference
For the full, always-current list of actions and their parameters, see the Cash Connect connector reference.