Skip to main content

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.

Phase 1 banks

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​

FamilyWhat it isExample actions
Payments (cash-out)Send a remittance and the bank pays itpost_payments_payroll, post_payments_suppliers, post_payments_boletos, post_payments_taxes, post_payments_utilities
Collections (cash-in)Issue and manage a boleto you chargepost_collections, patch_collections_by_collection, post_collections_by_collection_write_off
Synchronous inquiriesAsk a question answered in the same callpost_accounts_statements, post_payments_inquiries, post_collections_inquiries
File exchangeYou build the bank file, Cash Connect only transports itpost_files_upload_authorizations, post_files_transmissions, post_files_return_inquiries
Remittance draftsAssemble a large remittance item by item, then close itnine post/get/patch/delete_remittance_drafts... actions
ReturnsRead what became of what you sentget_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:

  1. 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 sandbox runs 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 the post_files_* actions). If you need both, create a second connection.

  2. The cash-connect connection, 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 is environment:

    SettingMeaning
    SandboxRuns against each bank's own homologation environment. Use it to develop.
    ProductionMoves real money.
  3. 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 with integration_absent.

Production is locked out of the editor

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_..."}.

Amounts are integer cents, as strings

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:

kindfieldsfor
bank_accountbank (3-digit FEBRABAN), branch, numbera transfer to any bank account
pix_keykey (1–77 chars)a PIX transfer
boleto_barcodecode (44 digits)paying a boleto (the barcode, never the typed line)
convenio_barcodecode (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.
Paying a boleto is not issuing one

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-item outcome: 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? }
There is no payload field

A 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)EventsWildcard
payment.* (mass_payment)payment.waiting_release, payment.waiting_payment, payment.updated, payment.settled, payment.rejected, payment.canceled, payment.failedpayment.all
file.* (file_transmission)file.settled, file.rejected, file.failedfile.all
boleto.* (collection_issuance)boleto.paid, boleto.canceled, boleto.rejected, boleto.expiredboleto.all

Fourteen concrete events plus three wildcards. Some notes:

  • payment.updated is item-scoped and incremental: one wave moved one or more items but the batch has not finished — go read the operation.
  • payment.settled fires once every item has finished; payment.rejected is the whole batch refused; payment.failed is a transport failure after retries are exhausted.
  • file.settled means there is a return to fetch via post_files_return_inquiries.
  • boleto.* are cash-in (a boleto you issued), not the payment.* 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:

CodeMeaning
integration_absentNo bank integration backs this bank + service for your organization
mode_mismatchThe connection's environment does not match what is being addressed
order_emptyA remittance with no items
draft_absent / draft_closed / draft_emptyThe draft does not exist / is already closed / is empty at closing
collection_absent / own_number_takenUnknown collection / that own_number is already used
file_absent / fingerprint_mismatchThe upload is missing / its bytes do not match the fingerprint
operation_absentUnknown operation id
not_in_the_callThe bank answers this inquiry later in a file, not synchronously
inquiry_not_asked / inquiry_unansweredNothing was asked / the answer has not arrived yet
unavailableThe bank could not be reached

Reference​

For the full, always-current list of actions and their parameters, see the Cash Connect connector reference.