Skip to main content

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:

  1. The concepts: what is reported, and which operations are international.
  2. Wallet qualification: who is on the other side of an on-chain transfer.
  3. Purpose codes: why the money moves, and the two tables (bands) they come from.
  4. The flows: payouts, deposits with a ticket and deposits without a ticket, which can be held.
  5. A checklist and common questions.
Timeline: optional through November 14, 2026, required from November 15, 2026

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, 2026From 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 addressAccepted: the wallet is PENDINGRefused with 400 Bad Request, naming the first missing field
An on-chain payout to a destination that is not QUALIFIEDCreatedRefused with 400 Bad Request, in Brazil too
A ticket without the purpose code of an international legCreatedRefused with 400 Bad Request
An on-chain deposit with no ticket, from a sender not known to be in BrazilCredited, then asks for its purpose codeHeld, 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:

WhatThe question it answersWhere you send it
Wallet qualificationWho 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 codeWhy 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 (inputCurrency and inputPaymentMethod on the quote);
  • the output leg is how it leaves: the payout (outputCurrency and outputPaymentMethod).

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 isDomestic or international?Purpose code for that leg
BRL rails: PIX, TED, BOLETO, BANK-TRANSFER in BRLDomesticNo
INTERNAL (between Avenia accounts)DomesticNo
REFUND (returns an earlier transfer)DomesticNo
Fiat in any currency other than BRL: USD, EUR, ARS, COP, MXN, a SWIFT currencyAlways internationalYes. No wallet is involved, so no qualification either.
A public chain (POLYGON, ETHEREUM, TRON, BASE, …), on a payoutDepends on the destination wallet's countryOnly when the wallet is not in Brazil
A public chain, on a deposit with a ticketAlways internationalYes: the sender is unknown when you quote
A public chain, on a deposit without a ticketDepends on the sender wallet's countryOnly 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:

OperationInternational?Why
PIX in BRL → BRLA to your Avenia balanceNoBoth legs are domestic.
BRLA → USDC on Polygon to a wallet with counterpartyCountry BRNoThe destination is in Brazil.
BRLA → USDC on Polygon to a wallet with counterpartyCountry USYes, the payoutThe destination is abroad.
BRLA → USDC on Polygon to a wallet whose country you never statedYes, the payoutTreated as abroad until you state BR.
BRL by PIX → USD by wireYes, the payoutThe payout is fiat in a currency other than BRL.
EUR by SEPA → BRLAYes, the depositThe deposit is fiat in a currency other than BRL.
USDC deposited on Polygon through a ticket, from any walletYes, the depositThe sender is unknown when you quote.
USDC sent straight to your wallet address by a wallet with counterpartyCountry ARYes, the depositThe sender is abroad.
USD by ACH → USDC on Polygon to a wallet abroadYes, both legsA deposit and a payout, each international: two codes.

What you send, flow by flow​

FlowWhat you sendFrom November 15, 2026, when it is missing
BRL operations (PIX, TED, boleto), INTERNAL transfers, refundsNothingNothing changes
On-chain payout, to a beneficiary wallet or a raw addressA QUALIFIED destination, and outputPurposeCode on the quote when the destination is not in BrazilThe ticket is refused
Payout in a currency other than BRL (wire, SEPA, SWIFT, …)outputPurposeCode on the quoteThe ticket is refused
Deposit with a ticket, on-chain (TRANSFER, PERMIT) or in a currency other than BRLinputPurposeCode on the quoteThe ticket is refused
Deposit in a currency other than BRL that a third party sends to your virtual accountinputPurposeCode after it is credited, when Avenia asks with TICKET-PURPOSE-CODE-REQUIREDIt is credited, and asks for its code
On-chain deposit without a ticket, sent straight to your wallet addressThe sender wallet's qualification, plus the deposit's purpose code when the sender is abroadIt 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:

WalletWhat it isWhere you qualify it
Beneficiary walletA wallet you register to pay out to.On creation, or later with PATCH. See the Wallet's Guide.
Deposit sender walletA 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 addressA walletAddress you pay out to without registering it.In ticketBlockchainOutput, next to walletAddress, when you create the ticket. See Payouts.

Fields​

FieldTypeThe question it answers
isSelfCustodyboolDoes the counterparty hold the wallet's private keys itself (true), or does an institution, such as an exchange, hold them for it (false)?
custodianNamestringWhich institution holds the keys? Only when isSelfCustody is false; not accepted when it is true. Up to 100 characters.
counterpartyNamestringWho 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.
counterpartyCountrystringWhere 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.
isSameEconomicGroupboolOnly 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:

isSelfCustodycounterpartyCountryAlso needed
trueBRcounterpartyName
trueAny other countrycounterpartyName, and isSameEconomicGroup for a company account
falseBRcounterpartyName, custodianName
falseAny other countrycounterpartyName, 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 PENDING and keeps the valid fields you sent, and with no qualification field at all it is PENDING with empty fields. You can complete it later with PATCH.
  • From November 15, 2026, every qualification you send must be complete: when you create a beneficiary wallet, when you PATCH a beneficiary or deposit sender wallet, and next to a raw walletAddress in a ticket. An incomplete one returns 400 Bad Request naming the first field it lacks, and nothing is stored. Wallets left PENDING before that date stay PENDING until 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:

  • counterpartyCountry that is not an ISO 3166-1 alpha-2 code;
  • counterpartyName or custodianName longer than 100 characters, or with control characters;
  • custodianName on a self-custody wallet (isSelfCustody is true);
  • isSameEconomicGroup on a wallet in Brazil (counterpartyCountry is BR);
  • isSameEconomicGroup set to true on 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 PATCH replaces the whole qualification: send every field each time, since a field you leave out is cleared.

Qualification Status and ACAM Group​

qualificationStatusMeaning
QUALIFIEDThe qualification is complete and valid.
PENDINGNo 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 (counterpartyCountry BR) 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:

GET/v2/purpose-codes
{
"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:

BandThe operation is worthCodes accepted
UP_TO_50KUSD 50,000 or lessThose whose bands include UP_TO_50K
ABOVE_50KMore than USD 50,000Those 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 (inputPurposeCode and outputPurposeCode) 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 purposeCodeBand and usdAmount, and the ticket keeps them.

To pick a code from the right table:

  1. Quote without the codes, and read purposeCodeBand.
  2. Show your user the codes of GET /v2/purpose-codes whose bands include that band.
  3. 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 (not INTERNAL).
  • 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.

QuoteinputPurposeCodeoutputPurposeCode
USDC on Polygon → BRLA INTERNALYes—
USD by ACH → USDC INTERNALYes—
BRLA INTERNAL → USDC on Polygon, wallet abroad—Yes
BRL by PIX → USD by wire—Yes
USD by ACH → USDC on Polygon, wallet abroadYesYes
USD by ACH → USDC on Polygon, wallet in BrazilYes— (dropped by the ticket)
EURC on Base → USD by wireYesYes
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:

FieldDescription
inputPurposeCodeThe code you stated for the deposit leg.
outputPurposeCodeThe code you stated for the payout leg.
purposeCodeBandThe table both codes come from: UP_TO_50K or ABOVE_50K.
usdAmountThe 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:

  1. Is the destination QUALIFIED? From November 15, 2026, an on-chain payout only goes to a QUALIFIED destination, in Brazil too: Avenia has to report who received it. A payout to a PENDING beneficiary wallet, or to a raw address without its complete qualification, is refused with 400 Bad Request.
  2. Is it in Brazil? The destination's counterpartyCountry decides whether the payout is international:
    • BR: the payout is domestic. The ticket drops any outputPurposeCode the quote carries. The deposit leg keeps its own inputPurposeCode when it is international (for example USD → USDC).
    • Any other country: the payout is international and needs outputPurposeCode on the quote. From November 15, 2026, a ticket without it is refused with 400 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.

DestinationThrough November 14, 2026From November 15, 2026
QUALIFIED in Brazil (BR)Domestic: no outputPurposeCode (dropped if sent)Same
QUALIFIED abroadoutputPurposeCode optionaloutputPurposeCode required
PENDING: a beneficiary wallet not yet qualified, or a raw address with missing fieldsPaid, treated as abroad unless its counterpartyCountry is BR; outputPurposeCode optionalRefused

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 (counterpartyCountry BR) 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​

PATCH/v2/account/tickets/{ticket-id}/purpose-code
ParameterTypeDescription
ticket-idstringThe ticket of the deposit.
subAccountIdstringOptional. 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:

StatusWhen
400 Bad RequestThe 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 FoundThere is no such ticket.
409 ConflictThe 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​

Starting November 15, 2026

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 counterpartyCountry is 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 (counterpartyCountry BR), such as any wallet Avenia holds for another account;
  • a deposit from one of your own wallets, and an INTERNAL transfer 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 doThe hold isreleaseReason
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 orderReleased as soon as both are done, with that codePURPOSE_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​

  1. The deposit arrives. Avenia records its sender wallet and holds the deposit with status AWAITING_PURPOSE_CODE. You receive:
    • DEPOSIT-PURPOSE-CODE-REQUIRED on the DEPOSIT-HOLD subscription, repeated every 24 hours until you state the code;
    • WALLET-QUALIFICATION-REQUIRED on the WALLET-QUALIFICATION subscription, when the sender wallet is PENDING. Its data.depositSenderWallet.id is the {wallet-id} to qualify. You can also list GET /v2/account/deposit-sender-wallets/ and match its walletAddress to the hold's senderWalletAddress.
  2. You answer, depending on who sent it:
    • a sender in Brazil: qualify the sender wallet with counterpartyCountry BR, 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_QUALIFICATION until the wallet is QUALIFIED. If it already is, the hold is released at once.
  3. The hold is released (RELEASED). The deposit is credited as usual, with its code when it has one. The hold's ticketId links the new ticket, which sends the usual ticket webhooks, starting with TICKET-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​

GET/v2/account/deposit-holds
ParameterTypeDescription
subAccountIdstringThe sub-account ID, if applicable.
statusstringFilter by AWAITING_PURPOSE_CODE, AWAITING_WALLET_QUALIFICATION or RELEASED.
cursorstringCursor 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.

FieldDescription
token, chain, amountThe crypto deposited, where it arrived and how much.
txHashThe deposit's transaction hash.
senderWalletAddressThe sender wallet. Qualifying it in Brazil (counterpartyCountry BR) releases the hold.
statusAWAITING_PURPOSE_CODE, AWAITING_WALLET_QUALIFICATION (its code is stated; its sender wallet is not QUALIFIED yet) or RELEASED.
purposeCodeBandWhile 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.
usdAmountWhile 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.
purposeCodeThe code stated. Not kept when the hold is released for a sender in Brazil.
releaseReasonPURPOSE_CODE or DOMESTIC_SENDER, once released.
ticketIdThe deposit's ticket, once released.
releasedAtWhen it was released.

State the Purpose Code of a Held Deposit​

PATCH/v2/account/deposit-holds/{hold-id}/purpose-code
ParameterTypeDescription
hold-idstringThe held deposit.
subAccountIdstringOptional. 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:

StatusWhen
400 Bad RequestThe code is missing or not in the table of the hold's band.
404 Not FoundThere is no such hold.
409 ConflictIts 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-codes of the quote's band, and let them pick one for each international leg.
  • Send inputPurposeCode and outputPurposeCode on 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 walletAddress you pay out to.
  • Qualify the beneficiary wallets you already have: from November 15, 2026 a PENDING wallet cannot receive on-chain payouts. List them with GET /v2/account/beneficiaries/wallets/?qualificationStatus=PENDING and complete each with PATCH /v2/account/beneficiaries/wallets/{wallet-id}. Repeat with subAccountId for 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-HOLD and TICKET, and handle WALLET-QUALIFICATION-REQUIRED, TICKET-PURPOSE-CODE-REQUIRED and DEPOSIT-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.