Creates a vendor bill.
POST /accounting/vendor-bills
Group: Accounting › Vendor Bills · Operation ID: createVendorBill
The response contains only the new bill's id. Read the record with GET accounting/vendor-bills/:id if you need its stored state, where vendorBillNumber appears once assigned. The bill is always created pendingApproval, so a status cannot be supplied here. currency governs every line amount and vatAmount, which are decimals with no currency of their own. vatPercentage is a percentage, so 19 means 19%, not 0.19. A bill with more lines than your tenant runs in one call is refused with TOO_MANY_LINES, so split it or load it through the integration platform.
Open in the interactive reference
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
Api-Version |
header | "v1" |
yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version v1. |
Request body
Required, application/json: createVendorBillInput.
| Field | Type | Required | Description |
|---|---|---|---|
vendorId |
string | yes | The vendor the bill is from. See id on GET accounting/vendors/:id. |
entityId |
string | yes | The legal entity the bill is posted under. See id on GET base/entities. |
billNumber |
string | yes | The vendor-issued invoice number, as printed on the bill. It is not unique across vendors. |
billDate |
string (date) | yes | The date printed on the vendor's invoice. Use format YYYY-MM-DD. |
dueDate |
string (date) | yes | The date payment is due. Use format YYYY-MM-DD. |
currency |
string | yes | The currency of the bill. Format ISO 4217, for example 'USD'. Governs every line amount and vatAmount, which are decimals. |
serviceDate |
string (date) | no | The date the goods or services were delivered. Use format YYYY-MM-DD. Omit to leave it unset. For an entity whose country requires a service date it then defaults to billDate. |
paymentTerms |
string | no | The payment-terms key, for example 'net30', 'net60' or 'dueOnReceipt'. The API does not check it against a list. Omit to inherit 'custom'. |
externalVendorBillId |
string | no | The identifier this bill has in the upstream system that produced it. It is not unique. |
markAsPaid |
boolean | no | Whether to mark the bill paid on creation. Defaults to false. |
origin |
string | no | The source system this record came from, as free-form text, for example 'Ramp'. Omit it and it stays unset. The server does not set a value. |
lines |
array of object | yes | The line items of the bill, in the order they should appear. At least one is expected. A bill with no lines asks for no expense. |
lines[].amount |
string (decimal) | yes | The total line amount, gross, including VAT where applicable, as a decimal in the bill currency. |
lines[].description |
string | no | A description of the line item, typically copied from the vendor's invoice. |
lines[].accountId |
string | no | The GL expense account this line posts to. See id on GET accounting/accounts/:id. |
lines[].departmentId |
string | no | The department this line is charged to. See id on GET departments/:id. |
lines[].businessUnitId |
string | no | The business unit this line is charged to. There is no endpoint for it. |
lines[].customerId |
string | no | The customer this expense is rebilled to. See id on GET accounting/customers/:id. It is only meaningful alongside billableExpense. |
lines[].vatPercentage |
string (decimal) | no | The VAT rate applied, as a percentage. 19 means 19%, not 0.19. |
lines[].vatAccountId |
string | no | The GL account the VAT posts to. See id on GET accounting/accounts/:id. |
lines[].reverseVatAccountId |
string | no | The GL account reverse-charge VAT posts to. See id on GET accounting/accounts/:id. |
lines[].vatAmount |
string (decimal) | no | The explicit VAT amount, as a decimal in the bill currency. Omit to derive it from vatPercentage. |
lines[].valueAllocationId |
string | no | The value-allocation rule distributing this line across dimensions. There is no endpoint for it. |
lines[].purchaseOrderLineId |
string | no | The purchase-order line this line is matched against. There is no endpoint for it. |
lines[].billableExpense |
string | no | The billable-expense status for this line. Values include 'yes', 'no' and 'skipped'. |
lines[].lineType |
string | no | The kind of line. Values include 'expense' (a one-time cost) and 'prepaidExpense' (an upfront cost spread over several periods). Defaults to 'expense'. |
attachments |
array of object | no | Files to attach to the bill. Each names an already-uploaded file by its id. |
attachments[].fileName |
string | yes | The display name of the attached file. |
attachments[].fileId |
string | yes | The identifier of the already-uploaded file. There is no endpoint for it. |
Example:
{
"vendorId": "3b7d5e9c-1a24-4f80-9c6d-2e8f70b13a45",
"entityId": "8f2c1d40-5a6b-4c7d-9e80-1f2a3b4c5d6e",
"billNumber": "INV-88213",
"billDate": "2026-08-01",
"dueDate": "2026-08-31",
"currency": "EUR",
"serviceDate": "2026-01-15",
"paymentTerms": "<paymentTerms>",
"externalVendorBillId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"markAsPaid": true,
"origin": "<origin>",
"lines": [
{
"amount": "1200.00",
"description": "Consulting, August",
"vatPercentage": "19.00"
}
],
"attachments": [
{
"fileName": "<fileName>",
"fileId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
]
}
Response
200 Success
Under data (createVendorBillOutput):
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | yes | The unique identifier for the created vendor bill. |
Errors
| Code | Status |
|---|---|
REFERENCE_NOT_FOUND |
400 |
ACCOUNT_ENTITY_MISMATCH |
400 |
TOO_MANY_LINES |
400 |
IDEMPOTENCY_NOT_SUPPORTED |
400 |
Statuses
400— Invalid request or business failure. Codes: REFERENCE_NOT_FOUND, ACCOUNT_ENTITY_MISMATCH, TOO_MANY_LINES. Sending an Idempotency-Key is answered withIDEMPOTENCY_NOT_SUPPORTED: this action does not run in its own transaction, so the replay guarantee cannot be offered and the header is refused rather than ignored.401— Missing or invalid access token403— Not authorized404— Not found429— Too many requests500— Internal server error