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.

POST /api/v1/invoices/ synchronous JSON

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/
What happens on submit Billora validates the invoice, resolves the buyer against its own customer records (creating one if needed), builds the FBR submission, and posts it — all before responding. The response already tells you whether FBR accepted it.

02 — Access

Authentication

Every request needs two headers, issued per company from Billora's admin panel (sys_admin → company → API Credentials):

HeaderValue
X-Api-KeyYour API key (not secret — safe to appear in logs)
X-Api-SecretYour API secret — shown once at generation time, store it securely
Keep this separate from any other Billora integration you have If your system already calls Billora for something else (e.g. an ATL/registration-status check), that likely uses a different credential and header scheme. This API key/secret pair is specific to invoice submission and is not interchangeable with it.

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

POST/api/v1/invoices/

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

GET/api/v1/invoices/<reference_number>/

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

GET/api/v1/invoices/account/

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

DELETE/api/v1/invoices/<reference_number>/

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

POST/api/v1/invoices/<reference_number>/retry/

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.

You usually don't need this If a submit call itself times out on your end, just submit again with the same 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_id with 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_no and/or sro_item_serial_no match 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

FieldTypeRequiredNotes
client_invoice_refstringYesYour own unique invoice reference. Reusing it returns the original result instead of submitting again — see Idempotency.
invoice_datestring (YYYY-MM-DD)NoDefaults to today.
tax_ratenumberYesOne 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_typestringNoThe 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_idstringNoThe 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_nostringNoOnly 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_nostringNoSame situation as sro_schedule_no — the FBR SRO item serial, e.g. "82".
notesstringNoFree text, shown on the invoice in Billora.
customer.namestringYes 
customer.registration_type"Registered" | "Unregistered"NoDefaults to Unregistered.
customer.ntnstringNo**One of ntn/cnic is required when registration_type is Registered.
customer.cnicstringNo**See above.
customer.addressstringNo 
customer.provincestringNoe.g. "SINDH", "PUNJAB". Defaults to Sindh if omitted or unrecognized.
items[]arrayYesAt least one line.
items[].descriptionstringYes 
items[].quantitynumberYesMust be > 0.
items[].ratenumberYesUnit 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_codestringNoRecommended — FBR may reject a line without one for some goods.
items[].uomstringNoFree text (e.g. "Kg", "Numbers, pieces, units"); matched case-insensitively, falls back to a generic unit if unrecognized.
items[].retail_pricenumberNoOnly 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

StatusHTTPMeaning
accepted200FBR confirmed the invoice. fbr_invoiceno is set.
ambiguous202Billora'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.
rejected422Billora 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.
failed500An internal error occurred, or Billora is offline for your company (no Sandbox/Live FBR connection). Nothing is kept; safe to submit again.
deleted200Removed with DELETE (e.g. a testing/sandbox posting). The client_invoice_ref can be submitted again.

Reference

Errors

HTTPWhen
403Missing or invalid X-Api-Key / X-Api-Secret, or your IP isn't on this client's allow-list (if one is configured).
404Unknown reference_number on status/retry.
409Retry called on a submission that isn't rejected or failed.
422Request body failed validation, or the invoice was rejected.
429Rate limit exceeded — see Rate limits.
500Internal 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.

  1. 1
    Always send the same client_invoice_ref for a given invoice on every attempt — e.g. your own internal invoice number, not a freshly generated value per call.
  2. 2
    If 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.
  3. 3
    The 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

Set your HTTP client timeout to 150 seconds or more Submission is synchronous end-to-end, including the call to FBR, and FBR's own API can occasionally take close to two minutes to respond. A shorter timeout risks your client giving up before Billora has actually finished — the invoice may still succeed server-side even though you saw a timeout. If that happens, just retry with the same client_invoice_ref rather than treating it as a hard failure.