TaxBaaz API — by ESASOFT

Version v1 · Base URL https://taxbaaz.esasoftpk.com/api · OpenAPI (JSON)

The TaxBaaz API lets your own software create invoices, look up FBR/PRA status, record receipts and read your books.

Getting started

  1. In TaxBaaz open Settings > API & Webhooks and create a key. Choose only the scopes you need.
  2. Send it on every request: Authorization: Bearer tbx_live_...
  3. 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

ParameterInTypeDescription
pagequeryintegerPage number, starting at 1.
per_pagequeryintegerRows per page (default 25, at most 100).
statusquerystringdraft, submitted, posted or cancelled.
authority_statusquerystringFor example pending, queued, posted, failed, not_required.
payment_statusquerystringunpaid, partial or paid.
customer_idqueryintegerOnly this customer's documents.
document_typequerystringFor example sale_invoice, credit_note.
fromquerystring (date)Invoice date on or after.
toquerystring (date)Invoice date on or before.
numberquerystringPart 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 fieldTypeNotes
branch_idintegeroptional
document_typestringOne of: sale_invoice, service_invoice, export_invoice; optional
invoice_datestringdate; optional
due_datestringdate; optional
customer_idintegerrequired
currencystringmax 3 chars; optional
exchange_ratenumberoptional
invoice_discount_amountnumberoptional
other_chargesnumberoptional
freightnumberoptional
notesstringmax 2000 chars; optional
termsstringmax 2000 chars; optional
draftbooleanoptional
itemsarray of objectsrequired
items[].item_idintegeroptional
items[].descriptionstringmax 255 chars; optional
items[].quantitynumberrequired
items[].unit_pricenumberoptional
items[].discount_percentnumberoptional
items[].discount_amountnumberoptional
items[].tax_rate_idintegeroptional
items[].unit_idintegeroptional
items[].tax_treatmentstringOne of: standard, reduced, zero_rated, exempt, further_tax_applicable; optional
paymentsarray of objectsoptional
payments[].methodstringOne of: cash, bank, card, cheque, online, credit
payments[].amountnumber
payments[].bank_account_idintegeroptional
payments[].reference_nostringmax 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

ParameterInTypeDescription
idpathintegerThe 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

ParameterInTypeDescription
idpathintegerThe 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

ParameterInTypeDescription
idpathintegerThe 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

ParameterInTypeDescription
pagequeryintegerPage number, starting at 1.
per_pagequeryintegerRows per page (default 25, at most 100).
searchquerystringPart 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 fieldTypeNotes
namestringrequiredmax 255 chars
business_namestringmax 255 chars; optional
customer_typestringrequiredOne of: registered, unregistered, export, government
ntnstringoptional
cnicstringoptional
strnstringoptional
provincestringmax 100 chars; optional
addressstringmax 255 chars; optional
citystringmax 100 chars; optional
phonestringmax 30 chars; optional
emailstringemail; max 255 chars; optional
contact_personstringmax 255 chars; optional
credit_limitnumberoptional
credit_daysintegeroptional
payment_termsstringmax 255 chars; optional
filer_statusstringrequiredOne of: filer, non-filer, unknown
notesstringmax 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

ParameterInTypeDescription
idpathintegerThe 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

ParameterInTypeDescription
pagequeryintegerPage number, starting at 1.
per_pagequeryintegerRows per page (default 25, at most 100).
searchquerystringPart 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 fieldTypeNotes
namestringrequiredmax 255 chars
codestringrequiredmax 50 chars
descriptionstringmax 2000 chars; optional
typestringOne of: goods, service; optional
hs_codestringmax 20 chars; optional
unit_idintegeroptional
sale_ratenumberrequired
purchase_ratenumberoptional
tax_rate_idintegeroptional
sro_schedule_nostringmax 50 chars; optional
sro_item_serial_nostringmax 50 chars; optional
tax_treatmentstringOne 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

ParameterInTypeDescription
idpathintegerThe 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 fieldTypeNotes
branch_idintegeroptional
receipt_datestringdate; optional
customer_idintegerrequired
amountnumberrequired
methodstringrequiredOne of: cash, bank_transfer, cheque, card, online
bank_account_idintegeroptional
reference_nostringmax 100 chars; optional
cheque_nostringmax 50 chars; optional
cheque_datestringdate; optional
remarksstringmax 1000 chars; optional
auto_allocatebooleanoptional
allocationsarray of objectsoptional
allocations[].invoice_idintegerrequired
allocations[].amountnumberrequired
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

ParameterInTypeDescription
keypathstringThe report key from GET /v1/reports.
pagequeryintegerPage number, starting at 1.
per_pagequeryintegerRows per page (default 25, at most 100).
sortquerystringA sortable column key.
directionquerystringasc 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

ParameterInTypeDescription
as_ofquerystring (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

ParameterInTypeDescription
fromquerystring (date)Defaults to the first of this month.
toquerystring (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

ParameterInTypeDescription
as_ofquerystring (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

ScopeAllows
invoices:readList and read invoices, their FBR/PRA status and PDFs
invoices:writeCreate invoices and other sales documents
customers:readList and read customers
customers:writeCreate customers
items:readList and read items
items:writeCreate items
receipts:writeRecord customer receipts
reports:readRun reports
accounting:readRead 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.

EventSent when
invoice.createdA sales document was created
invoice.postedAn invoice was accepted by FBR/PRA and has its fiscal number
invoice.failedFBR/PRA rejected an invoice or retries ran out
payment.receivedA customer receipt was recorded
journal.postedA 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"
        }
    }
}
HeaderMeaning
X-TaxBaaz-EventThe event name.
X-TaxBaaz-Event-IdSame id inside the body; use it to ignore duplicates.
X-TaxBaaz-DeliveryThis delivery attempt's id.
X-TaxBaaz-TimestampUnix time the request was signed.
X-TaxBaaz-Signaturet=<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)