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:

  1. You send 40 payouts, each with fee "1.5" on its row, for 60 USD in all.
  2. You receive 3 incoming wires, each followed by its own fee_charge row.
  3. One payout failed. If its fee came back, there is a fee_charge row with direction in.

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, fee is the fee for that transfer, as a string. It is "0" when there is no fee. The transfer response gives its fee_currency.
  • Every settled fee also has its own row, with transaction_type fee_charge, direction out, channel internal, status completed, reference set to the fee's id, and a description in the form Fee charge: <service> - <fee type>.
  • A fee refund is a fee_charge row with direction in and a description that starts Fee refund:.
  • Do not add both a transfer's fee and its fee_charge row. They describe the same fee.
  • A fee row's sending_account_id is the account Gravv took the fee from, and its customer_id is 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_charge row. 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.

Reference

Was this page helpful?