Billing API
This section of the DeskOS API covers invoices, credit notes and the payments received against them. From an outside system such as an accounting package, a BI or reporting stack, a spreadsheet sync, n8n or Zapier, or an AI agent you can list every invoice a workspace has raised, read one with its line items and tax breakdown, list the credit notes that reduce them, list the money actually received, and keep your own copy in step as things change.
The typical use is a finance export. An accountant pulls every invoice for a campus and a date range, pulls the credit notes for the same window, pulls the receipts behind them, and reconciles all three against their own ledger without anyone exporting a spreadsheet by hand.
Getting a key, sending it, campus rules, the response shape and the errors every section shares are all on the Authentication page. This page only covers what is specific to billing.
There is no create, update or delete endpoint here, and there will not be one. An invoice is a legal document and a receipt is a record of money that moved. Both are raised and corrected in the admin console, where the change is attributed to the person who made it. An API key is not a person. A POST or PATCH to any path on this page answers 405.
Base path
All paths on this page are relative to:
https://oscar.nova.deskos.net/api/v1/orders/external/
Your Developer Settings page shows the exact base URL for your workspace.
Permissions
Billing permissions all start with billing:. An admin ticks them per key on the Developer Settings page.
| Permission | What it allows | Endpoints |
|---|---|---|
billing:invoices:read | Read invoices, their totals, tax and derived payment status | GET invoices/, GET invoices/<id>/ |
billing:credit_notes:read | Read credit notes and their line items | GET credit-notes/, GET credit-notes/<id>/ |
billing:payments:read | Read the receipts against invoices | GET payments/, and the payments block on an invoice |
GET health/ needs no permission and works with any key. Use it to confirm the key carries the billing: permissions you expect before doing anything else.
Unlike other sections, a key only gets billing permissions if the admin creating it holds both Billing: view and Financials: view in DeskOS. Listing invoices and seeing what they are worth are two separate rights inside the product, and an operator who cannot see amounts on screen must not be able to mint a key that exports every amount the organisation has ever billed. An admin who holds only one of the two sees the Billing section on the Developer Settings page marked as not grantable.
A read-only accounting sync normally wants all three permissions. A dashboard that only charts what was billed needs billing:invoices:read alone.
Campuses
Invoices and credit notes belong to a campus. The general campus rules are on the Authentication page. For this section:
- Every list endpoint accepts
?campus_id=to narrow results. Without it they return everything the key can reach. - A key restricted to campuses sees invoices, credit notes and receipts for those campuses only.
- A receipt has no campus of its own. It is scoped through the invoice it settles, so a campus filter on
payments/filters on the invoice's campus. - A very old invoice may predate campus assignment and carry no campus. An unrestricted key still sees it, through its company. A campus-restricted key does not.
Billing centres
Every invoice and credit note is raised from a billing centre: the legal entity that bills, with its own registered name, its own tax number, its own invoice number series and its own tax configuration. It is the "bill from" half of the document, the counterpart to the billing_to block.
A billing centre is not a campus and is not finer than one. One entity commonly trades out of several campuses, and one campus can host more than one entity. So:
- Exporting by campus can put two campuses of one legal entity into two ledgers, and two legal entities at one campus into one. Both are wrong, and neither shows up as a missing row.
- An accounting export, a tax return and a set of statutory books are per entity.
billing_center_idis the filter that matches them.
Every invoice, credit note and receipt payload carries a billing_center block, and all three list endpoints accept ?billing_center_id=.
"billing_center": {
"id": 3,
"code": "ACME-01",
"name": "Acme Spaces Private Limited",
"gst_number": "29AABBC1234D1Z5",
"gst_state_code": "29",
"gst_state_name": "Karnataka",
"address_line_1": "Level 4, 12 MG Road",
"address_line_2": null,
"city": "Bengaluru",
"state": "Karnataka",
"pincode": "560001",
"country": "India"
}
id is what ?billing_center_id= takes. code is the operator's own short reference for the entity, which they set themselves and may leave blank; it is not the id and the two are not interchangeable.
GET health/ lists the entities a key can name, so you never have to hardcode an id:
"billing_centers": [
{"id": 3, "code": "ACME-01", "name": "Acme Spaces Private Limited",
"gst_number": "29AABBC1234D1Z5", "campus_ids": [7, 9]},
{"id": 5, "code": null, "name": "Acme Ventures LLP",
"gst_number": "27ZZZZZ9999Z9Z9", "campus_ids": [7]}
]
campus_ids is where each entity trades, narrowed to the campuses your key can reach. Two entities sharing a campus, as above, is exactly the case a campus filter cannot express.
- A billing centre id that belongs to another operator answers
404, the same as an unknown one. - A campus-restricted key may still name an entity that spans campuses it cannot reach. It gets that entity's documents for its own campuses only. Naming the entity never widens what the key can see.
- An invoice raised before the operator configured any entity carries
"billing_center": null.
How money is reported
Four things decide whether your numbers match the operator's. All four are deliberate.
Amounts are strings with two decimals, in the currency the payload names. Never parse them as floats, and never assume a symbol: DeskOS runs in several currencies and the currency is a property of the workspace, not of this API.
Payment status is derived on every read, never stored. It is computed from the payments actually completed against the invoice plus the live credit notes raised against it, using the same rounding tolerance the admin invoice list uses. An invoice within one currency unit of settled reads as paid, because part payments routinely land a unit short after a gateway rounds and chasing that unit is not a real receivable.
payment_status | Meaning |
|---|---|
pending | Nothing has been settled against it |
partially_paid | Some of it is settled, some is still owed |
paid | Settled, within the rounding tolerance |
cancelled | The invoice was cancelled, so nothing is owed |
Proforma and cancelled documents are excluded unless you ask for them. A proforma has not been billed and a cancelled invoice is not owed, so summing either produces a number nobody in the operator's office would recognise. Send include_proforma=true or include_cancelled=true when you genuinely want them.
A credit note reduces what is owed exactly as a payment does. amount_due on an invoice is total_amount minus what was paid minus what was credited. If you are computing receivables, use amount_due rather than subtracting payments yourself.
{
"total_amount": "59000.00",
"amount_paid": "30000.00",
"amount_credited": "5000.00",
"amount_due": "24000.00",
"payment_status": "partially_paid"
}
API endpoints
| Method | Path | Permission | Purpose |
|---|---|---|---|
| GET | health/ | none | What this key is and can do |
| GET | invoices/ | billing:invoices:read | List and filter invoices |
| GET | invoices/<id>/ | billing:invoices:read | One invoice with items, credit notes and receipts |
| GET | credit-notes/ | billing:credit_notes:read | List and filter credit notes |
| GET | credit-notes/<id>/ | billing:credit_notes:read | One credit note with its items |
| GET | payments/ | billing:payments:read | List the receipts against invoices |
Invoice, credit note and receipt ids are UUIDs. Company and campus ids are integers.
1. List invoices
GET /api/v1/orders/external/invoices/
GET /api/v1/orders/external/invoices/?campus_id=7&date_from=2026-08-01&date_to=2026-08-31
GET /api/v1/orders/external/invoices/?status=pending,partially_paid&overdue=true
GET /api/v1/orders/external/invoices/?updated_since=2026-09-13T06:00:00Z
| Filter | Notes |
|---|---|
campus_id | Invoices raised at this campus |
billing_center_id | Invoices raised by this legal entity, across every campus it trades at |
company_id | Invoices for one company |
status | Comma-separated: pending, partially_paid, paid, cancelled. Derived, not stored |
date_from, date_to | YYYY-MM-DD, inclusive, on invoice_date |
updated_since | ISO 8601 datetime. Everything changed at or after this moment |
invoice_number | Exact match, case-insensitive |
search | Contains, case-insensitive, on invoice number or company name |
overdue | true returns only invoices past due_date with money still owed |
include_proforma | true also returns proforma invoices. Off by default |
include_cancelled | true also returns cancelled invoices. Off by default |
page, page_size | page_size defaults to 50, maximum 200 |
Asking for status=cancelled includes cancelled invoices automatically, otherwise the filter would answer an explicit question with an empty list.
{
"success": true,
"count": 651,
"page": 1,
"page_size": 50,
"has_next": true,
"invoices": [
{
"id": "0f0b2a0e-3c1d-4e2b-9a77-2c1f5b7d4a10",
"invoice_number": "ACM/26-27/0412",
"invoice_date": "2026-08-01",
"due_date": "2026-08-07",
"period_start": "2026-08-01",
"period_end": "2026-08-31",
"campus_id": 7,
"billing_center": {
"id": 3,
"code": "ACME-01",
"name": "Acme Spaces Private Limited",
"gst_number": "29AABBC1234D1Z5",
"gst_state_code": "29",
"gst_state_name": "Karnataka",
"address_line_1": "Level 4, 12 MG Road",
"address_line_2": null,
"city": "Bengaluru",
"state": "Karnataka",
"pincode": "560001",
"country": "India"
},
"company": {"id": 41, "name": "Widgets Ltd"},
"currency": "INR",
"subtotal_amount": "50000.00",
"discount_amount": "0.00",
"total_tax_amount": "9000.00",
"round_off_amount": "0.00",
"total_amount": "59000.00",
"amount_paid": "30000.00",
"amount_credited": "5000.00",
"amount_due": "24000.00",
"payment_status": "partially_paid",
"taxes": [
{"slot": 1, "name": "CGST", "percent": "9.00", "amount": "4500.00"},
{"slot": 2, "name": "SGST", "percent": "9.00", "amount": "4500.00"}
],
"tax_names_source": "snapshot",
"is_proforma": false,
"is_cancelled": false,
"cancelled_at": null,
"converted_to_invoice_id": null,
"credit_note_issued": true,
"narration": "August seats",
"billing_to": {
"name": "Ada Lovelace",
"company": "Widgets Private Limited",
"gst_number": "29AABBC1234D1Z5",
"email": "accounts@widgets.example",
"address_line_1": "Level 4, 12 MG Road",
"address_line_2": null,
"city": "Bengaluru",
"state": "Karnataka",
"pincode": "560001"
},
"created_at": "2026-08-01T09:00:00+05:30",
"updated_at": "2026-09-10T16:40:00+05:30"
}
]
}
billing_to is the address as it was recorded on the document when it was issued, not the customer's address today. An invoice from March still shows the legal name and address it was raised against, even after the customer moves office or renames itself. The customer's current billing identity is on GET companies/<id>/ in the Members API.
billing_center is the legal entity that raised the invoice. See Billing centres above for why it is not the same question as which campus.
Which tax, not just how much
taxes carries up to three slots. Each is a real tax with a name, a rate and an amount, not an anonymous position:
| Field | Meaning |
|---|---|
slot | Which of the three tax columns this is, 1, 2 or 3 |
name | What that tax is called: CGST, SGST, IGST, VAT, and so on |
percent | The rate applied |
amount | The tax charged, in the invoice's currency |
The names matter because the slot number alone cannot tell an intrastate invoice from an interstate one. Nine per cent in slot 1 and nine in slot 2 is CGST plus SGST; eighteen in slot 3 is IGST. They are the same total and different postings. A slot with no percent and no amount is left out rather than returned as a row of zeroes.
How many slots there are and what they are called depend on the country and on the billing centre the invoice was raised from, so read the names rather than assuming an Indian three.
tax_names_source says where those names came from:
snapshot, the normal case: the names were recorded on the document when it was issued and are exactly what the invoice says. An entity that later renames or restructures its taxes never rewrites its old documents.derived, for documents raised before DeskOS recorded the names: they are read from the entity's tax configuration as it stands today, which mislabels anything issued under an earlier setup. Safe as a label, not safe to post automatically to a tax account without a human looking.
Line items carry the same slots with the same names, because slot 1 on a line is slot 1 on its document.
Store the highest updated_at you have seen, then poll ?updated_since= with that value. Raising an invoice, recording a payment against it, cancelling it and crediting it all move updated_at, so a poller sees every change without re-reading the year. Remember to URL-encode the timestamp: an unencoded +05:30 offset arrives as a space and is rejected.
2. One invoice
GET /api/v1/orders/external/invoices/<id>/
Returns the same object under invoice, plus three blocks a list cannot afford to carry:
items: the line items, each with quantity, unit price, HSN, its own tax slots, and the pro-rata window when the line is a part-month charge.credit_notes: every credit note raised against this invoice, in short form, cancelled ones included and flagged.payments: the receipts against it. Present only when the key also carriesbilling:payments:read. A key without that permission does not get an empty list, because an empty list reads as "no money received"; it does not get the key at all.
{
"success": true,
"invoice": {
"id": "0f0b2a0e-3c1d-4e2b-9a77-2c1f5b7d4a10",
"invoice_number": "ACM/26-27/0412",
"total_amount": "59000.00",
"amount_due": "24000.00",
"payment_status": "partially_paid",
"items": [
{
"id": 8812,
"name": "Dedicated desk",
"description": "12 seats, August",
"type": "subscription",
"hsn": "997212",
"quantity": 12,
"unit_price": "4166.67",
"total_price": "50000.00",
"is_taxable": true,
"taxes": [
{"slot": 1, "name": "CGST", "percent": "9.00", "amount": "4500.00"},
{"slot": 2, "name": "SGST", "percent": "9.00", "amount": "4500.00"}
],
"is_prorata_item": false,
"prorata_start_date": null,
"prorata_end_date": null
}
],
"credit_notes": [
{
"id": "6a1c9d33-8f42-4e0a-bb31-90e2f5a17c44",
"credit_note_number": "CN/26-27/0033",
"credit_note_date": "2026-08-20",
"credit_note_type": "post_sale_discount",
"total_amount": "5000.00",
"is_cancelled": false
}
],
"payments": [
{
"id": "b4d1e7a2-55c9-4f70-9d3e-71a0c2f4e881",
"amount": "30000.00",
"payment_mode": "neft",
"reference_number": "HDFC0098231",
"transaction_date": "2026-08-05",
"status": "reconciled",
"collection_account": {"id": "2c9f4d71-...", "name": "HDFC Current", "account_type": "bank"},
"created_at": "2026-08-05T11:12:00+05:30",
"updated_at": "2026-08-06T09:00:00+05:30"
}
]
}
}
Each receipt here carries its own invoice block as well, the same shape GET payments/ returns. It is trimmed from the example above because on an invoice detail it only repeats the invoice you already asked for.
3. Credit notes
GET /api/v1/orders/external/credit-notes/
GET /api/v1/orders/external/credit-notes/?campus_id=7&date_from=2026-08-01&date_to=2026-08-31
GET /api/v1/orders/external/credit-notes/<id>/
Credit notes are first-class documents in DeskOS with their own number series, their own date, their own tax breakdown and their own line items. They are not a netted total subtracted from something else.
| Filter | Notes |
|---|---|
campus_id | Credit notes raised at this campus |
billing_center_id | Credit notes raised by this legal entity |
company_id | Credit notes for one company |
invoice_id | Credit notes against one invoice |
credit_note_number | Exact match, case-insensitive |
credit_note_type | Comma-separated: sales_return, post_sale_discount, deficiency_in_service, correction, other |
date_from, date_to | YYYY-MM-DD, inclusive, on credit_note_date |
updated_since | ISO 8601 datetime |
include_cancelled | true also returns cancelled credit notes. Off by default |
page, page_size | page_size defaults to 50, maximum 200 |
{
"success": true,
"count": 14,
"page": 1,
"page_size": 50,
"has_next": false,
"credit_notes": [
{
"id": "6a1c9d33-8f42-4e0a-bb31-90e2f5a17c44",
"credit_note_number": "CN/26-27/0033",
"credit_note_date": "2026-08-20",
"credit_note_type": "post_sale_discount",
"campus_id": 7,
"billing_center": {"id": 3, "code": "ACME-01", "name": "Acme Spaces Private Limited", "...": "..."},
"company": {"id": 41, "name": "Widgets Ltd"},
"invoice": {
"id": "0f0b2a0e-3c1d-4e2b-9a77-2c1f5b7d4a10",
"invoice_number": "ACM/26-27/0412",
"invoice_date": "2026-08-01"
},
"reason": "Two seats released mid month",
"subtotal_amount": "4237.29",
"discount_amount": "0.00",
"total_tax_amount": "762.71",
"total_amount": "5000.00",
"taxes": [
{"slot": 1, "name": "CGST", "percent": "9.00", "amount": "381.36"},
{"slot": 2, "name": "SGST", "percent": "9.00", "amount": "381.35"}
],
"tax_names_source": "snapshot",
"is_cancelled": false,
"cancelled_at": null,
"billing_to": {"...": "..."},
"created_at": "2026-08-20T15:20:00+05:30",
"updated_at": "2026-08-20T15:20:00+05:30"
}
]
}
The invoice block carries the invoice number and date as they were snapshotted onto the credit note, which is what a tax authority expects to see on the document. GET credit-notes/<id>/ adds an items array in the same shape as invoice items.
A credit note is raised by the same legal entity as the invoice it reduces, and carries its own billing_center and its own named tax slots, both in the same shape as an invoice.
A credit note whose invoice your key cannot reach is not returned, and naming such an invoice in ?invoice_id= answers 404.
4. Payments received
GET /api/v1/orders/external/payments/
GET /api/v1/orders/external/payments/?campus_id=7&date_from=2026-08-01&date_to=2026-08-31
GET /api/v1/orders/external/payments/?invoice_id=0f0b2a0e-3c1d-4e2b-9a77-2c1f5b7d4a10
One row per receipt: money that actually landed in a collection account, with the mode it arrived by, its reference and its reconciliation status. Each row carries the invoice it settles, so a page of receipts needs no second call to be useful.
| Filter | Notes |
|---|---|
campus_id | Receipts against invoices raised at this campus |
billing_center_id | Receipts against invoices raised by this legal entity |
company_id | Receipts against one company's invoices |
invoice_id | Receipts against one invoice |
payment_mode | Comma-separated: neft, rtgs, imps, upi, cheque, cash, card, gateway, deposit_adjustment, other |
status | Comma-separated: unreconciled, reconciled, bounced, cancelled |
date_from, date_to | YYYY-MM-DD, inclusive, on transaction_date |
updated_since | ISO 8601 datetime |
page, page_size | page_size defaults to 50, maximum 200 |
amount_paidAn invoice can be settled without cash landing anywhere. TDS deducted by the customer, an advance already held, and a security deposit adjusted against a final bill all reduce what is owed without creating a receipt. So the receipts against an invoice can total less than its amount_paid, and that is correct rather than missing data. amount_paid and amount_due on the invoice are the figures to reconcile against; payments/ explains the part of it that arrived as money.
A receipt that is not linked to an invoice, such as a standalone advance, is not returned at all. A row with no campus of its own cannot be proved to belong to a campus a key may reach.
A receipt has no billing centre of its own. It belongs to the entity that raised the invoice it settles, so ?billing_center_id= here filters on the invoice's entity, and the invoice block on each row carries that entity's billing_center.
status is the reconciliation state of the receipt, not the state of the invoice. unreconciled means the operator has recorded the receipt but not yet matched it against a bank statement line. bounced means it was recorded and then failed, so it no longer settles anything.
Internal notes on a receipt and the staff member who recorded it are never returned. Notes carry edit-audit lines written by staff for staff, and neither belongs in a payload an outside system reads.
A finance export, end to end
GET health/to confirm the key, see which campuses it can reach and read thebilling_centerslist.GET invoices/?billing_center_id=3&date_from=2026-08-01&date_to=2026-08-31&page_size=200, paging untilhas_nextis false. Filter by the entity rather than the campus: the books you are posting into belong to a legal entity.GET credit-notes/with the same entity and window.GET payments/with the same entity and window.- Reconcile: billed is the sum of
total_amounton the invoices, credited is the sum oftotal_amounton the credit notes, received is the sum ofamounton the receipts, and outstanding is the sum ofamount_dueon the invoices. Do not compute outstanding yourself by subtracting receipts from invoices, because it would miss TDS, advances and deposit adjustments. - Afterwards, poll each endpoint with
?updated_since=and your highest seenupdated_atrather than re-reading the window.
Code examples
curl
KEY="dk_live_..."
BASE="https://oscar.nova.deskos.net/api/v1/orders/external"
# 1. What can this key do?
curl -s -H "Authorization: Api-Key $KEY" "$BASE/health/"
# 2. Every invoice raised in August at campus 7
curl -s -H "Authorization: Api-Key $KEY" \
"$BASE/invoices/?campus_id=7&date_from=2026-08-01&date_to=2026-08-31&page_size=200"
# 3. What is still owed
curl -s -H "Authorization: Api-Key $KEY" \
"$BASE/invoices/?status=pending,partially_paid&overdue=true&page_size=200"
# 4. The credit notes for the same month
curl -s -H "Authorization: Api-Key $KEY" \
"$BASE/credit-notes/?campus_id=7&date_from=2026-08-01&date_to=2026-08-31"
Python
import requests
from decimal import Decimal
BASE = "https://oscar.nova.deskos.net/api/v1/orders/external"
HEAD = {"Authorization": "Api-Key dk_live_..."}
def pages(path, **params):
"""Yield every row across every page of a list endpoint."""
params.setdefault("page_size", 200)
page = 1
while True:
params["page"] = page
body = requests.get(f"{BASE}/{path}", headers=HEAD, params=params, timeout=30).json()
key = next(k for k in body if isinstance(body[k], list))
for row in body[key]:
yield row
if not body.get("has_next"):
return
page += 1
invoices = list(pages("invoices/", campus_id=7, date_from="2026-08-01", date_to="2026-08-31"))
billed = sum(Decimal(i["total_amount"]) for i in invoices)
outstanding = sum(Decimal(i["amount_due"]) for i in invoices)
currency = invoices[0]["currency"] if invoices else None
print(len(invoices), "invoices", billed, "billed", outstanding, "outstanding", currency)
requests encodes the params dict for you, which is what keeps an updated_since timestamp with a +05:30 offset intact.
JavaScript / Node.js
const BASE = "https://oscar.nova.deskos.net/api/v1/orders/external";
const HEAD = { Authorization: "Api-Key dk_live_..." };
async function pages(path, params = {}) {
const rows = [];
let page = 1;
let more = true;
while (more) {
const query = new URLSearchParams({ ...params, page, page_size: 200 });
const body = await fetch(`${BASE}/${path}?${query}`, { headers: HEAD }).then((r) => r.json());
const key = Object.keys(body).find((k) => Array.isArray(body[k]));
rows.push(...body[key]);
more = body.has_next;
page += 1;
}
return rows;
}
const invoices = await pages("invoices/", { campus_id: 7, status: "pending,partially_paid" });
Amounts are strings. Keep them as strings, or use a decimal library. Number("59000.00") is fine for display and wrong for money.
Instructions for AI agents
The general rules (how a key is obtained, how to handle it, what to do on 401 and 403) are on the Authentication page. Read those first. This section adds what is specific to billing.
Permissions to ask for
- "Export our invoices into the accounting system":
billing:invoices:readandbilling:credit_notes:read, plusbilling:payments:readif you are reconciling receipts. - "Chart what we billed each month":
billing:invoices:readonly. - "Tell me who owes us money":
billing:invoices:readonly.amount_duealready accounts for credit notes.
There is no write permission to ask for. If a human asks you to raise, edit or cancel an invoice, or to record a payment, say that this API cannot and that it is done in the admin console.
Behaviour rules
- Never sum
total_amountacross a page and call it revenue. It is what was billed, before credit notes. Subtract credit notes, or use the operator's own reports. - Do not include proforma or cancelled documents in a total unless the human asked for them specifically. Both are excluded by default for that reason.
- Do not recompute payment status yourself.
payment_statusandamount_dueare already derived from the money with the operator's rounding tolerance. A figure you derive differently will disagree with the operator's screen, and the operator's screen is what they will trust. - Do not treat receipts as the whole story of what was paid. TDS, advances and deposit adjustments settle invoices without a receipt.
- Never assume a currency symbol. Read
currencyoff the payload and say so in your answer. - Treat a 404 as "not visible to this key", not as proof the document does not exist. It may belong to a campus the key cannot reach.
- Respect the daily limit.
health/showsrequests_todayagainstdaily_limit, shared across every section the key can reach. Useupdated_sincefor incremental syncs andpage_size=200rather than fetching documents one at a time.
Billing centres and taxes
- An export destined for a ledger, a tax return or a set of books is per legal entity. Filter with
?billing_center_id=, not?campus_id=, and read the ids fromGET health/. - Never infer the entity from the campus. One entity trades at several campuses and one campus can host several entities.
- Post a tax by its
name, never by its slot number. Two nine per cent slots are CGST plus SGST; one eighteen per cent slot is IGST. - If
tax_names_sourceisderived, say so to the human before mapping those names onto tax codes. The document predates DeskOS recording its own tax names, so the names are read from today's configuration and can be wrong for an old document.
Reporting back to the human
When you finish, tell the human the key name you used (never the key itself), the billing centre, campus and date window you covered, how many invoices, credit notes and receipts you read, whether proforma or cancelled documents were included, the currency, and any 403 or 429 you hit.
Frequently asked questions
Can I raise or cancel an invoice through the API? No, and that is permanent. This section reads. Invoices and credit notes are raised, corrected and cancelled in the admin console, where the action is attributed to a person.
Why does my total not match the operator's revenue report?
Three usual reasons: you included proforma invoices, you did not subtract credit notes, or you are comparing a billed figure to a collected one. Billed is the sum of total_amount, collected is the sum of receipt amount, and outstanding is the sum of amount_due. They are three different numbers.
Why does an invoice read paid when a small amount is still owed?
An invoice within one currency unit of settled is treated as settled, the same as on the operator's own screen. Part payments routinely land a unit short after a gateway rounds.
The receipts against this invoice do not add up to amount_paid. Is something missing?
No. TDS, advances and deposit adjustments settle an invoice without money landing in a collection account, so they create no receipt.
Which of our companies raised this invoice?
billing_center on the invoice, and the same block on credit notes and on the invoice inside a receipt. Filter a whole export with ?billing_center_id=, and read the available ids from GET health/. Campus will not answer this: one entity usually trades at several campuses, and a campus can host more than one.
How do I know whether a tax slot is CGST, SGST, IGST or something else?
Read name on the slot. tax_names_source says whether that name was recorded on the document when it was issued (snapshot) or read from today's tax configuration because the document predates the recording (derived). Post derived names with a human in the loop.
Can I get a customer's legal name, tax number and billing address?
Yes, two ways. billing_to on an invoice is the snapshot as it stood when the document was issued. For the customer's current details, GET companies/ in the Members API returns a billing block with the legal entity name, tax number and default billing address, plus an is_active flag.
Can I get a statement of account for a company?
Not yet. Today you build one from invoices/, credit-notes/ and payments/ filtered by company_id. A single statement endpoint is on the list.
Can DeskOS push billing changes to me instead of me polling?
Not yet. Webhooks cover members, companies and memberships today. See Webhooks. For billing, poll with updated_since.
Quick reference
Base: https://oscar.nova.deskos.net/api/v1/orders/external/
Auth: Authorization: Api-Key dk_live_...
Method: GET only. Everything else answers 405.
GET health/
GET invoices/?campus_id=&billing_center_id=&company_id=&status=&date_from=&date_to=
&updated_since=&invoice_number=&search=&overdue=&include_proforma=
&include_cancelled=&page=&page_size=
GET invoices/<id>/
GET credit-notes/?campus_id=&billing_center_id=&company_id=&invoice_id=
&credit_note_number=&credit_note_type=&date_from=&date_to=
&updated_since=&include_cancelled=&page=&page_size=
GET credit-notes/<id>/
GET payments/?campus_id=&billing_center_id=&company_id=&invoice_id=&payment_mode=
&status=&date_from=&date_to=&updated_since=&page=&page_size=
Permissions: billing:invoices:read, billing:credit_notes:read, billing:payments:read
(an admin needs Billing: view AND Financials: view to grant them)
Statuses: pending, partially_paid, paid, cancelled (derived, never stored)
Excluded: proforma and cancelled, unless include_proforma / include_cancelled
Billed from: billing_center on every document; ?billing_center_id= on every list
Taxes: up to 3 named slots; tax_names_source = snapshot | derived
Money: strings with two decimals, in the currency the payload names
Dates: invoice_date, credit_note_date, transaction_date are YYYY-MM-DD
Getting help
API keys, permissions, campus restrictions, revocation and the access log are all self-service from Developer Settings in your admin console (avatar menu, top right). For a higher daily limit or anything else, reach the DeskOS team through your usual support channel.
- Email: sales@deskos.net
- Phone: +91 742810 9069