Invoices (AP)
Vendor bills. Coded to cost codes and commitments, checked against remaining balance, held for retainage.
The invoice object
Every object carries an id, timestamps, and an audit_trail. Money is integer cents. Expand any node in the explorer to see how it links to the rest of the record.
- idstringUnique, stable identifier. Prefixed by object type.
- objectstringString naming the object type.
- projectstringProject ID.
- numberstringVendor invoice number.
- vendorstringVendor ID.
- commitmentstringPO or subcontract it bills against.
- amountintegerGross, cents.
- retainage_heldintegerWithheld per commitment terms.
- statusenumreceived · coded · needs_review · approved · paid · void.
- cost_codestringCoded cost code.
- exceptionsarrayChecks that failed, e.g. exceeds_commitment, duplicate, missing_waiver.
- coded_bystringUser or agent action that coded it.
- created_attimestampISO 8601, UTC.
- updated_attimestampISO 8601, UTC. Changes on every write.
- audit_trailstringPath to the append-only history of who changed what, and when.
object{17}
- id"inv_4471"id
- object"invoice"string
- project"prj_24118"id
- number"INV-4471"string
- vendor"vnd_volt_electric"id
- vendor_name"Volt Electric"string
- commitment"cmt_po118"id
- amount4820000integer$48,200.00
- retainage_held482000integer$4,820.00
- status"needs_review"string
- cost_code"cc_260500"id
exceptionsarray[1]
0object{3}
- code"exceeds_commitment"string
- message"Exceeds PO-118 remaining by $6,150.00"string
- amount615000integer$6,150.00
- coded_by"act_8812"id
- due_date"2026-10-25"date
- created_at
- updated_at
- audit_trail"/v1/invoices/inv_4471/audit"string
/v1/invoicesList invoices
Filter by status to build an exceptions queue.
Parameters
- projectstringqueryID of the project, e.g. prj_24118.
- statusenumqueryStatus filter.
- has_exceptionsbooleanqueryOnly invoices with failed checks.
- limitintegerqueryPage size, 1 to 100. Default 25.
- cursorstringqueryCursor from a previous page’s next_cursor.
Returns
A paginated list of invoice objects. Errors use the standard error shape.
curl https://api.os.construction/v1/invoices \
-H "Authorization: Bearer $OS_API_KEY"const res = await fetch("https://api.os.construction/v1/invoices", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.OS_API_KEY}`,
},
});
const page = await res.json();import os, uuid, requests
res = requests.get(
"https://api.os.construction/v1/invoices",
headers={
"Authorization": f"Bearer {os.environ['OS_API_KEY']}",
},
)
res.raise_for_status()
data = res.json(){
"object": "list",
"url": "/v1/invoices",
"has_more": false,
"next_cursor": null,
"data": [
{
"id": "inv_4471",
"object": "invoice",
"project": "prj_24118",
"number": "INV-4471",
"vendor": "vnd_volt_electric",
"vendor_name": "Volt Electric",
"commitment": "cmt_po118",
"amount": 4820000,
"retainage_held": 482000,
"status": "needs_review",
"cost_code": "cc_260500",
"exceptions": [
{
"code": "exceeds_commitment",
"message": "Exceeds PO-118 remaining by $6,150.00",
"amount": 615000
}
],
"coded_by": "act_8812",
"due_date": "2026-10-25",
"created_at": "2026-09-29T13:11:50Z",
"updated_at": "2026-09-29T13:12:04Z",
"audit_trail": "/v1/invoices/inv_4471/audit"
}
]
}/v1/invoices/{id}Retrieve an invoice
Includes exceptions and who coded it.
Parameters
- idstringpathrequiredThe invoice ID, e.g. inv_4471.
Returns
The invoice object. Errors use the standard error shape.
curl https://api.os.construction/v1/invoices/inv_4471 \
-H "Authorization: Bearer $OS_API_KEY"const res = await fetch("https://api.os.construction/v1/invoices/inv_4471", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.OS_API_KEY}`,
},
});
const obj = await res.json();import os, uuid, requests
res = requests.get(
"https://api.os.construction/v1/invoices/inv_4471",
headers={
"Authorization": f"Bearer {os.environ['OS_API_KEY']}",
},
)
res.raise_for_status()
data = res.json(){
"id": "inv_4471",
"object": "invoice",
"project": "prj_24118",
"number": "INV-4471",
"vendor": "vnd_volt_electric",
"vendor_name": "Volt Electric",
"commitment": "cmt_po118",
"amount": 4820000,
"retainage_held": 482000,
"status": "needs_review",
"cost_code": "cc_260500",
"exceptions": [
{
"code": "exceeds_commitment",
"message": "Exceeds PO-118 remaining by $6,150.00",
"amount": 615000
}
],
"coded_by": "act_8812",
"due_date": "2026-10-25",
"created_at": "2026-09-29T13:11:50Z",
"updated_at": "2026-09-29T13:12:04Z",
"audit_trail": "/v1/invoices/inv_4471/audit"
}/v1/invoicesCreate an invoice
Usually from email capture; also open for your scanner or vendor portal.
Idempotent. Send an Idempotency-Key; retries within 24 hours return the original result and never write twice.
Parameters
- Idempotency-KeystringheaderrequiredAny unique string. Replays return the first result instead of writing twice.
- projectstringbodyrequiredProject ID.
- vendorstringbodyrequiredVendor ID.
- numberstringbodyrequiredInvoice number.
- amountintegerbodyrequiredCents.
- filestringbodyUploaded PDF file ID.
Returns
The invoice object. Errors use the standard error shape.
curl -X POST https://api.os.construction/v1/invoices \
-H "Authorization: Bearer $OS_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"project": "prj_24118",
"vendor": "vnd_volt_electric",
"number": "INV-4471",
"amount": 4820000,
"file": "file_inv4471"
}'const res = await fetch("https://api.os.construction/v1/invoices", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.OS_API_KEY}`,
"Idempotency-Key": crypto.randomUUID(),
"Content-Type": "application/json",
},
body: JSON.stringify({
"project": "prj_24118",
"vendor": "vnd_volt_electric",
"number": "INV-4471",
"amount": 4820000,
"file": "file_inv4471"
}),
});
const obj = await res.json();import os, uuid, requests
res = requests.post(
"https://api.os.construction/v1/invoices",
headers={
"Authorization": f"Bearer {os.environ['OS_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"project": "prj_24118",
"vendor": "vnd_volt_electric",
"number": "INV-4471",
"amount": 4820000,
"file": "file_inv4471"
},
)
res.raise_for_status()
data = res.json(){
"id": "inv_4471",
"object": "invoice",
"project": "prj_24118",
"number": "INV-4471",
"vendor": "vnd_volt_electric",
"vendor_name": "Volt Electric",
"commitment": "cmt_po118",
"amount": 4820000,
"retainage_held": 482000,
"status": "received",
"cost_code": null,
"exceptions": [],
"coded_by": null,
"due_date": "2026-10-25",
"created_at": "2026-09-29T13:11:50Z",
"updated_at": "2026-09-29T13:12:04Z",
"audit_trail": "/v1/invoices/inv_4471/audit"
}/v1/invoices/{id}Code an invoice
Set cost code and commitment. Checks rerun on every write.
Idempotent. Send an Idempotency-Key; retries within 24 hours return the original result and never write twice.
Parameters
- idstringpathrequiredThe invoice ID, e.g. inv_4471.
- Idempotency-KeystringheaderrequiredAny unique string. Replays return the first result instead of writing twice.
- cost_codestringbodyCost code ID.
- commitmentstringbodyCommitment ID.
Returns
The invoice object. Errors use the standard error shape.
curl -X PATCH https://api.os.construction/v1/invoices/inv_4471 \
-H "Authorization: Bearer $OS_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"cost_code": "cc_260500",
"commitment": "cmt_po118"
}'const res = await fetch("https://api.os.construction/v1/invoices/inv_4471", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.OS_API_KEY}`,
"Idempotency-Key": crypto.randomUUID(),
"Content-Type": "application/json",
},
body: JSON.stringify({
"cost_code": "cc_260500",
"commitment": "cmt_po118"
}),
});
const obj = await res.json();import os, uuid, requests
res = requests.patch(
"https://api.os.construction/v1/invoices/inv_4471",
headers={
"Authorization": f"Bearer {os.environ['OS_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"cost_code": "cc_260500",
"commitment": "cmt_po118"
},
)
res.raise_for_status()
data = res.json(){
"id": "inv_4471",
"object": "invoice",
"project": "prj_24118",
"number": "INV-4471",
"vendor": "vnd_volt_electric",
"vendor_name": "Volt Electric",
"commitment": "cmt_po118",
"amount": 4820000,
"retainage_held": 482000,
"status": "needs_review",
"cost_code": "cc_260500",
"exceptions": [
{
"code": "exceeds_commitment",
"message": "Exceeds PO-118 remaining by $6,150.00",
"amount": 615000
}
],
"coded_by": "act_8812",
"due_date": "2026-10-25",
"created_at": "2026-09-29T13:11:50Z",
"updated_at": "2026-09-29T13:12:04Z",
"audit_trail": "/v1/invoices/inv_4471/audit"
}Events
Webhooks fire on every state change of a invoice.