Billing and invoicing
Read invoices and line items via the Billing API.
The Billing API exposes invoices and line items. Use it to surface billing history inside your dashboard, reconcile usage against carrier records, or pull invoice data into accounting software, BI pipelines, or any external system that needs structured invoice records.
Resource model
A customer has one billing profile. The profile produces an invoice each billing period. Each invoice contains line items — one row per shipment charge — that sum to the invoice total.
customer → billing profile → invoice → line itemEndpoints
GET /billing/invoices— paginated list, withstatusandsearchfilters.GET /billing/invoices/{id}— single invoice with up to 250 line items inline.GET /billing/invoices/{id}/line_items— paginated line items beyond the inline cap.
All endpoints authenticate with Authorization: Bearer <your_token>. See Authentication.
The flow
The list call enumerates invoices. The show call returns one invoice with up to 250 line items inline. When line_items_has_next_page is true, you fall through to the paginated line_items endpoint to fetch the rest.
Step 1: List invoices
Default page size is 50, sorted newest first. Filter by status (DRAFT, OPEN, PAID, UNCOLLECTIBLE, VOID) or search by invoice number.
Endpoint: GET https://api.parsel.app/billing/invoices
curl "https://api.parsel.app/billing/invoices?status=OPEN&page=1&page_size=50" \
-H "Authorization: Bearer <your_token>"const url = new URL("https://api.parsel.app/billing/invoices");
url.searchParams.set("status", "OPEN");
url.searchParams.set("page", "1");
url.searchParams.set("page_size", "50");
const res = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.PARSEL_TOKEN}` },
});
if (!res.ok) throw new Error(`Parsel API ${res.status}`);
const { data: invoices, metadata } = await res.json();import os
import requests
resp = requests.get(
"https://api.parsel.app/billing/invoices",
headers={"Authorization": f"Bearer {os.environ['PARSEL_TOKEN']}"},
params={"status": "OPEN", "page": 1, "page_size": 50},
)
resp.raise_for_status()
body = resp.json()
invoices, metadata = body["data"], body["metadata"]The response carries the rows in data and the page state in metadata. Three example invoices below — one OPEN, one PAID, one DRAFT — show the shape variation:
{
"data": [
{
"id": "0a6bb80e-3d62-4663-b82f-11840b83850b",
"status": "OPEN",
"currency": "USD",
"invoice_number": "INV-VXW8LQ-20260401-20260415",
"period_start": "2026-04-01",
"period_end": "2026-04-15",
"total_cents": 38370,
"invoice_date": "2026-04-14",
"due_date": "2026-05-15",
"pdf_url": "https://storage.googleapis.com/parsel-billing-documents/invoices/0a6bb80e-3d62-4663-b82f-11840b83850b/INV-VXW8LQ-20260401-20260415.pdf",
"csv_url": "https://storage.googleapis.com/parsel-billing-documents/invoices/0a6bb80e-3d62-4663-b82f-11840b83850b/INV-VXW8LQ-20260401-20260415.csv"
},
{
"id": "2f9efa7e-f203-4f74-90f8-be4bb4751ad1",
"status": "PAID",
"currency": "USD",
"invoice_number": "20260314-00006",
"period_start": "2026-03-20",
"period_end": "2026-03-27",
"total_cents": 2815,
"invoice_date": "2026-03-27",
"due_date": "2026-04-03",
"pdf_url": "https://storage.googleapis.com/parsel-invoices/seed/tsn-20260314.pdf",
"csv_url": "https://storage.googleapis.com/parsel-invoices/seed/tsn-20260314.csv"
},
{
"id": "f5cd5971-596e-4ff9-af1e-f0f43622a822",
"status": "DRAFT",
"currency": "USD",
"invoice_number": "INV-1FVNN7U-20260401-20260415",
"period_start": "2026-04-01",
"period_end": "2026-04-15",
"total_cents": 0,
"invoice_date": "2026-04-14",
"due_date": "2026-04-29",
"pdf_url": null,
"csv_url": null
}
],
"metadata": {
"current_page": 1,
"page_size": 50,
"total_count": 14,
"total_pages": 1
}
}pdf_url and csv_url are null for DRAFT invoices that haven't been finalized — gate any document fetch on a non-null value.
To page through every invoice, increment page and re-fetch until current_page === total_pages. See Pagination for the full envelope contract.
Step 2: Fetch one invoice
Get the full invoice with line items inline.
Endpoint: GET https://api.parsel.app/billing/invoices/{id}
curl https://api.parsel.app/billing/invoices/0a6bb80e-3d62-4663-b82f-11840b83850b \
-H "Authorization: Bearer <your_token>"async function fetchInvoice(id: string) {
const res = await fetch(
`https://api.parsel.app/billing/invoices/${id}`,
{ headers: { Authorization: `Bearer ${process.env.PARSEL_TOKEN}` } }
);
if (res.status === 404) return null;
if (!res.ok) throw new Error(`Parsel API ${res.status}`);
const { data } = await res.json();
return data;
}def fetch_invoice(invoice_id: str):
resp = requests.get(
f"https://api.parsel.app/billing/invoices/{invoice_id}",
headers={"Authorization": f"Bearer {os.environ['PARSEL_TOKEN']}"},
)
if resp.status_code == 404:
return None
resp.raise_for_status()
return resp.json()["data"]The single-resource envelope wraps the invoice in data. The line_items array carries up to 250 items inline; the line_items_has_next_page flag tells you whether the cap was hit. Three of the 35 line items on this invoice shown below:
{
"data": {
"id": "0a6bb80e-3d62-4663-b82f-11840b83850b",
"status": "OPEN",
"currency": "USD",
"invoice_number": "INV-VXW8LQ-20260401-20260415",
"period_start": "2026-04-01",
"period_end": "2026-04-15",
"total_cents": 38370,
"invoice_date": "2026-04-14",
"due_date": "2026-05-15",
"pdf_url": "https://storage.googleapis.com/parsel-billing-documents/invoices/0a6bb80e-3d62-4663-b82f-11840b83850b/INV-VXW8LQ-20260401-20260415.pdf",
"csv_url": "https://storage.googleapis.com/parsel-billing-documents/invoices/0a6bb80e-3d62-4663-b82f-11840b83850b/INV-VXW8LQ-20260401-20260415.csv",
"line_items": [
{
"carrier": "OnTrac",
"tracking_code": "C39345589343",
"description": null,
"postage_cents": 1080,
"surcharges_cents": 420,
"total_cents": 1500,
"created_at": "2026-04-14T12:05:29.738121Z"
},
{
"carrier": "OnTrac",
"tracking_code": "C94022731930",
"description": null,
"postage_cents": 605,
"surcharges_cents": 145,
"total_cents": 750,
"created_at": "2026-04-14T12:05:29.761074Z"
},
{
"carrier": "OnTrac",
"tracking_code": "C79880094126",
"description": null,
"postage_cents": 640,
"surcharges_cents": 40,
"total_cents": 680,
"created_at": "2026-04-14T12:05:29.776903Z"
}
],
"line_items_has_next_page": false
}
}Step 3: Handle invoices with more than 250 line items
When line_items_has_next_page is true, only the first 250 items are inline. Fetch the remainder from the paginated endpoint.
Endpoint: GET https://api.parsel.app/billing/invoices/{id}/line_items
curl "https://api.parsel.app/billing/invoices/0a6bb80e-3d62-4663-b82f-11840b83850b/line_items?page=1&page_size=100" \
-H "Authorization: Bearer <your_token>"async function fetchAllLineItems(invoiceId: string) {
const items = [];
let page = 1;
while (true) {
const url = new URL(
`https://api.parsel.app/billing/invoices/${invoiceId}/line_items`
);
url.searchParams.set("page", String(page));
url.searchParams.set("page_size", "100");
const res = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.PARSEL_TOKEN}` },
});
if (!res.ok) throw new Error(`Parsel API ${res.status}`);
const { data, metadata } = await res.json();
items.push(...data);
if (metadata.current_page >= metadata.total_pages) break;
page += 1;
}
return items;
}def fetch_all_line_items(invoice_id: str):
items = []
page = 1
while True:
resp = requests.get(
f"https://api.parsel.app/billing/invoices/{invoice_id}/line_items",
headers={"Authorization": f"Bearer {os.environ['PARSEL_TOKEN']}"},
params={"page": page, "page_size": 100},
)
resp.raise_for_status()
body = resp.json()
items.extend(body["data"])
if body["metadata"]["current_page"] >= body["metadata"]["total_pages"]:
break
page += 1
return itemsThe paginated endpoint returns the standard list envelope. metadata.total_count reflects the true line-item count (excluding soft-deleted items, so totals match what customers see on their invoice PDFs).
{
"data": [
{
"carrier": "OnTrac",
"tracking_code": "C39345589343",
"description": null,
"postage_cents": 1080,
"surcharges_cents": 420,
"total_cents": 1500,
"created_at": "2026-04-14T12:05:29.738121Z"
}
],
"metadata": {
"current_page": 1,
"page_size": 10,
"total_count": 35,
"total_pages": 4
}
}For very large invoices, fetch the inline 250 first to start populating downstream tables, then page through the remainder in the background.