Creates a purchase order.
POST /accounting/purchase-orders
Group: Accounting › Purchase Orders · Operation ID: createPurchaseOrder
The response contains only the new order's id. Read the record with GET accounting/purchase-orders/:id if you need its stored state, where poNumber appears once assigned. At least one line is required. A line is either an item line or an expense line, and isItem says which: an item line is priced by quantity times price and needs itemId and unitOfMeasureId, an expense line is priced by editableAmount and needs an accountId. currency governs every amount, which are decimals with no currency of their own. The order is created pendingApproval and then put through your tenant's approval policy, which may approve it at once when no approvers are configured, so do not assume a created order is still awaiting approval.
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. |
Idempotency-Key |
header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with 400 IDEMPOTENCY_NOT_SUPPORTED rather than silently ignored. |
Request body
Required, application/json: createPurchaseOrderInput.
| Field | Type | Required | Description |
|---|---|---|---|
entityId |
string | yes | The legal entity that owns the order. See id on GET base/entities. |
vendorId |
string | yes | The vendor the order is placed with. See id on GET accounting/vendors/:id. |
currency |
string | yes | The currency of the order. Format ISO 4217, for example 'USD'. Governs every line amount. |
description |
string | no | The description of the order, shown to the vendor. |
internalDescription |
string | no | An internal description of the order, not shown to the vendor. |
externalMemo |
string | no | A memo printed on vendor-facing documents. |
externalPONumber |
string | no | The reference number this order has in the upstream system. |
paymentTerms |
string | no | The payment-terms code agreed with the vendor, for example 'net30'. |
startDate |
string (date) | no | The date the order starts. Use format YYYY-MM-DD. |
endDate |
string (date) | no | The date the order ends. Use format YYYY-MM-DD. |
needByDate |
string (date) | no | The date the goods or services are required by. Use format YYYY-MM-DD. |
requestedById |
string | no | The employee who requested the order. There is no endpoint for it. |
lines |
array of object | yes | The lines this order places. At least one is required. |
lines[].isItem |
boolean | yes | Whether this is an item line. True for an item line, priced by quantity times price. False for an expense line, priced by editableAmount. |
lines[].description |
string | no | A description of what is being ordered. |
lines[].itemId |
string | no | The item being ordered. Expected on an item line. |
lines[].accountId |
string | no | The ledger account the line is booked to. See id on GET accounting/accounts/:id. Expected on an expense line. |
lines[].unitOfMeasureId |
string | no | The unit the quantity is expressed in. Expected on an item line. |
lines[].departmentId |
string | no | The department the cost is allocated to. See id on GET departments/:id. |
lines[].quantity |
string (decimal) | no | The ordered quantity, as a decimal. Expected on an item line. |
lines[].price |
string (decimal) | no | The unit price, in the order currency, as a decimal. Expected on an item line. |
lines[].editableAmount |
string (decimal) | no | The manually entered line amount, in the order currency, as a decimal. Expected on an expense line. |
lines[].startDate |
string (date) | no | The date this line starts. Use format YYYY-MM-DD. |
lines[].endDate |
string (date) | no | The date this line ends. Use format YYYY-MM-DD. |
lines[].estimatedDeliveryDate |
string (date) | no | The expected delivery date for this line. Use format YYYY-MM-DD. |
attachments |
array of object | no | Files to attach to the order. 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:
{
"entityId": "8f2c1d40-5a6b-4c7d-9e80-1f2a3b4c5d6e",
"vendorId": "3b7d5e9c-1a24-4f80-9c6d-2e8f70b13a45",
"currency": "USD",
"description": "Q4 office supplies",
"internalDescription": "<internalDescription>",
"externalMemo": "<externalMemo>",
"externalPONumber": "<externalPONumber>",
"paymentTerms": "<paymentTerms>",
"startDate": "2026-01-15",
"endDate": "2026-01-15",
"needByDate": "2026-10-15",
"requestedById": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"lines": [
{
"isItem": false,
"description": "Stationery",
"editableAmount": "450.00"
}
],
"attachments": [
{
"fileName": "<fileName>",
"fileId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
]
}
Response
200 Success
Under data (createPurchaseOrderOutput):
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | yes | The unique identifier for the created purchase order. |
Response headers
Idempotency-Replayed— Present andtrueonly when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent asfalse.
Errors
| Code | Status |
|---|---|
REFERENCE_NOT_FOUND |
400 |
TOO_MANY_LINES |
400 |
IDEMPOTENCY_KEY_CONFLICT |
409 |
IDEMPOTENCY_PAYLOAD_MISMATCH |
422 |
Statuses
400— Invalid request or business failure. Codes: REFERENCE_NOT_FOUND, TOO_MANY_LINES401— Missing or invalid access token403— Not authorized404— Not found409— The Idempotency-Key is still being processed by another request.422— The Idempotency-Key was already used with a different request payload.429— Too many requests500— Internal server error