The TaxBaaz API lets your own software create invoices, look up FBR/PRA status, record receipts and read your books.
Getting started
- In TaxBaaz open Settings > API & Webhooks and create a key. Choose only the scopes you need.
- Send it on every request:
Authorization: Bearer tbx_live_...
- Call
GET /v1/me to confirm it works.
A key acts as the user who created it, so that user's role, permissions and branches apply on top of the key's scopes. Keys work only if your plan includes API access.
Sandbox keys
A key created as sandbox (tbx_test_...) is for building your integration safely. It can read your data, but every create call is run in full (validation, tax calculation, numbering) and then rolled back: nothing is saved and nothing is sent to FBR/PRA. The response shows what would have happened. Responses carry X-TaxBaaz-Sandbox. Because nothing is saved, an id returned by a sandbox create cannot be fetched afterwards.
Errors
Every error has the same shape: {"error": {"code": "...", "message": "...", "details": {...}, "request_id": "req_..."}}. Match on code; show message to people. Quote request_id if you contact support. Statuses: 401 key problem, 402 plan or subscription, 403 scope or permission, 404 not found, 422 invalid request, 429 rate limit, 5xx our side.
Rate limits
Per company and plan, per minute: starter 60, business 300, enterprise 1200; sandbox keys 30. Over the limit you get 429 with a Retry-After header.
Idempotency
Send Idempotency-Key: <unique text> on POST requests. Retrying the same request with the same key returns the original response (header Idempotent-Replayed: true) instead of creating a duplicate. Keys are remembered for 24 hours.
Pagination
List endpoints take page and per_page (at most 100) and return data, links and meta.
Money and dates
Amounts are strings with four decimals (never floats). Dates are YYYY-MM-DD, timestamps ISO 8601. Times follow the company's timezone setting.
Account
GET/v1/me
Who am I?
Shows the company, the user the key acts as, the key's scopes and environment, and the rate limit that applies. A good first call to check a new key.
curl https://taxbaaz.esasoftpk.com/api/v1/me -H "Authorization: Bearer $TAXBAAZ_KEY"
Responses: 200, 401, 429
Invoices
GET/v1/invoicesscope: invoices:read
List invoices
Newest first. Only documents the user behind the key may see are returned.
Scope: invoices:read
| Parameter | In | Type | Description |
page | query | integer | Page number, starting at 1. |
per_page | query | integer | Rows per page (default 25, at most 100). |
status | query | string | draft, submitted, posted or cancelled. |
authority_status | query | string | For example pending, queued, posted, failed, not_required. |
payment_status | query | string | unpaid, partial or paid. |
customer_id | query | integer | Only this customer's documents. |
document_type | query | string | For example sale_invoice, credit_note. |
from | query | string (date) | Invoice date on or after. |
to | query | string (date) | Invoice date on or before. |
number | query | string | Part of the invoice number. |
curl https://taxbaaz.esasoftpk.com/api/v1/invoices -H "Authorization: Bearer $TAXBAAZ_KEY"
Responses: 200, 401, 403, 429
POST/v1/invoicesscope: invoices:write
Create an invoice
Creates the invoice, numbers it, posts it to your books and returns it (201). Sending it to FBR/PRA happens afterwards in the background - it never delays or fails this call; watch authority.status or subscribe to the invoice.posted / invoice.failed webhooks. Give each line either an item_id or a description and unit_price. Send "draft": true to save without posting. Use an Idempotency-Key header so a retry cannot create a second invoice.
Scope: invoices:write
| Body field | Type | | Notes |
branch_id | integer | | optional |
document_type | string | | One of: sale_invoice, service_invoice, export_invoice; optional |
invoice_date | string | | date; optional |
due_date | string | | date; optional |
customer_id | integer | required | |
currency | string | | max 3 chars; optional |
exchange_rate | number | | optional |
invoice_discount_amount | number | | optional |
other_charges | number | | optional |
freight | number | | optional |
notes | string | | max 2000 chars; optional |
terms | string | | max 2000 chars; optional |
draft | boolean | | optional |
items | array of objects | required | |
items[].item_id | integer | | optional |
items[].description | string | | max 255 chars; optional |
items[].quantity | number | required | |
items[].unit_price | number | | optional |
items[].discount_percent | number | | optional |
items[].discount_amount | number | | optional |
items[].tax_rate_id | integer | | optional |
items[].unit_id | integer | | optional |
items[].tax_treatment | string | | One of: standard, reduced, zero_rated, exempt, further_tax_applicable; optional |
payments | array of objects | | optional |
payments[].method | string | | One of: cash, bank, card, cheque, online, credit |
payments[].amount | number | | |
payments[].bank_account_id | integer | | optional |
payments[].reference_no | string | | max 100 chars; optional |
curl -X POST https://taxbaaz.esasoftpk.com/api/v1/invoices \
-H "Authorization: Bearer $TAXBAAZ_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"customer_id":12,"invoice_date":"2026-09-19","items":[{"item_id":5,"quantity":2},{"description":"Delivery","quantity":1,"unit_price":500}],"payments":[{"method":"cash","amount":1000}]}'
Responses: 201, 401, 403, 422, 429
GET/v1/invoices/{id}scope: invoices:read
Get an invoice
One invoice with its lines and its FBR/PRA status.
Scope: invoices:read
| Parameter | In | Type | Description |
id | path | integer | The id. |
curl https://taxbaaz.esasoftpk.com/api/v1/invoices/{id} -H "Authorization: Bearer $TAXBAAZ_KEY"
Responses: 200, 401, 403, 429
GET/v1/invoices/{id}/statusscope: invoices:read
Get an invoice's FBR/PRA status
A light call for polling: the invoice status, payment status, the authority status (pending, queued, posted, failed, not_required), the fiscal number(s) and, if the last attempt failed, the reason.
Scope: invoices:read
| Parameter | In | Type | Description |
id | path | integer | The id. |
curl https://taxbaaz.esasoftpk.com/api/v1/invoices/{id}/status -H "Authorization: Bearer $TAXBAAZ_KEY"
Responses: 200, 401, 403, 429
GET/v1/invoices/{id}/pdfscope: invoices:read
Download an invoice PDF
The invoice printed with the company's template, including the fiscal number and QR code once posted.
Scope: invoices:read
| Parameter | In | Type | Description |
id | path | integer | The id. |
curl https://taxbaaz.esasoftpk.com/api/v1/invoices/{id}/pdf -H "Authorization: Bearer $TAXBAAZ_KEY"
Responses: 200, 401, 403, 429
Customers
GET/v1/customersscope: customers:read
List customers
Sorted by name.
Scope: customers:read
| Parameter | In | Type | Description |
page | query | integer | Page number, starting at 1. |
per_page | query | integer | Rows per page (default 25, at most 100). |
search | query | string | Part of the name, NTN or email. |
curl https://taxbaaz.esasoftpk.com/api/v1/customers -H "Authorization: Bearer $TAXBAAZ_KEY"
Responses: 200, 401, 403, 429
POST/v1/customersscope: customers:write
Create a customer
A registered customer must have an NTN or STRN. Opening balances are not accepted here; import them from the TaxBaaz Import screen.
Scope: customers:write
| Body field | Type | | Notes |
name | string | required | max 255 chars |
business_name | string | | max 255 chars; optional |
customer_type | string | required | One of: registered, unregistered, export, government |
ntn | string | | optional |
cnic | string | | optional |
strn | string | | optional |
province | string | | max 100 chars; optional |
address | string | | max 255 chars; optional |
city | string | | max 100 chars; optional |
phone | string | | max 30 chars; optional |
email | string | | email; max 255 chars; optional |
contact_person | string | | max 255 chars; optional |
credit_limit | number | | optional |
credit_days | integer | | optional |
payment_terms | string | | max 255 chars; optional |
filer_status | string | required | One of: filer, non-filer, unknown |
notes | string | | max 2000 chars; optional |
curl -X POST https://taxbaaz.esasoftpk.com/api/v1/customers \
-H "Authorization: Bearer $TAXBAAZ_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"name":"Ali Traders","customer_type":"registered","ntn":"1234567","filer_status":"filer","city":"Lahore"}'
Responses: 201, 401, 403, 422, 429
GET/v1/customers/{id}scope: customers:read
Get a customer
One customer.
Scope: customers:read
| Parameter | In | Type | Description |
id | path | integer | The id. |
curl https://taxbaaz.esasoftpk.com/api/v1/customers/{id} -H "Authorization: Bearer $TAXBAAZ_KEY"
Responses: 200, 401, 403, 429
Items
GET/v1/itemsscope: items:read
List items
Sorted by name.
Scope: items:read
| Parameter | In | Type | Description |
page | query | integer | Page number, starting at 1. |
per_page | query | integer | Rows per page (default 25, at most 100). |
search | query | string | Part of the name or code. |
curl https://taxbaaz.esasoftpk.com/api/v1/items -H "Authorization: Bearer $TAXBAAZ_KEY"
Responses: 200, 401, 403, 429
POST/v1/itemsscope: items:write
Create an item
The code must be unique in your company.
Scope: items:write
| Body field | Type | | Notes |
name | string | required | max 255 chars |
code | string | required | max 50 chars |
description | string | | max 2000 chars; optional |
type | string | | One of: goods, service; optional |
hs_code | string | | max 20 chars; optional |
unit_id | integer | | optional |
sale_rate | number | required | |
purchase_rate | number | | optional |
tax_rate_id | integer | | optional |
sro_schedule_no | string | | max 50 chars; optional |
sro_item_serial_no | string | | max 50 chars; optional |
tax_treatment | string | | One of: standard, reduced, zero_rated, exempt, further_tax_applicable; optional |
curl -X POST https://taxbaaz.esasoftpk.com/api/v1/items \
-H "Authorization: Bearer $TAXBAAZ_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"code":"ITM-001","name":"Copper Wire 2.5mm","sale_rate":1500,"hs_code":"8544.4990","tax_rate_id":1}'
Responses: 201, 401, 403, 422, 429
GET/v1/items/{id}scope: items:read
Get an item
One item.
Scope: items:read
| Parameter | In | Type | Description |
id | path | integer | The id. |
curl https://taxbaaz.esasoftpk.com/api/v1/items/{id} -H "Authorization: Bearer $TAXBAAZ_KEY"
Responses: 200, 401, 403, 429
Receipts
POST/v1/receiptsscope: receipts:write
Record a receipt
Records money received from a customer and posts it to the books. Apply it to invoices with allocations, or send "auto_allocate": true to pay the oldest invoices first; anything left stays as the customer's advance.
Scope: receipts:write
| Body field | Type | | Notes |
branch_id | integer | | optional |
receipt_date | string | | date; optional |
customer_id | integer | required | |
amount | number | required | |
method | string | required | One of: cash, bank_transfer, cheque, card, online |
bank_account_id | integer | | optional |
reference_no | string | | max 100 chars; optional |
cheque_no | string | | max 50 chars; optional |
cheque_date | string | | date; optional |
remarks | string | | max 1000 chars; optional |
auto_allocate | boolean | | optional |
allocations | array of objects | | optional |
allocations[].invoice_id | integer | required | |
allocations[].amount | number | required | |
curl -X POST https://taxbaaz.esasoftpk.com/api/v1/receipts \
-H "Authorization: Bearer $TAXBAAZ_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"customer_id":12,"amount":25000,"method":"bank_transfer","reference_no":"TRX-8891","auto_allocate":true}'
Responses: 201, 401, 403, 422, 429
Reports
GET/v1/reportsscope: reports:read
List reports
The reports the user behind the key may run, with the filters each accepts.
Scope: reports:read
curl https://taxbaaz.esasoftpk.com/api/v1/reports -H "Authorization: Bearer $TAXBAAZ_KEY"
Responses: 200, 401, 403, 429
GET/v1/reports/{key}scope: reports:read
Run a report
Runs one of the reports from the list. Pass its filters as query parameters (for example period=this_month, from, to, branch_id). Paged with page and per_page.
Scope: reports:read
| Parameter | In | Type | Description |
key | path | string | The report key from GET /v1/reports. |
page | query | integer | Page number, starting at 1. |
per_page | query | integer | Rows per page (default 25, at most 100). |
sort | query | string | A sortable column key. |
direction | query | string | asc or desc. |
curl https://taxbaaz.esasoftpk.com/api/v1/reports/{key} -H "Authorization: Bearer $TAXBAAZ_KEY"
Responses: 200, 401, 403, 429
Accounting
GET/v1/accounting/trial-balancescope: accounting:read
Trial balance
Debit and credit balance of every account as at a date. Company-wide, so it needs a user who can see all branches.
Scope: accounting:read
| Parameter | In | Type | Description |
as_of | query | string (date) | Defaults to today. |
curl https://taxbaaz.esasoftpk.com/api/v1/accounting/trial-balance -H "Authorization: Bearer $TAXBAAZ_KEY"
Responses: 200, 401, 403, 429
GET/v1/accounting/profit-and-lossscope: accounting:read
Profit and loss
Income, expenses and net income for a period. Company-wide.
Scope: accounting:read
| Parameter | In | Type | Description |
from | query | string (date) | Defaults to the first of this month. |
to | query | string (date) | Defaults to today. |
curl https://taxbaaz.esasoftpk.com/api/v1/accounting/profit-and-loss -H "Authorization: Bearer $TAXBAAZ_KEY"
Responses: 200, 401, 403, 429
GET/v1/accounting/balance-sheetscope: accounting:read
Balance sheet
Assets, liabilities and equity as at a date. Company-wide.
Scope: accounting:read
| Parameter | In | Type | Description |
as_of | query | string (date) | Defaults to today. |
curl https://taxbaaz.esasoftpk.com/api/v1/accounting/balance-sheet -H "Authorization: Bearer $TAXBAAZ_KEY"
Responses: 200, 401, 403, 429
Scopes
| Scope | Allows |
invoices:read | List and read invoices, their FBR/PRA status and PDFs |
invoices:write | Create invoices and other sales documents |
customers:read | List and read customers |
customers:write | Create customers |
items:read | List and read items |
items:write | Create items |
receipts:write | Record customer receipts |
reports:read | Run reports |
accounting:read | Read the trial balance, profit and loss and balance sheet |
Webhooks
Register an HTTPS URL under Settings > API & Webhooks > Webhooks and choose the events you want. We send each event as a signed POST with a JSON body.
| Event | Sent when |
invoice.created | A sales document was created |
invoice.posted | An invoice was accepted by FBR/PRA and has its fiscal number |
invoice.failed | FBR/PRA rejected an invoice or retries ran out |
payment.received | A customer receipt was recorded |
journal.posted | A journal entry was posted to the ledger |
Example body:
{
"id": "evt_9x2k...",
"type": "invoice.posted",
"api_version": "v1",
"created_at": "2026-09-19T10:15:00+05:00",
"data": {
"id": 4711,
"number": "INV-000123",
"status": "posted",
"authority": {
"name": "fbr",
"status": "posted",
"fbr_invoice_number": "1234567890123456789"
}
}
}
| Header | Meaning |
X-TaxBaaz-Event | The event name. |
X-TaxBaaz-Event-Id | Same id inside the body; use it to ignore duplicates. |
X-TaxBaaz-Delivery | This delivery attempt's id. |
X-TaxBaaz-Timestamp | Unix time the request was signed. |
X-TaxBaaz-Signature | t=<timestamp>,v1=<hex HMAC-SHA256 of "<timestamp>.<raw body>" with your signing secret> |
A delivery that does not get a 2xx within 10 seconds is retried after 1, 5, 30, 120, 360 minutes, then abandoned. An endpoint that fails 25 deliveries in a row is switched off. Answer with any 2xx quickly and do the real work afterwards. The same event can arrive more than once; use id to ignore repeats. Every delivery, with the response we got, is in the delivery log, and you can resend any of them.
Verifying the signature
Reject a request unless the signature matches and the timestamp is recent (five minutes is a good limit). Use the raw request body, before any JSON parsing.
// PHP
$header = $_SERVER['HTTP_X_TAXBAAZ_SIGNATURE']; // t=1758270000,v1=ab12...
parse_str(str_replace(',', '&', $header), $p);
$raw = file_get_contents('php://input');
$expected = hash_hmac('sha256', $p['t'].'.'.$raw, $signingSecret);
if (! hash_equals($expected, $p['v1']) || abs(time() - (int) $p['t']) > 300) { http_response_code(400); exit; }
// Node.js (Express, with express.raw({type: 'application/json'}))
const [t, v1] = req.get('X-TaxBaaz-Signature').split(',').map(s => s.split('=')[1]);
const expected = crypto.createHmac('sha256', secret).update(t + '.' + req.body).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1))) return res.sendStatus(400);
# Python
t, v1 = [part.split('=')[1] for part in request.headers['X-TaxBaaz-Signature'].split(',')]
expected = hmac.new(secret.encode(), f"{t}.".encode() + request.get_data(), hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, v1): abort(400)