International Transfers (ACAM)
Introduction
This page explains what Avenia needs from you so that it can report your international transfers to the Brazilian Central Bank (BCB), and what happens when that information is missing. If you are new to the topic, read it top to bottom once. It goes in this order:
- The concepts: what is reported, and which operations are international.
- Wallet qualification: who is on the other side of an on-chain transfer.
- Purpose codes: why the money moves, and the two tables (bands) they come from.
- The flows: payouts, deposits with a ticket and deposits without a ticket, which can be held.
- A checklist and common questions.
Everything on this page is optional through November 14, 2026. Until the end of that day nothing is refused or held because a qualification or a purpose code is missing. Only invalid values are refused, as they are today.
On November 15, 2026 Avenia turns these rules on, and they stay on:
| Through November 14, 2026 | From November 15, 2026 | |
|---|---|---|
| A qualification you send with missing fields: creating or updating a beneficiary wallet, updating a deposit sender wallet, or a raw payout address | Accepted: the wallet is PENDING | Refused with 400 Bad Request, naming the first missing field |
An on-chain payout to a destination that is not QUALIFIED | Created | Refused with 400 Bad Request, in Brazil too |
| A ticket without the purpose code of an international leg | Created | Refused with 400 Bad Request |
| An on-chain deposit with no ticket, from a sender not known to be in Brazil | Credited, then asks for its purpose code | Held, not credited, until you identify its sender |
PIX, TED, boleto and other BRL operations, INTERNAL transfers between Avenia accounts and refunds are not affected, before or after that date.
Integrate now, while everything is still optional, and use the time to qualify the wallets you already have (see the checklist).
The Concepts
Why this exists
Brazilian regulation requires Avenia to report to the BCB every international transfer made by its clients. This report is called ACAM. Your Avenia balance is held in Brazil by Avenia SPSAV, Avenia's regulated Brazilian entity. So when money or crypto leaves Brazil, or arrives from abroad, the BCB treats it much like a foreign-exchange operation.
Each report needs two pieces of information that only you have:
| What | The question it answers | Where you send it |
|---|---|---|
| Wallet qualification | Who is on the other side of an on-chain transfer: who owns the wallet, who holds its keys, and in which country. | On the wallet: a beneficiary wallet, a deposit sender wallet, or a raw payout address. |
| Purpose code | Why the money is moving, as a code from the BCB's fixed list. For example, 32999 is international travel. | On the quote, one for each international leg: inputPurposeCode and outputPurposeCode. |
This applies to every Avenia account, main account or sub-account.
Legs: input and output
Every operation, and so every quote and ticket, has two legs:
- the input leg is how the value enters: the deposit (
inputCurrencyandinputPaymentMethodon the quote); - the output leg is how it leaves: the payout (
outputCurrencyandoutputPaymentMethod).
Each leg is a transfer of its own, and each can cross the border or not. That is why a quote has two purpose codes: inputPurposeCode is the reason for the deposit and outputPurposeCode the reason for the payout. A leg that is not international takes no code.
Is my operation international?
Look at each leg on its own:
| The leg is | Domestic or international? | Purpose code for that leg |
|---|---|---|
BRL rails: PIX, TED, BOLETO, BANK-TRANSFER in BRL | Domestic | No |
INTERNAL (between Avenia accounts) | Domestic | No |
REFUND (returns an earlier transfer) | Domestic | No |
Fiat in any currency other than BRL: USD, EUR, ARS, COP, MXN, a SWIFT currency | Always international | Yes. No wallet is involved, so no qualification either. |
A public chain (POLYGON, ETHEREUM, TRON, BASE, …), on a payout | Depends on the destination wallet's country | Only when the wallet is not in Brazil |
| A public chain, on a deposit with a ticket | Always international | Yes: the sender is unknown when you quote |
| A public chain, on a deposit without a ticket | Depends on the sender wallet's country | Only when the sender is not in Brazil (see holds) |
For an on-chain leg, the counterparty wallet's counterpartyCountry decides:
BR: the leg is domestic and takes no purpose code.- Any other country, or not stated yet: the leg is international and takes a purpose code. A wallet whose country you never stated is treated as abroad.
A deposit with a ticket is the exception: you quote it before anyone knows which wallet will send the crypto, so its quote always takes inputPurposeCode.
Some examples:
| Operation | International? | Why |
|---|---|---|
| PIX in BRL → BRLA to your Avenia balance | No | Both legs are domestic. |
BRLA → USDC on Polygon to a wallet with counterpartyCountry BR | No | The destination is in Brazil. |
BRLA → USDC on Polygon to a wallet with counterpartyCountry US | Yes, the payout | The destination is abroad. |
| BRLA → USDC on Polygon to a wallet whose country you never stated | Yes, the payout | Treated as abroad until you state BR. |
| BRL by PIX → USD by wire | Yes, the payout | The payout is fiat in a currency other than BRL. |
| EUR by SEPA → BRLA | Yes, the deposit | The deposit is fiat in a currency other than BRL. |
| USDC deposited on Polygon through a ticket, from any wallet | Yes, the deposit | The sender is unknown when you quote. |
USDC sent straight to your wallet address by a wallet with counterpartyCountry AR | Yes, the deposit | The sender is abroad. |
| USD by ACH → USDC on Polygon to a wallet abroad | Yes, both legs | A deposit and a payout, each international: two codes. |
What you send, flow by flow
| Flow | What you send | From November 15, 2026, when it is missing |
|---|---|---|
BRL operations (PIX, TED, boleto), INTERNAL transfers, refunds | Nothing | Nothing changes |
| On-chain payout, to a beneficiary wallet or a raw address | A QUALIFIED destination, and outputPurposeCode on the quote when the destination is not in Brazil | The ticket is refused |
| Payout in a currency other than BRL (wire, SEPA, SWIFT, …) | outputPurposeCode on the quote | The ticket is refused |
Deposit with a ticket, on-chain (TRANSFER, PERMIT) or in a currency other than BRL | inputPurposeCode on the quote | The ticket is refused |
| Deposit in a currency other than BRL that a third party sends to your virtual account | inputPurposeCode after it is credited, when Avenia asks with TICKET-PURPOSE-CODE-REQUIRED | It is credited, and asks for its code |
| On-chain deposit without a ticket, sent straight to your wallet address | The sender wallet's qualification, plus the deposit's purpose code when the sender is abroad | It is held, not credited, until you answer |
Wallet Qualification
A qualification describes the counterparty of an on-chain transfer: the person or company that owns the wallet on the other side, in which country it is, and who holds the wallet's keys. Avenia reports these facts to the BCB, and the country also decides whether the transfer is international at all.
You send the same set of fields for every kind of wallet:
| Wallet | What it is | Where you qualify it |
|---|---|---|
| Beneficiary wallet | A wallet you register to pay out to. | On creation, or later with PATCH. See the Wallet's Guide. |
| Deposit sender wallet | A wallet that sent crypto to your account. Avenia records it when the deposit arrives. | With PATCH, once a deposit from it is recorded. See Deposit Sender Wallets. |
| Raw payout address | A walletAddress you pay out to without registering it. | In ticketBlockchainOutput, next to walletAddress, when you create the ticket. See Payouts. |
Fields
| Field | Type | The question it answers |
|---|---|---|
isSelfCustody | bool | Does the counterparty hold the wallet's private keys itself (true), or does an institution, such as an exchange, hold them for it (false)? |
custodianName | string | Which institution holds the keys? Only when isSelfCustody is false; not accepted when it is true. Up to 100 characters. |
counterpartyName | string | Who owns the wallet? For a beneficiary wallet or payout address, whoever receives the funds; for a deposit sender wallet, whoever sent them. Up to 100 characters. |
counterpartyCountry | string | Where is the counterparty? An individual's country of residence or a company's country of incorporation, not the custodian's country. ISO 3166-1 alpha-2 code (see GET /v2/countries). BR makes the transfer domestic. |
isSameEconomicGroup | bool | Only when counterpartyCountry is not BR: is the counterparty a company of your own economic group? An economic group is a set of companies under common control, such as a parent company, its subsidiaries and its sister companies. An individual account belongs to no economic group, so for it this is always false: omit it. |
Rules
A wallet is QUALIFIED only when its qualification is complete and valid. Complete means it has every field that applies to it:
isSelfCustody | counterpartyCountry | Also needed |
|---|---|---|
true | BR | counterpartyName |
true | Any other country | counterpartyName, and isSameEconomicGroup for a company account |
false | BR | counterpartyName, custodianName |
false | Any other country | counterpartyName, custodianName, and isSameEconomicGroup for a company account |
What happens when a field is missing depends on the date:
- Through November 14, 2026, a missing field does not fail the request. The wallet is
PENDINGand keeps the valid fields you sent, and with no qualification field at all it isPENDINGwith empty fields. You can complete it later withPATCH. - From November 15, 2026, every qualification you send must be complete: when you create a beneficiary wallet, when you
PATCHa beneficiary or deposit sender wallet, and next to a rawwalletAddressin a ticket. An incomplete one returns400 Bad Requestnaming the first field it lacks, and nothing is stored. Wallets leftPENDINGbefore that date stayPENDINGuntil you qualify them, but cannot receive on-chain payouts.
A field sent with an invalid value always returns 400 Bad Request naming it, and nothing is stored:
counterpartyCountrythat is not an ISO 3166-1 alpha-2 code;counterpartyNameorcustodianNamelonger than 100 characters, or with control characters;custodianNameon a self-custody wallet (isSelfCustodyistrue);isSameEconomicGroupon a wallet in Brazil (counterpartyCountryisBR);isSameEconomicGroupset totrueon an individual account.
Also keep in mind:
- An empty or blank name counts as not sent.
- Nothing is assumed: no field gets a default value, the country included. The only exception is a wallet Avenia itself holds for another account, which is qualified automatically.
- A
PATCHreplaces the whole qualification: send every field each time, since a field you leave out is cleared.
Qualification Status and ACAM Group
qualificationStatus | Meaning |
|---|---|
QUALIFIED | The qualification is complete and valid. |
PENDING | No qualification, or an incomplete one. Complete it with PATCH. |
Wallet responses also carry acamGroup, the ACAM group Avenia reports the wallet under, computed from the qualification: I for a wallet qualified abroad, III for a self-custody wallet qualified in Brazil, and null otherwise (a PENDING wallet included). You never send it.
Examples
A self-custody wallet of a person in Brazil:
{
"isSelfCustody": true,
"counterpartyName": "John Doe",
"counterpartyCountry": "BR"
}
A wallet held by an exchange for a company abroad, in your economic group:
{
"isSelfCustody": false,
"custodianName": "Example Exchange Ltd",
"counterpartyName": "Example Holdings LLC",
"counterpartyCountry": "US",
"isSameEconomicGroup": true
}
Deposit Sender Wallets
Every wallet that sends a crypto deposit to your account is recorded as a deposit sender wallet. Its qualification tells Avenia whether that deposit, and every later one from the same wallet, is an international transfer:
- a sender in Brazil (
counterpartyCountryBR) makes its deposits domestic: they take no purpose code and are never held; - a sender abroad, or one whose country is not stated yet, makes them international.
A new sender wallet starts PENDING, and each deposit from a PENDING sender sends you a WALLET-QUALIFICATION-REQUIRED webhook asking you to qualify it. The exception is a wallet Avenia holds for another account (another Avenia client, or another account of yours): it is filled in automatically as custodied by Avenia SPSAV in Brazil, so its deposits are domestic. Deposits between wallets of your own account are not deposits at all, and are not recorded.
Qualify a sender wallet with the fields and rules above. See Deposit Sender Wallets to list them, qualify them and list their deposits.
Purpose Codes
A purpose code tells the BCB why the money moves: paying for goods, a service, an investment, a transfer between your own accounts, and so on. The BCB keeps the list of codes, in two tables. Avenia publishes both, with descriptions in Portuguese, English and Spanish, so that your users can pick the right code.
Purpose Code Catalog
This public endpoint needs no authentication:
{
"purposeCodes": [
{
"code": "12029",
"group": {
"pt": "Comércio exterior – Demais",
"en": "Foreign trade – Other",
"es": "Comercio exterior – Demás"
},
"description": {
"pt": "Operações back to back",
"en": "Back-to-back operations",
"es": "Operaciones back to back"
},
"bands": ["ABOVE_50K"]
}
]
}
bands lists the tables a code belongs to. A few codes, like 32999, are in both.
GET /v2/countries, also public, lists the accepted counterpartyCountry values: {"countries": [{"code": "BR", "name": "Brazil"}, ...]}.
Bands: which table a code comes from
The BCB has one table of codes for operations worth up to USD 50,000 and another for operations worth more. Avenia calls them bands:
| Band | The operation is worth | Codes accepted |
|---|---|---|
UP_TO_50K | USD 50,000 or less | Those whose bands include UP_TO_50K |
ABOVE_50K | More than USD 50,000 | Those whose bands include ABOVE_50K |
How the band is set:
- One band per operation. The operation's value picks a single band, and both codes of a quote (
inputPurposeCodeandoutputPurposeCode) must come from its table. - The value is in USD (
usdAmount): the input amount, or the output amount when only the output is in USD (for example BRL by PIX → USD by wire). USD and USD stablecoins (USDC,USDT) count at face value. Any other currency, BRL, BRLA, EUR or EURC for example, is converted to USD. - The quote shows it. Every quote with an international leg returns
purposeCodeBandandusdAmount, and the ticket keeps them.
To pick a code from the right table:
- Quote without the codes, and read
purposeCodeBand. - Show your user the codes of
GET /v2/purpose-codeswhosebandsinclude that band. - Quote again with the code (or codes) chosen, and create the ticket with that quote.
For example, a quote of 10,000 USDC is worth USD 10,000 and is in UP_TO_50K, and a quote of 300,000 BRL by PIX → USD by wire is in ABOVE_50K if its USD output is above 50,000. A code that is not in the band's table returns 400 Bad Request with the band and the operation's USD value: quote again with a code from that table.
Purpose Code on the Quote
State one purpose code for each international leg of the fixed-rate quote:
inputPurposeCode, the reason for the deposit (input leg), when that leg is international: fiat in a currency other than BRL, or crypto on a public chain (notINTERNAL).outputPurposeCode, the reason for the payout (output leg), when that leg is international: fiat in a currency other than BRL, or crypto on a public chain to a destination not in Brazil.
A single ticket can be two international transfers. A USD deposit paid out in USDC to a wallet abroad is money entering Brazil and money leaving it, reported separately, so it takes both codes, and they can differ.
| Quote | inputPurposeCode | outputPurposeCode |
|---|---|---|
USDC on Polygon → BRLA INTERNAL | Yes | — |
USD by ACH → USDC INTERNAL | Yes | — |
BRLA INTERNAL → USDC on Polygon, wallet abroad | — | Yes |
| BRL by PIX → USD by wire | — | Yes |
| USD by ACH → USDC on Polygon, wallet abroad | Yes | Yes |
| USD by ACH → USDC on Polygon, wallet in Brazil | Yes | — (dropped by the ticket) |
| EURC on Base → USD by wire | Yes | Yes |
BRL by PIX → BRLA INTERNAL, or BRLA INTERNAL → BRL by PIX | — | — |
The quote cannot know the destination wallet of an on-chain payout: that comes with the ticket. So the quote accepts outputPurposeCode on any on-chain payout, and the ticket drops it when the destination turns out to be in Brazil (see Payouts).
The quote returns 400 Bad Request naming the field when a code:
- is sent for a leg that cannot be international (BRL,
INTERNAL); - is not a known code;
- is not in the table of the quote's band (the error states the band and the operation's USD value).
A missing code is never an error on the quote. From November 15, 2026 it is checked when you create the ticket: a ticket whose international leg has no code is refused with 400 Bad Request naming inputPurposeCode or outputPurposeCode.
curl -X GET "https://api.sandbox.avenia.io:10952/v2/account/quote/fixed-rate" \
-H "Authorization: Bearer eyJhdXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
-G \
--data-urlencode "inputCurrency=USD" \
--data-urlencode "inputPaymentMethod=ACH" \
--data-urlencode "inputAmount=10000" \
--data-urlencode "outputCurrency=USDC" \
--data-urlencode "outputPaymentMethod=POLYGON" \
--data-urlencode "inputPurposeCode=YOUR_DEPOSIT_PURPOSE_CODE" \
--data-urlencode "outputPurposeCode=YOUR_PAYOUT_PURPOSE_CODE"
{
"quoteToken": "eyJhdXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"inputCurrency": "USD",
"inputPaymentMethod": "ACH",
"inputAmount": "10000",
"outputCurrency": "USDC",
"outputPaymentMethod": "POLYGON",
"outputAmount": "9985.21",
"inputPurposeCode": "YOUR_DEPOSIT_PURPOSE_CODE",
"outputPurposeCode": "YOUR_PAYOUT_PURPOSE_CODE",
"purposeCodeBand": "UP_TO_50K",
"usdAmount": "10000"
}
A quote with an international leg carries these fields, and the ticket's quote keeps them:
| Field | Description |
|---|---|
inputPurposeCode | The code you stated for the deposit leg. |
outputPurposeCode | The code you stated for the payout leg. |
purposeCodeBand | The table both codes come from: UP_TO_50K or ABOVE_50K. |
usdAmount | The operation's value in USD, which picks the band. |
Payouts
An on-chain payout goes to a destination: a beneficiary wallet (beneficiaryWalletId), which carries its own qualification, or a raw walletAddress, qualified in ticketBlockchainOutput when you create the ticket. When you create the ticket, Avenia checks the destination in two steps:
- Is the destination
QUALIFIED? From November 15, 2026, an on-chain payout only goes to aQUALIFIEDdestination, in Brazil too: Avenia has to report who received it. A payout to aPENDINGbeneficiary wallet, or to a raw address without its complete qualification, is refused with400 Bad Request. - Is it in Brazil? The destination's
counterpartyCountrydecides whether the payout is international:BR: the payout is domestic. The ticket drops anyoutputPurposeCodethe quote carries. The deposit leg keeps its owninputPurposeCodewhen it is international (for example USD → USDC).- Any other country: the payout is international and needs
outputPurposeCodeon the quote. From November 15, 2026, a ticket without it is refused with400 Bad Request.
The chart shows the rules from November 15, 2026. Through November 14, 2026 neither 400 is returned: a PENDING destination receives the payout, and one whose country is not BR, or not stated, is treated as abroad, where outputPurposeCode is still optional.
| Destination | Through November 14, 2026 | From November 15, 2026 |
|---|---|---|
QUALIFIED in Brazil (BR) | Domestic: no outputPurposeCode (dropped if sent) | Same |
QUALIFIED abroad | outputPurposeCode optional | outputPurposeCode required |
PENDING: a beneficiary wallet not yet qualified, or a raw address with missing fields | Paid, treated as abroad unless its counterpartyCountry is BR; outputPurposeCode optional | Refused |
A payout in fiat other than BRL (a wire, SEPA, SWIFT…) has no wallet: it always takes outputPurposeCode, required from November 15, 2026. An INTERNAL payout, to an Avenia account, is never international and takes neither a qualification nor a code.
Paying out to a raw address
Send the qualification next to walletAddress:
{
"quoteToken": "eyJadXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"ticketBlockchainOutput": {
"walletChain": "POLYGON",
"walletAddress": "0xXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"isSelfCustody": false,
"custodianName": "Example Exchange Ltd",
"counterpartyName": "Example Holdings LLC",
"counterpartyCountry": "US",
"isSameEconomicGroup": false
}
}
The address follows the same rules as a beneficiary wallet: through November 14, 2026 missing fields leave it PENDING; from November 15, 2026 they are refused. A missing or invalid field returns 400 Bad Request naming it within ticketBlockchainOutput, such as ticketBlockchainOutput.counterpartyName. The qualification fields are only accepted with a raw walletAddress on a public chain: not with beneficiaryWalletId (qualify the wallet itself instead), and not on an INTERNAL payout.
The ticket's blockchainReceiverInfo shows the destination's qualification and qualificationStatus as they stood when the ticket was created.
Deposits
This section covers deposits that have a ticket. For on-chain deposits sent straight to your wallet address, see Crypto Deposit Holds.
Stating the code on the quote
When you create a ticket for a deposit on a public chain (TRANSFER or PERMIT) or in a currency other than BRL, state inputPurposeCode on its quote. An on-chain deposit takes it whoever ends up sending the crypto, since the sender is unknown when you quote. From November 15, 2026 a ticket without it is refused with 400 Bad Request.
A deposit with a ticket is never held: its ticket already carries the code.
When Avenia asks for the code afterwards
A deposit can still be credited without a purpose code:
- a deposit in USD, EUR, ARS, COP or MXN that a third party sends to your virtual account: Avenia creates its ticket by itself, with no code;
- through November 14, 2026, a ticket you created without the code;
- through November 14, 2026, an on-chain deposit sent straight to your wallet address (from November 15, 2026 such a deposit is held instead).
Once such a deposit is credited, you receive a TICKET-PURPOSE-CODE-REQUIRED webhook on the TICKET subscription:
- in a currency other than BRL: always, whoever sent it;
- on-chain: unless its sender wallet is in Brazil (
counterpartyCountryBR) or is one of your own wallets.
The deposit is already credited and keeps going: the webhook asks for the code, it does not pause the ticket. It is sent once, not repeated, so keep its ticket.id, or list the deposits still waiting (see below). The ticket carries quote.purposeCodeBand and quote.usdAmount: state a code from that band's table, as below, and it becomes the ticket's quote.inputPurposeCode. A payout's code is only ever stated on the quote.
State the Purpose Code of a Deposit
| Parameter | Type | Description |
|---|---|---|
ticket-id | string | The ticket of the deposit. |
subAccountId | string | Optional. The sub-account the ticket belongs to. |
curl -X PATCH "https://api.sandbox.avenia.io:10952/v2/account/tickets/TICKET-UUID-HERE/purpose-code" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer eyJhdXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
-d '{
"inputPurposeCode": "YOUR_PURPOSE_CODE"
}'
On success it returns 200 OK with the updated ticket, as {"ticket": {...}}. The code is stated once: repeating the same code returns the ticket again, so a retry is safe. Otherwise:
| Status | When |
|---|---|
400 Bad Request | The code is missing or not in the table of the ticket's band; the ticket is not an international deposit (on-chain or in a currency other than BRL); or the on-chain deposit is domestic: it came from one of your own wallets or from a sender wallet in Brazil. |
404 Not Found | There is no such ticket. |
409 Conflict | The ticket already has a different deposit purpose code, or its deposit was not credited yet. |
List the deposits missing a code
To show a warning or a counter without paging through your whole history, list only the deposits still waiting for their code:
curl -X GET "https://api.sandbox.avenia.io:10952/v2/account/tickets/?missingPurposeCode=true&subAccountId=*" \
-H "Authorization: Bearer eyJhdXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
It returns the credited international deposits (PROCESSING, ON-HOLD, PAID or PARTIAL-FAILED) that asked for a code and have none yet: exactly the tickets the PATCH above accepts. A ticket leaves the list once you state its code, or, for an on-chain deposit, once you qualify its sender wallet in Brazil (counterpartyCountry BR). Without subAccountId it lists the main account; with subAccountId=*, every account of the workspace. The response and paging are those of GET /v2/account/tickets/.
Crypto Deposit Holds
Through November 14, 2026 no deposit is held: deposits are credited as usual and the list of holds is empty. From November 15, 2026, the deposits described below are held, and are not credited to your balance until they are released. Qualify your deposit sender wallets before that date: a deposit from a sender in Brazil (counterpartyCountry BR) is never held.
Why a deposit is held
When you create a ticket, you state the purpose code before the money moves. A deposit sent straight to your account's wallet address, with no ticket, skips that step: Avenia only learns of it when the crypto arrives. If its sender is abroad, it is an international transfer, and Avenia could not report it without its purpose code and its sender's qualification.
So, from November 15, 2026, Avenia holds such a deposit before crediting it, and asks you who sent it and why. Once it knows, the hold is released and the deposit is credited as usual.
Which deposits are held
An on-chain deposit is held when all of these are true:
- it was sent straight to your account's wallet address, without a ticket;
- it comes from a wallet that is not one of your own;
- its sender wallet is not known to be in Brazil: its
counterpartyCountryis not stated yet, or is another country.
A sender QUALIFIED abroad is held too: Avenia knows who sent the deposit, but not why. Never held:
- a deposit with a ticket (on-chain, PIX, or in any currency): its ticket carries its code;
- a deposit in a currency other than BRL to your virtual account: it is credited, and asks for its code;
- a deposit from a sender in Brazil (
counterpartyCountryBR), such as any wallet Avenia holds for another account; - a deposit from one of your own wallets, and an
INTERNALtransfer from another Avenia account.
What releases a hold
An international transfer needs both answers, who and why. A domestic one needs neither. So a hold is released in one of two ways:
| What you do | The hold is | releaseReason |
|---|---|---|
Qualify its sender wallet in Brazil (counterpartyCountry BR) | Released at once, without a code. A code stated before is not used: a domestic transfer takes none. | DOMESTIC_SENDER |
State its purpose code, and qualify its sender wallet abroad (QUALIFIED), in either order | Released as soon as both are done, with that code | PURPOSE_CODE |
- The purpose code belongs to one deposit: state it for each hold, with
PATCH /v2/account/deposit-holds/{hold-id}/purpose-code. - The qualification belongs to the sender wallet: qualifying it once, with
PATCH /v2/account/deposit-sender-wallets/{wallet-id}, settles every hold from that wallet. In Brazil, all of them are released. Abroad, those whose code is already stated are released, and later deposits from it only wait for their code.
Both requests release a hold at once when it is ready. A hold does not expire: it waits until you answer.
A held deposit, step by step
- The deposit arrives. Avenia records its sender wallet and holds the deposit with status
AWAITING_PURPOSE_CODE. You receive:DEPOSIT-PURPOSE-CODE-REQUIREDon theDEPOSIT-HOLDsubscription, repeated every 24 hours until you state the code;WALLET-QUALIFICATION-REQUIREDon theWALLET-QUALIFICATIONsubscription, when the sender wallet isPENDING. Itsdata.depositSenderWallet.idis the{wallet-id}to qualify. You can also listGET /v2/account/deposit-sender-wallets/and match itswalletAddressto the hold'ssenderWalletAddress.
- You answer, depending on who sent it:
- a sender in Brazil: qualify the sender wallet with
counterpartyCountryBR, and the hold is released; - a sender abroad: state the deposit's purpose code, and qualify the sender wallet with its country. Once the code is stated, the hold is
AWAITING_WALLET_QUALIFICATIONuntil the wallet isQUALIFIED. If it already is, the hold is released at once.
- a sender in Brazil: qualify the sender wallet with
- The hold is released (
RELEASED). The deposit is credited as usual, with its code when it has one. The hold'sticketIdlinks the new ticket, which sends the usual ticket webhooks, starting withTICKET-CREATED.
The band of a held deposit
Until it is credited, a held deposit's USD value follows the price of the crypto. While the hold awaits its code, usdAmount and purposeCodeBand are its value and band at the moment you read them. When you state the code, it is checked against the band at that moment, and the hold keeps that band and value. The ticket created at the release uses them too, even if the price moves in between. For USD stablecoins (USDC, USDT) the value is simply the amount.
Get all Deposit Holds
| Parameter | Type | Description |
|---|---|---|
subAccountId | string | The sub-account ID, if applicable. |
status | string | Filter by AWAITING_PURPOSE_CODE, AWAITING_WALLET_QUALIFICATION or RELEASED. |
cursor | string | Cursor for pagination, from the previous response. |
curl -X GET "https://api.sandbox.avenia.io:10952/v2/account/deposit-holds?status=AWAITING_PURPOSE_CODE" \
-H "Authorization: Bearer eyJhdXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
{
"holds": [
{
"id": "5b0e3c1a-8f2d-4e6b-9a7c-1d2e3f4a5b6c",
"token": "USDT",
"chain": "TRON",
"amount": "62000",
"txHash": "9f8e7d6c5b4a39281706f5e4d3c2b1a09f8e7d6c5b4a39281706f5e4d3c2b1a0",
"senderWalletAddress": "TXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"purposeCodeBand": "ABOVE_50K",
"usdAmount": "62000",
"status": "AWAITING_PURPOSE_CODE",
"createdAt": "2026-11-16T12:32:12.208933Z"
}
],
"cursor": "MTc5NDgzMjMzMjIwODkzMy01YjBlM2MxYS04ZjJkLTRlNmItOWE3Yy0xZDJlM2Y0YTViNmM="
}
Holds are listed newest first. When the response cursor is non-empty, pass it back to fetch the next page.
| Field | Description |
|---|---|
token, chain, amount | The crypto deposited, where it arrived and how much. |
txHash | The deposit's transaction hash. |
senderWalletAddress | The sender wallet. Qualifying it in Brazil (counterpartyCountry BR) releases the hold. |
status | AWAITING_PURPOSE_CODE, AWAITING_WALLET_QUALIFICATION (its code is stated; its sender wallet is not QUALIFIED yet) or RELEASED. |
purposeCodeBand | While the hold awaits its code: the table its code is checked against, from its value now. Once the code is stated: the table it was checked against. Once released without a code: the band of its value at release. |
usdAmount | While the hold awaits its code: its value in USD now. Once the code is stated: the value it was checked against. Once released without a code: its value at release. |
purposeCode | The code stated. Not kept when the hold is released for a sender in Brazil. |
releaseReason | PURPOSE_CODE or DOMESTIC_SENDER, once released. |
ticketId | The deposit's ticket, once released. |
releasedAt | When it was released. |
State the Purpose Code of a Held Deposit
| Parameter | Type | Description |
|---|---|---|
hold-id | string | The held deposit. |
subAccountId | string | Optional. The sub-account the hold belongs to. |
curl -X PATCH "https://api.sandbox.avenia.io:10952/v2/account/deposit-holds/5b0e3c1a-8f2d-4e6b-9a7c-1d2e3f4a5b6c/purpose-code" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer eyJhdXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
-d '{
"purposeCode": "YOUR_PURPOSE_CODE"
}'
On success it returns 200 OK with the hold, as {"hold": {...}}, with the code, band and USD value it was checked against: RELEASED when its sender wallet is already QUALIFIED abroad, otherwise AWAITING_WALLET_QUALIFICATION until you qualify that wallet. Otherwise:
| Status | When |
|---|---|
400 Bad Request | The code is missing or not in the table of the hold's band. |
404 Not Found | There is no such hold. |
409 Conflict | Its code was already stated, the hold was already released, or the sender wallet is in Brazil (the hold is released without a code). |
Checklist before November 15, 2026
- Show your users the purpose codes from
GET /v2/purpose-codesof the quote's band, and let them pick one for each international leg. - Send
inputPurposeCodeandoutputPurposeCodeon the quote for each international leg (see Purpose Code on the Quote). - Send the full qualification when you register a beneficiary wallet, and next to every raw
walletAddressyou pay out to. - Qualify the beneficiary wallets you already have: from November 15, 2026 a
PENDINGwallet cannot receive on-chain payouts. List them withGET /v2/account/beneficiaries/wallets/?qualificationStatus=PENDINGand complete each withPATCH /v2/account/beneficiaries/wallets/{wallet-id}. Repeat withsubAccountIdfor each sub-account. - Qualify your deposit sender wallets: list them with
GET /v2/account/deposit-sender-wallets/?qualificationStatus=PENDING. Deposits without a ticket from a sender qualified in Brazil (BR) are never held. - Subscribe your webhooks to
WALLET-QUALIFICATION,DEPOSIT-HOLDandTICKET, and handleWALLET-QUALIFICATION-REQUIRED,TICKET-PURPOSE-CODE-REQUIREDandDEPOSIT-PURPOSE-CODE-REQUIRED. - Answer the deposits already waiting for a code: list them with
GET /v2/account/tickets/?missingPurposeCode=true&subAccountId=*.
Common Questions
My flows are only PIX in and out. Do I need to do anything?
No. PIX pay-ins, PIX payouts, refunds and INTERNAL transfers are domestic: they take no purpose code and no wallet qualification, before or after November 15, 2026. Do not send a purpose code on these quotes: a code on a domestic leg returns 400 Bad Request.
Is a PIX deposit ever held? No. Only on-chain deposits sent straight to your wallet address, without a ticket, from a sender not known to be in Brazil, are held.
What happens on November 15, 2026 to the wallets I left PENDING?
They stay PENDING. A PENDING beneficiary wallet cannot receive on-chain payouts until you qualify it. A PENDING deposit sender wallet does not block deposits with a ticket, but its deposits without a ticket are held until you qualify it in Brazil, or qualify it abroad and state each deposit's purpose code.
Can I change a purpose code once it is stated?
No. A ticket's or a hold's code is stated once: a different code returns 409 Conflict. A ticket's code comes from its quote, so for a new operation, quote again with the right code.
What if I never answer a hold?
The deposit stays held, and is not credited or returned. The DEPOSIT-PURPOSE-CODE-REQUIRED webhook is repeated every 24 hours until you state its code.
Do sub-accounts follow the same rules?
Yes. Every endpoint on this page takes subAccountId, and each sub-account qualifies its own wallets.