Add up a month of fees
List every fee Gravv charged an account in a month, tie each to its movement, and subtract fee refunds.
Use this recipe to list every fee Gravv charged an account in a month, match each fee to its movement, and subtract fee refunds.
Who it is for. This recipe is for a finance lead who checks Gravv's fees against the agreed price list at the end of the month.
Status. Available.
Money example
In October, from the USD account:
- You send 40 payouts, each with
fee"1.5"on its row, for 60 USD in all. - You receive 3 incoming wires, each followed by its own
fee_chargerow. - One payout failed. If its fee came back, there is a
fee_chargerow withdirectionin.
Your total is the sum of the out fee rows, minus the sum of the in fee rows.
Before you start
- Your agreed price list.
- The id of the account Gravv takes your fees from. That is your business's main account for deposit fees, and the sending account for transfer fees.
Amounts come from your business's own pricing, so this recipe never quotes a price.
Find out what is required
- On a transfer's row,
feeis the fee for that transfer, as a string. It is"0"when there is no fee. The transfer response gives itsfee_currency. - Every settled fee also has its own row, with
transaction_typefee_charge,directionout,channelinternal,statuscompleted,referenceset to the fee's id, and adescriptionin the formFee charge: <service> - <fee type>. - A fee refund is a
fee_chargerow withdirectioninand adescriptionthat startsFee refund:. - Do not add both a transfer's
feeand itsfee_chargerow. They describe the same fee. - A fee row's
sending_account_idis the account Gravv took the fee from, and itscustomer_idis the customer the movement was for. Gravv takes deposit fees from your business's main account, never from the deposit. - A fee your plan bills on the monthly invoice, instead of taking it at once, has no
fee_chargerow. You find it on your invoice instead.
Steps
1. List the fee rows
The list has no date filter. Page through it, newest first, until date_created is before 2026-10-01T00:00:00Z.
curl --request GET \
--url 'https://api.gravv.xyz/v1/transactions?account_id=43b7a3a6-9a8d-4a03-980d-7b71d8f56413&transaction_type=fee_charge&items_per_page=100&page=1' \
--header 'Api-Key: <API_KEY>'
const response = await fetch("https://api.gravv.xyz/v1/transactions?account_id=43b7a3a6-9a8d-4a03-980d-7b71d8f56413&transaction_type=fee_charge&items_per_page=100&page=1", {
method: "GET",
headers: {
"Api-Key": process.env.GRAVV_API_KEY,
},
});
console.log(response.status, await response.json());
import os
import requests
response = requests.get(
"https://api.gravv.xyz/v1/transactions?account_id=43b7a3a6-9a8d-4a03-980d-7b71d8f56413&transaction_type=fee_charge&items_per_page=100&page=1",
headers={
"Api-Key": os.environ["GRAVV_API_KEY"],
},
)
print(response.status_code, response.json())
{
"data": {
"items": [
{
"id": "4b0dbb41-8d52-48f1-942c-3fe860e7a113",
"amount": "15",
"date_created": "2026-10-13T08:02:40Z",
"direction": "out",
"transaction_type": "fee_charge",
"description": "Fee charge: transfers - wire_in",
"reference": "e2acf72f-9e57-4f7a-a0ee-89aed453dd32",
"channel": "internal",
"status": "completed",
"currency": "USD"
}
],
"page": 1,
"items_per_page": 1,
"total_pages": 1,
"total_items": 1
},
"error": null
}
2. Add them up
For each fee type in description, add the out rows and subtract the in rows. Compare each total with your price list.
3. Cross-check the payouts
curl --request GET \
--url 'https://api.gravv.xyz/v1/transactions?account_id=43b7a3a6-9a8d-4a03-980d-7b71d8f56413&direction=out&transaction_type=global_remittance,bank_account&items_per_page=100&page=1' \
--header 'Api-Key: <API_KEY>'
const response = await fetch("https://api.gravv.xyz/v1/transactions?account_id=43b7a3a6-9a8d-4a03-980d-7b71d8f56413&direction=out&transaction_type=global_remittance,bank_account&items_per_page=100&page=1", {
method: "GET",
headers: {
"Api-Key": process.env.GRAVV_API_KEY,
},
});
console.log(response.status, await response.json());
import os
import requests
response = requests.get(
"https://api.gravv.xyz/v1/transactions?account_id=43b7a3a6-9a8d-4a03-980d-7b71d8f56413&direction=out&transaction_type=global_remittance,bank_account&items_per_page=100&page=1",
headers={
"Api-Key": os.environ["GRAVV_API_KEY"],
},
)
print(response.status_code, response.json())
Read fee on each row. US bank payouts are bank_account rows, and cross-border payouts are global_remittance rows.
What can go wrong
| Response | Why | Fix |
|---|---|---|
A payout shows fee "0" |
No fee is set for that route, or Gravv could not work out the fee when you sent the payout. In both cases, Gravv charges no fee | This is expected. You owe nothing for that payout |
| A fee row with no movement in the same month | Gravv writes a fee row when it charges the fee, and the fee can settle after its movement | Match fee rows to movements within one day either side, not on the exact day |
| A fee is in a different currency from the payout | Percentage fees use the source currency, and conversions charge in the asset sent | Read fee_currency in the transfer response |
| The volume total includes fees | /v1/transactions/volume adds every transaction type unless you filter |
Always pass transaction_type |
Webhooks you will receive
Gravv sends no webhook for fees.
Testing in sandbox
Run a simulated deposit and a small sandbox payout. Fee rows appear only when your sandbox pricing has fees. In sandbox, a 5 USD payout showed fee "1" on its own row, but no fee_charge row for it appeared within minutes. A KYC fee did appear as its own row (Fee charge: identity - kyc_individual). So in sandbox, read transfer fees from the transfer rows.