Documentation Index Fetch the complete documentation index at: https://docs.arcuserp.com/llms.txt
Use this file to discover all available pages before exploring further.
What this guide covers
The purchase-to-pay (P2P) workflow moves goods and money through four stages:
Purchase order — commit to buying from a vendor
Goods receipt — receive the goods into inventory
Vendor bill — record the vendor’s invoice and clear the GRNI accrual
AP payment — pay the bill and close the liability
Each stage is a separate API call. GL entries post automatically at the right stage. No manual journal entries are needed for the standard flow.
Prerequisites
API key with scopes: purchasing:write, purchasing:read, accounting:write
An active vendor account (account_type: vendor)
One or more product IDs to order
A warehouse location ID
Stage 1: Create the purchase order
POST /v1/orders with document_type: "purchase_order". The order is created in draft status. No GL entry fires at this stage.
curl
TypeScript SDK
Python SDK
curl -s -X POST "https://api.arcuserp.com/v1/orders" \
-H "Authorization: Bearer $ARCUS_API_KEY " \
-H "Content-Type: application/json" \
-H "Idempotency-Key: po-acme-2026-05-12-001" \
-d '{
"document_type": "purchase_order",
"account_id": "acct_acme_vendor_uuid",
"location_id": "loc_main_warehouse_uuid",
"payment_terms": "Net30",
"line_items": [
{
"product_id": "prod_dome_riser_uuid",
"quantity": 24,
"cost_rate": 18.00,
"notes": "Dome Riser Kit - Q2 restock"
},
{
"product_id": "prod_flat_riser_uuid",
"quantity": 12,
"cost_rate": 15.00
}
]
}'
Save po.data.id (the PO UUID). You will reference it in later stages.
Confirm the PO (open it)
Once reviewed, transition to open status, which makes it eligible for receiving:
curl -s -X PATCH "https://api.arcuserp.com/v1/orders/ $PO_ID " \
-H "Authorization: Bearer $ARCUS_API_KEY " \
-H "Content-Type: application/json" \
-d '{"status": "open"}'
Stage 2: Receive goods
POST /v1/orders/{po_id}/receive records the goods arriving at your warehouse.
GL entries that fire at this stage:
Account Debit Credit Inventory (Finished Goods) unit_cost x qty_received GRNI (Goods Received Not Invoiced) unit_cost x qty_received
GRNI is a liability accrual that stays open until you post the vendor bill in Stage 3.
curl
TypeScript SDK
Python SDK
curl -s -X POST "https://api.arcuserp.com/v1/orders/ $PO_ID /receive" \
-H "Authorization: Bearer $ARCUS_API_KEY " \
-H "Content-Type: application/json" \
-H "Idempotency-Key: recv-po-00042-2026-05-14" \
-d '{
"received_date": "2026-05-14",
"location_id": "loc_main_warehouse_uuid",
"line_items": [
{
"order_item_id": "oi_dome_riser_uuid",
"quantity_received": 24,
"bin_id": "bin_A1_uuid"
},
{
"order_item_id": "oi_flat_riser_uuid",
"quantity_received": 12,
"bin_id": "bin_A2_uuid"
}
]
}'
The response includes inventory_updated: true and the GL journal entry ID for the GRNI posting. Inventory on-hand increases immediately.
Partial receipt: you can receive fewer than the ordered quantity. The remaining quantity stays open on the PO. Fire another receive call when the rest arrives.
Stage 3: Enter and post the vendor bill
When the vendor’s invoice arrives, create a vendor bill linked to the PO. Posting the bill clears the GRNI accrual and records the AP liability.
Create the draft bill
curl -s -X POST "https://api.arcuserp.com/v1/vendor-bills" \
-H "Authorization: Bearer $ARCUS_API_KEY " \
-H "Content-Type: application/json" \
-H "Idempotency-Key: bill-acme-inv-20260514" \
-d '{
"vendor_id": "acct_acme_vendor_uuid",
"po_id": "'" $PO_ID "'",
"vendor_invoice_number": "INV-2026-0514",
"bill_date": "2026-05-14",
"due_date": "2026-06-13",
"line_items": [
{
"po_item_id": "oi_dome_riser_uuid",
"quantity": 24,
"unit_cost": 18.00,
"description": "Dome Riser Kit"
},
{
"po_item_id": "oi_flat_riser_uuid",
"quantity": 12,
"unit_cost": 15.00,
"description": "Flat Riser Kit"
}
]
}'
The bill is created in draft status. No GL entry yet.
Post the bill
curl -s -X POST "https://api.arcuserp.com/v1/vendor-bills/ $BILL_ID /post" \
-H "Authorization: Bearer $ARCUS_API_KEY "
GL entries that fire when the bill is posted:
Account Debit Credit GRNI (Goods Received Not Invoiced) total bill amount Accounts Payable total bill amount
This clears the GRNI accrual from Stage 2. The AP liability is now open until Stage 4.
Stage 4: Pay the vendor bill
POST /v1/ap-payments closes the AP liability and records the outgoing cash.
curl
TypeScript SDK
Python SDK
curl -s -X POST "https://api.arcuserp.com/v1/ap-payments" \
-H "Authorization: Bearer $ARCUS_API_KEY " \
-H "Content-Type: application/json" \
-H "Idempotency-Key: ap-pay-bill-20260613" \
-d '{
"vendor_id": "acct_acme_vendor_uuid",
"payment_date": "2026-06-13",
"payment_method": "check",
"bank_account_id": "bank_operating_uuid",
"memo": "Acme Inv 2026-0514 - Dome/Flat Riser restock",
"applications": [
{
"bill_id": "'" $BILL_ID "'",
"amount": 612.00
}
]
}'
GL entries that fire at payment:
Account Debit Credit Accounts Payable 612.00 Bank (Operating Checking) 612.00
The AP liability is cleared. The vendor bill transitions to paid status.
GL summary: the full P2P transaction trail
Stage Event Debit Credit 2 Receive goods Inventory FG GRNI 3 Post vendor bill GRNI Accounts Payable 4 Pay bill Accounts Payable Bank
At Stage 4, GRNI nets to zero and Inventory FG reflects the real cost. This is the standard accrual accounting pattern (NetSuite, Acumatica, QuickBooks Enterprise all follow the same sequence).
Partial payments and multi-bill payments
A single AP payment can apply to multiple bills:
{
"applications" : [
{ "bill_id" : "bill_uuid_1" , "amount" : 432.00 },
{ "bill_id" : "bill_uuid_2" , "amount" : 180.00 }
]
}
You can also pay a bill partially. The bill remains open with a reduced balance_due until the remaining balance is paid or written off.
Vendor credits
To apply a vendor credit against a bill payment, include vendor_credit_ids[] in the payment body:
{
"vendor_credit_ids" : [ "vc_uuid_1" ],
"applications" : [
{ "bill_id" : "bill_uuid" , "amount" : 462.00 }
]
}
The credit reduces the cash portion of the payment. If credits fully cover the bill, no bank JE posts.
Common errors
Code HTTP Meaning po_not_found404 PO UUID does not exist for this entity po_not_receivable422 PO status is not open or processing quantity_exceeds_ordered422 Receipt line quantity exceeds PO line quantity remaining bill_already_posted409 Attempted to post a bill that is already in open status bill_balance_insufficient422 Payment application amount exceeds bill balance due bank_account_not_found404 bank_account_id does not exist for this entityinsufficient_scope403 API key missing purchasing:write or accounting:write scope
Idempotency
Every write in this flow accepts Idempotency-Key. Use a stable key tied to your source record (e.g. po-<source-po-id>, recv-<source-receipt-id>, bill-<vendor-invoice-number>). Keys expire after 24 hours.
Verifying the full chain
After all four stages, run these checks:
# Confirm PO is fulfilled
curl "https://api.arcuserp.com/v1/orders/ $PO_ID " \
-H "Authorization: Bearer $ARCUS_API_KEY "
# Expect: status = fulfilled or processing
# Confirm inventory increased
curl "https://api.arcuserp.com/v1/inventory?product_id=prod_dome_riser_uuid" \
-H "Authorization: Bearer $ARCUS_API_KEY "
# Expect: on_hand increased by 24
# Confirm bill is paid
curl "https://api.arcuserp.com/v1/vendor-bills/ $BILL_ID " \
-H "Authorization: Bearer $ARCUS_API_KEY "
# Expect: status = paid, balance_due = 0
# Run AP aging to confirm no residual payable
curl "https://api.arcuserp.com/v1/reports/ap-aging" \
-H "Authorization: Bearer $ARCUS_API_KEY "
Next steps