Integration guide
Billora Invoicing API
Submit sales invoices from your own accounting/ERP system directly to Billora, which validates them, posts them to FBR, and returns the outcome in the same response — no polling required for the normal case.
01 — Start here
Overview
Billora handles e-invoicing and FBR digital-invoice submission for your business. This API lets your own accounting system submit an invoice the moment it's finalized, instead of someone re-entering it into Billora by hand.
Base URL for every endpoint below:
https://erp.billora.pk/api/v1/invoices/
02 — Access
Authentication
Every request needs two headers, issued per company from Billora's admin panel (sys_admin → company → API Credentials):
| Header | Value |
|---|---|
| X-Api-Key | Your API key (not secret — safe to appear in logs) |
| X-Api-Secret | Your API secret — shown once at generation time, store it securely |
A bad or missing key/secret returns 403:
{
"detail": "Invalid API secret."
}
03 — Quick start
End-to-end example
One call, one response. No separate "check back later" step for the normal case.
curl -X POST https://erp.billora.pk/api/v1/invoices/ \ -H "X-Api-Key: your_api_key" \ -H "X-Api-Secret: your_api_secret" \ -H "Content-Type: application/json" \ --max-time 180 \ -d '{ "client_invoice_ref": "ERP-2026-04871", "invoice_date": "2026-10-13", "customer": { "name": "Nando'"'"'s Pakistan Pvt Ltd", "ntn": "1211412-0", "address": "Plot 4, Korangi Industrial Area, Karachi", "province": "SINDH", "registration_type": "Registered" }, "tax_rate": 18, "items": [ { "description": "Printed packaging boxes", "hs_code": "4819.1000", "uom": "Numbers, pieces, units", "quantity": 500, "rate": 45.00 } ], "notes": "ERP invoice #4871" }'
Response — invoice accepted and already posted to FBR:
{
"reference_number": "BLR-20261013-7F3A9C21E0",
"status": "accepted",
"trans_id": 10512,
"fbr_invoiceno": "3310104ABPR1760...DI",
"error": null,
"fbr_mode": "live"
}
fbr_mode is in every response: live (real FBR), sandbox (FBR test system - the FBR invoice number isn't a real one) or offline (no FBR connection set up - nothing is sent or kept, status failed).
Endpoint
Submit an invoice
Validates, builds, and posts the invoice to FBR before responding. See Request fields for the full body schema and Status values for what the response's status can be.
Validation error (nothing was created)
{
"status": "rejected",
"error": "customer.ntn or customer.cnic is required when registration_type is 'Registered'. items[0].rate must be greater than 0."
}
HTTP 422. All field problems are combined into one message.
Endpoint
Check a submission's status
Mainly useful for the rare ambiguous outcome — everything else is already final in the submit response.
{
"reference_number": "BLR-20261013-7F3A9C21E0",
"status": "accepted",
"error": null,
"trans_id": 10512,
"fbr_invoiceno": "3310104ABPR1760...DI",
"created_at": "2026-10-13T11:02:04",
"updated_at": "2026-10-13T11:02:41",
"fbr_mode": "live"
}
Unknown reference number returns 404 with {"error": "Not found"}.
Endpoint
Check credentials and FBR mode
Submits nothing. Use it to test the API key/secret and see which FBR mode your invoices will be posted in.
{
"company_id": 14,
"company_name": "Delizia Private Limited",
"fbr_mode": "live"
}
Endpoint
Delete a submission's invoice
Removes the invoice from Billora so it can be corrected and submitted again with the same client_invoice_ref. Allowed for invoices posted in sandbox (testing) mode and for rejected/failed submissions. Returns 409 for an invoice FBR accepted in Live mode, or while an FBR outcome is still unknown.
{
"reference_number": "BLR-20261013-7F3A9C21E0",
"status": "deleted",
"fbr_mode": "sandbox"
}
Endpoint
Retry a failed submission
Only works on a submission currently in rejected or failed — anything else returns 409. Runs the same build-and-post sequence again and returns the new outcome, same shape as submit's response.
client_invoice_ref instead — see Idempotency. Retry is for when you got a clean rejected/failed response and have fixed the underlying problem (e.g. a missing FBR sale type was added).
Reference
Scenarios not yet set up
If your company already has a sale type configured for the scenario_id you send, it's just used — nothing below applies. If it doesn't, Billora sets one up automatically on this first call instead of rejecting it, as long as:
- Your company is already enabled for that
scenario_idwith FBR in Billora (this is a one-time thing Billora support does per scenario — it's not something this API call can grant on its own). - If the scenario needs an SRO reference,
sro_schedule_noand/orsro_item_serial_nomatch a real entry in FBR's own SRO reference list — an unrecognized value is rejected, not guessed.
Everything else about the scenario (its FBR transaction type) is looked up from FBR's own reference data, never taken from what you send. This only covers a plain-percentage tax_rate — a scenario with a fixed or compound FBR rate (e.g. a fixed amount per unit) still needs Billora support to configure once, since trusting a client-declared tax formula is treated differently from trusting which already-approved scenario applies.
Reference
Request body fields
| Field | Type | Required | Notes |
|---|---|---|---|
| client_invoice_ref | string | Yes | Your own unique invoice reference. Reusing it returns the original result instead of submitting again — see Idempotency. |
| invoice_date | string (YYYY-MM-DD) | No | Defaults to today. |
| tax_rate | number | Yes | One sales-tax percentage for the whole invoice, e.g. 18. Must match a sale type already configured for your company in Billora. For a sale type with a fixed or compound FBR rate (e.g. a fixed amount per unit — see scenario_id below), this is ignored; send any number, but send scenario_id or sale_type instead so Billora can select it directly. |
| sale_type | string | No | The exact FBR transaction type text for one of your company's configured sale types, e.g. "Processing/Conversion of Goods". Needed if your company has more than one sale type configured at the same tax_rate, and required for a sale type whose FBR rate isn't a plain percentage (e.g. Cement's fixed per-unit rate) — tax_rate can't select those on its own. An unrecognized value is rejected, never guessed. |
| scenario_id | string | No | The FBR scenario code for one of your company's configured sale types, e.g. "SN016". Same purpose as sale_type — use whichever one you have on hand; both narrow down the same lookup and can be sent together. If your company has nothing configured for this scenario_id yet, Billora sets it up automatically the first time — see Scenarios not yet set up — as long as your company is already enabled for that scenario with FBR. |
| sro_schedule_no | string | No | Only for scenarios governed by an SRO (e.g. SN005, SN006, SN007, SN024) when Billora is setting up scenario_id for your company for the first time — the exact FBR SRO schedule text, e.g. "EIGHTH SCHEDULE Table 1". Unused once that scenario is already configured. |
| sro_item_serial_no | string | No | Same situation as sro_schedule_no — the FBR SRO item serial, e.g. "82". |
| notes | string | No | Free text, shown on the invoice in Billora. |
| customer.name | string | Yes | |
| customer.registration_type | "Registered" | "Unregistered" | No | Defaults to Unregistered. |
| customer.ntn | string | No* | *One of ntn/cnic is required when registration_type is Registered. |
| customer.cnic | string | No* | *See above. |
| customer.address | string | No | |
| customer.province | string | No | e.g. "SINDH", "PUNJAB". Defaults to Sindh if omitted or unrecognized. |
| items[] | array | Yes | At least one line. |
| items[].description | string | Yes | |
| items[].quantity | number | Yes | Must be > 0. |
| items[].rate | number | Yes | Unit price, must be > 0. Amount/tax are always computed server-side as quantity × rate — a submitted total is never trusted, so don't send one. |
| items[].hs_code | string | No | Recommended — FBR may reject a line without one for some goods. |
| items[].uom | string | No | Free text (e.g. "Kg", "Numbers, pieces, units"); matched case-insensitively, falls back to a generic unit if unrecognized. |
| items[].retail_price | number | No | Only relevant for FBR's Third Schedule (fixed/notified retail price) scenarios, e.g. packaged consumer goods under scenario SN008. Ignored for every other scenario — omit it unless your company sells these. |
Reference
Status values
| Status | HTTP | Meaning |
|---|---|---|
| accepted | 200 | FBR confirmed the invoice. fbr_invoiceno is set. |
| ambiguous | 202 | Billora's own call to FBR didn't get a confirmed response (e.g. a timeout). Genuinely unknown yet — don't resubmit; check status again shortly instead. |
| rejected | 422 | Billora or FBR declined the invoice. error explains why and fbr_error_codes lists FBR's codes (e.g. ["0052"]). The invoice is not kept in Billora - fix the data and submit again; the same client_invoice_ref can be reused. |
| failed | 500 | An internal error occurred, or Billora is offline for your company (no Sandbox/Live FBR connection). Nothing is kept; safe to submit again. |
| deleted | 200 | Removed with DELETE (e.g. a testing/sandbox posting). The client_invoice_ref can be submitted again. |
Reference
Errors
| HTTP | When |
|---|---|
403 | Missing or invalid X-Api-Key / X-Api-Secret, or your IP isn't on this client's allow-list (if one is configured). |
404 | Unknown reference_number on status/retry. |
409 | Retry called on a submission that isn't rejected or failed. |
422 | Request body failed validation, or the invoice was rejected. |
429 | Rate limit exceeded — see Rate limits. |
500 | Internal error while processing — safe to retry. |
Reference
Idempotency & retries
client_invoice_ref is your safety net. Billora remembers it per invoice — submitting the same value again, for any reason, returns whatever the first attempt actually resolved to instead of processing it a second time.
- 1Always send the same
client_invoice_reffor a given invoice on every attempt — e.g. your own internal invoice number, not a freshly generated value per call. - 2If your own request times out (see Timeouts) before you see a response, it's safe to just call submit again with the same body. You'll either get the real result back immediately, or the original call will still be finishing — either way, nothing gets posted to FBR twice.
- 3The one exception is ambiguous — that specifically means Billora itself doesn't know the outcome yet either. Use status, not a resubmit, until it resolves.
Reference
Rate limits
Each API client has a per-minute request limit (default 60/min, configurable per client). Exceeding it returns:
{
"detail": "Request was throttled. Expected available in 60 seconds."
}
HTTP 429. If your integration needs a higher limit, ask Billora to raise it for your API client.
Reference
Timeouts
client_invoice_ref rather than treating it as a hard failure.