Skip to content

Reports

The ledger, how refunds net out, the export, and what is not in it.

Every collected payment leaves one row, every refund leaves a negative one, and the report is that ledger summed over a period. Nothing is recomputed at read time, so a report of last quarter shows the rates that applied then, not the rates that apply now.

What goes into the ledger

A row is written when the payment is credited, not when it is created and not when it is attempted. Four things stop it.

ConditionResult
Tax is off on your accountNo row, and no catching up later if you switch it on
The payment is a sandbox oneNo row, ever
The payment is a funding top-up of your own balanceNo row
The computed tax is zeroNo row, so a zero-rated country leaves nothing behind

The row is written once per payment. A second attempt on the same payment, or a replay of the event, never duplicates it.

A refund writes a negative row

Refund a payment and the ledger reverses the tax in the same proportion. Refund a third of the amount and a third of the tax comes back, at the original rate, against the original country, with both amounts negative.

That proportion is capped at the whole: refunding more than the recorded base still reverses at most one hundred percent of the tax. And when the original payment left no row, because Tax was off at the time, the reversal is computed fresh from today's rate for that country rather than being skipped.

The consequence is the one you want at filing time: the report nets collections against reversals on its own, and you never subtract anything by hand.

The report

GEThttps://api.wajub.com/tax/reports

Five query parameters, all optional, and the two filters narrow the whole payload rather than adding a section to it.

periodenumoptionaldefault : 30d
One of 7d, 30d, 90d, month, year or custom. month is the current calendar month, year the current calendar year.
start_datedateoptional
Only read when period is custom. Missing, it falls back to thirty days ago.
end_datedateoptional
The end of the window, on any period. Defaults to the end of today.
countrystringoptional
Two letters. Restricts the whole report to one country.
currencystringoptional
Three letters. Restricts the whole report to one currency.

The response nests everything under report, and gives you the same figures three times: once as a total, once per country, once per currency.

Response · 200 OK
{
"code": 200,
"status": "OK",
"report": {
"period": "30d",
"start_date": "2026-08-13",
"end_date": "2026-09-12",
"summary": {
"total_tax_collected": 808500,
"total_taxable_amount": 4200000,
"total_reversals": -12300,
"net_tax": 796200,
"currency": "XAF"
},
"by_country": [
{
"country_code": "CM",
"tax_collected": 808500,
"tax_reversed": -12300,
"taxable_amount": 4200000,
"transactions": 312
}
],
"by_currency": [
{
"currency": "XAF",
"tax_collected": 808500,
"tax_reversed": -12300,
"taxable_amount": 4200000,
"transactions": 312
}
]
}
}

Three details matter when you reconcile against your own books. tax_collected counts only positive rows and tax_reversed only negative ones, so net_tax is their sum and is the figure you file. transactions counts every row in the group, reversals included, so a country with three hundred payments and twelve refunds reports three hundred and twelve. And summary.currency is whatever you filtered on, or the first currency found: on a multi-currency account the summary adds amounts across currencies, which is only meaningful once you have filtered to one.

File country by country, one currency at a time

You owe tax to a jurisdiction in its own currency, so the report you act on is the one with both country and currency set. by_country on an unfiltered report is useful for spotting where you have exposure, not for filling in a return.

The Dashboard report is not the same object

Tax in the Dashboard answers the same question with a different payload, so do not expect the two to line up field for field.

APIDashboard
Periodstart_date and end_dateA period object with start and end
Totalsummary.total_tax_collectedtotal_tax
Per rowtax_collected and transactionstax_amount and count
Collections and reversalsSplit into two fieldsSummed into one
Across currenciesNot convertedConverted into your base currency as global_total
RatesAbsentThe rate table is included alongside

The Dashboard one nets collections and reversals into a single figure per row, which is why a month with refunds reads lower there than tax_collected does on the API. Both are right, they answer slightly different questions.

The CSV export

Tax, then export, downloads the current view as a semicolon-separated CSV with a UTF-8 marker, so Excel opens it correctly in French and Portuguese locales without an import step. It carries the period, the totals, the base currency conversion when you trade in several, then one block per country and one per currency.

Exports count against your plan's monthly quota: ten a month on Pay as you go, unlimited from Growth up. There is no export on the API.

Registration thresholds

Many jurisdictions only require you to register once your local revenue crosses a line. GET /tax/thresholds lists the thresholds Wajub tracks, with the amount, the currency and the period each is measured over.

GET /tax/thresholds/alerts compares them against your own recorded revenue for the current calendar year and returns only what needs attention: approaching from 80 % of the line, and exceeded at 100 %. An empty array means nothing is close, and it is also what you get before any tax has been recorded.

What the ledger does not hold

Worth knowing so you do not look for exports that are not there.

No per-jurisdiction breakdown. When several sub-national rates apply, the row carries their total, not the split. The breakdown exists in the calculation and is not persisted.

No mobile money levy and no digital services tax. Both are computed in places and neither is recorded, shown on a payment, or added to anything. Where a country levies one, it is not in your Wajub figures.

Nothing on the payment object. GET /payments/{id} returns no tax field. The ledger is read through the report endpoint and the Dashboard, nowhere else.

No webhook. No event fires when a row is written or reversed.

What did you think of this content?