> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stateset.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Procure to pay

> Supplier, purchase order, receipt, three-way match, payment run — the spend path with the control points that make it auditable.

# Procure to pay

Spending money safely is a sequence of *matches*: the PO says what you agreed to buy, the receipt
says what arrived, the bill says what the supplier wants — and payment happens only where all three
agree. The API models each step explicitly; this guide connects them.

## 1 — Supplier and purchase order

```bash theme={null}
curl -X POST https://api.stateset.com/api/v1/purchase-orders \
  -H "Authorization: Bearer $STATESET_API_KEY" \
  -H 'content-type: application/json' \
  -d '{
    "supplier_id": "sup_88",
    "line_items": [{ "sku": "SHOE-RED-10", "quantity": 500, "unit_cost": "21.40" }]
  }'
```

The PO lifecycle is deliberate: `submit → approve → send → acknowledge`. Each is its own call
(`POST /api/v1/purchase-orders/{id}/submit`, `/approve`, `/send`, `/acknowledge`) because each is a
different actor — requester, approver, you-to-supplier, supplier-to-you. `hold` and `cancel` exist
at every stage before completion.

<Tip>
  `acknowledge` is the step teams drop, and it's the one that converts "we sent a PO" into "the
  supplier has committed". An unacknowledged PO past its expected-ack window is a supply risk you can
  query for, not a surprise three weeks later.
</Tip>

## 2 — The goods arrive

Receiving has its own workflow — [receiving and put-away](/guides/commerce/receiving-and-putaway)
covers it fully. The short version: an inbound shipment arrives, a receipt records what was
actually in the boxes, and `POST /api/v1/purchase-orders/{id}/receive` ties the received quantities
back to the PO.

Received ≠ ordered is normal, not exceptional: short ships, overages, damage. The receipt captures
reality; the PO keeps the agreement. Keeping them separate is what makes step 4 possible.

## 3 — The bill arrives

```bash theme={null}
curl -X POST https://api.stateset.com/api/v1/ap/bills \
  -H "Authorization: Bearer $STATESET_API_KEY" \
  -H 'content-type: application/json' \
  -d '{ "supplier_id": "sup_88", "purchase_order_id": "po_301", "amount": "10700.00", "due_date": "2026-08-25" }'
```

## 4 — Three-way match

```bash theme={null}
curl https://api.stateset.com/api/v1/ap/bills/{id}/three-way-match \
  -H "Authorization: Bearer $STATESET_API_KEY"
```

The engine compares **PO ↔ receipt ↔ bill**. Then branch:

| Match result               | Action                                                                 |
| -------------------------- | ---------------------------------------------------------------------- |
| All three agree            | `POST /api/v1/ap/bills/{id}/approve`                                   |
| Bill exceeds receipt or PO | `POST /api/v1/ap/bills/{id}/dispute` — with the mismatch as the reason |
| Bill wrong entirely        | `POST /api/v1/ap/bills/{id}/cancel`                                    |

<Warning>
  The match is the control that stops the classic fraud and the classic mistake alike: paying for
  quantity never received, or paying a price never agreed. **Approve from the match result, not from
  the PDF.** A disputed bill blocks payment until resolved — that is the point, not an inconvenience
  to route around.
</Warning>

## 5 — Pay in runs, not one-offs

```bash theme={null}
curl -X POST https://api.stateset.com/api/v1/ap/payment-runs \
  -H "Authorization: Bearer $STATESET_API_KEY" \
  -H 'content-type: application/json' \
  -d '{ "due_before": "2026-08-01" }'

curl -X POST https://api.stateset.com/api/v1/ap/payment-runs/{id}/approve -H "Authorization: Bearer $STATESET_API_KEY"
curl -X POST https://api.stateset.com/api/v1/ap/payment-runs/{id}/process -H "Authorization: Bearer $STATESET_API_KEY"
```

A run gathers approved bills due in a window into one reviewable, approvable batch — one look at
total cash out instead of forty individual decisions. `cancel` works until `process`; after that,
a wrong payment is `POST /api/v1/ap/payments/{id}/void` and a corrected bill, not an edit.

## Watching the whole pipe

```bash theme={null}
curl "https://api.stateset.com/api/v1/ap/aging" -H "Authorization: Bearer $STATESET_API_KEY"
```

AP aging is the mirror of [AR aging](/guides/commerce/ar-collections): what you owe, by how overdue.
The useful discipline is watching *disputed* bills' age separately — an old dispute is money you
probably owe and a supplier relationship quietly degrading.

## Related

* [Receiving and put-away](/guides/commerce/receiving-and-putaway) — step 2 in full
* [Period close](/guides/commerce/period-close) — where these entries get locked
* [Vendor returns](/guides/commerce/returns-and-rtv#return-to-vendor) — when the goods go back
