A purchase order is raised against a supplier for goods or services you're buying. It moves through a fixed lifecycle: draft → sent → fulfilled, with cancel available from draft or sent. A fulfilled purchase order can be converted into an expense so it shows up in your reporting.
Status values
A purchase order is always in exactly one of these statuses:
| Status | Description |
|---|---|
draft | Created but not yet sent to the supplier. Fully editable; unnumbered |
sent | Sent to the supplier. Supplier details are frozen; can be fulfilled or cancelled |
fulfilled | Goods or services have been received. Can be converted to an expense |
cancelled | Cancelled; no further transitions |
Numbering
A new purchase order is created as a draft with no number, so an abandoned draft never consumes one and leaves a gap in the sequence. A number in the form PO-0001 is allocated once, the first time it leaves draft (i.e. on send), drawn from a single account-wide counter (unlike invoices, purchase orders have no per-supplier number prefix). Re-sending an already-sent purchase order never allocates a second number.
List purchase orders
GET /purchase_order
Returns purchase orders for your account.
| Query parameter | Type | Description |
|---|---|---|
| status | string | Filter to one status: draft · sent · fulfilled · cancelled |
curl "https://api.rebill.co.za/purchase_order?status=sent" \ -H "Authorization: Bearer sk_your_secret_key"
Get a purchase order
GET /purchase_order/:id
Returns a single purchase order by ID.
Purchase order object
| Field | Type | Description |
|---|---|---|
| id | string | Unique identifier |
| public_id | string | Short ID used in the public, unauthenticated supplier-facing link |
| number | string | Sequence number in the form PO-0001; empty while still a draft |
| status | string | draft · sent · fulfilled · cancelled |
| supplier_id | string | ID of the Supplier this purchase order was raised against |
| supplier_name | string | Supplier name, frozen at send time |
| supplier_email | string | Supplier email, frozen at send time |
| supplier_address | string | Supplier address, frozen at send time |
| currency | string | ISO 4217 currency code |
| amount | integer | Total in cents, computed from items (including any per-line discounts) |
| po_date | timestamp | Date on the purchase order |
| delivery_date | string | Expected delivery date (empty if unset) |
| items | array | Line items (same structure as invoice items) |
| notes | string | Free-text notes |
| expense_id | string | ID of the Expense this purchase order was converted to, if any |
| created | timestamp | When the purchase order was created |
Create a purchase order
POST /purchase_order
Creates a new draft purchase order against a supplier. The supplier must exist and must not be archived.
| Field | Required | Type | Description |
|---|---|---|---|
| supplier_id | Yes | string | ID of an existing, non-archived supplier |
| po_date | Yes | date | Date on the purchase order (YYYY-MM-DD) |
| items | Yes | array | At least one line item (same structure as invoice items) |
| delivery_date | No | date | Expected delivery date |
| currency | No | string | ISO 4217 code; defaults to your account currency |
| notes | No | string | Free-text notes |
curl -X POST https://api.rebill.co.za/purchase_order \
-H "Authorization: Bearer sk_your_secret_key" \
-H "Content-Type: application/json" \
-d '{
"supplier_id": "sup_001",
"po_date": "2026-03-20",
"items": [
{
"name": "Cement (50kg bags)",
"quantity": 20,
"price": 15000,
"vat_type": "standard"
}
],
"notes": "Deliver to site office"
}'Response (201 Created):
{
"id": "po_001"
}Update a purchase order
PUT /purchase_order/:id
Replaces the editable fields on a purchase order. Accepts the same fields as create. Only draft purchase orders can be updated; any other status returns 422 Unprocessable.
Delete a purchase order
DELETE /purchase_order/:id
Permanently deletes a purchase order. Only draft purchase orders can be deleted; any other status returns 422 Unprocessable.
curl -X DELETE https://api.rebill.co.za/purchase_order/po_001 \ -H "Authorization: Bearer sk_your_secret_key"
Send a purchase order
POST /purchase_order/:id/send
Moves a draft purchase order to sent, allocates its number, freezes the supplier snapshot fields, and emails a PDF to the supplier. The supplier must have an email address, or this returns 422 Unprocessable before anything is consumed (no number is allocated on a doomed send). Only draft purchase orders can be sent.
| Field | Required | Type | Description |
|---|---|---|---|
| pdf_base64 | No | string | Base64-encoded PDF to attach to the email. Omit to send without a PDF attachment |
| skip_email | No | boolean | Commit the numbering/status transition without emailing the supplier (default: false). Follow up with POST /purchase_order/:id/send_email once you have rendered a PDF from the now-numbered purchase order |
curl -X POST https://api.rebill.co.za/purchase_order/po_001/send \ -H "Authorization: Bearer sk_your_secret_key"
Send the purchase order email
POST /purchase_order/:id/send_email
Delivers the "purchase order sent" email for a purchase order that was already moved to sent via send with skip_email: true. Only a purchase order in sent status can use this endpoint. Accepts the same optional pdf_base64 field as send.
Resend the purchase order email
POST /purchase_order/:id/resend_email
Resends the email for a sent or fulfilled purchase order using its current snapshot fields and the supplier's current email address (not the frozen send-time snapshot). No status change, no renumbering. Accepts the same optional pdf_base64 field as send.
curl -X POST https://api.rebill.co.za/purchase_order/po_001/resend_email \ -H "Authorization: Bearer sk_your_secret_key"
Fulfil a purchase order
POST /purchase_order/:id/fulfil
Marks a sent purchase order as fulfilled once goods or services have been received. Only sent purchase orders can be fulfilled.
curl -X POST https://api.rebill.co.za/purchase_order/po_001/fulfil \ -H "Authorization: Bearer sk_your_secret_key"
Cancel a purchase order
POST /purchase_order/:id/cancel
Marks a draft or sent purchase order as cancelled. Fulfilled and already-cancelled purchase orders cannot be cancelled.
curl -X POST https://api.rebill.co.za/purchase_order/po_001/cancel \ -H "Authorization: Bearer sk_your_secret_key"
Convert to an expense
POST /purchase_order/:id/convert_expense
Turns a fulfilled purchase order into an Expense, pre-filled from the purchase order's frozen supplier snapshot, currency, and amount. Only fulfilled purchase orders can be converted, and only once: a purchase order that has already been converted returns 409 Conflict.
| Field | Required | Type | Description |
|---|---|---|---|
| category_id | Yes | string | ID of an existing expense category |
| payment_method | Yes | string | cash, card, bank_transfer, or other |
| amount | No | integer | Amount in cents to record on the expense; defaults to the purchase order's amount |
| date | No | date | Expense date (YYYY-MM-DD); defaults to today |
curl -X POST https://api.rebill.co.za/purchase_order/po_001/convert_expense \
-H "Authorization: Bearer sk_your_secret_key" \
-H "Content-Type: application/json" \
-d '{
"category_id": "cat-002",
"payment_method": "bank_transfer"
}'Response (201 Created):
{
"expense_id": "exp-045"
}The created expense's supplier_id and purchase_order_id fields link it back to this purchase order and its supplier; its notes are set to "From purchase order PO-0001".
List notifications for a purchase order
GET /purchase_order/:id/notification
Returns the delivery history (email sends) recorded for this purchase order, alongside delivery/engagement state for tracked emails.
curl https://api.rebill.co.za/purchase_order/po_001/notification \ -H "Authorization: Bearer sk_your_secret_key"