Purchase Order Parser API
v1.0Extract every field from any purchase order document — with server-side arithmetic validation built in.
Overview
The Purchase Order Parser API turns any PO document into clean, structured JSON with a single API call. It extracts vendor, buyer, ship-to, full line items, and all financial totals — then validates the arithmetic server-side and flags any discrepancies before returning the response.
🤖 AI-Powered
Works with any PO layout, template, or vendor format without configuration
✅ Math Validated
Line-item arithmetic and grand total reconciliation checked server-side on every request
📸 OCR Included
Handles scanned PDFs and image-based POs automatically on the MEGA plan
💡 Perfect For
- • Procurement automation and ERP integration (SAP, Oracle, NetSuite)
- • Accounts payable platforms cross-referencing POs against supplier invoices
- • Supply chain and order management systems
- • Supplier portals accepting buyer PO uploads
- • Finance teams flagging pricing discrepancies before payment
Quick Start
Base URL
https://api.cparse.com/po/v1Authentication
X-API-Key: YOUR_API_KEYGet your API key from the dashboard. New accounts include free credit — no credit card required.
Quick Example
cURL
curl --request POST \
--url https://api.cparse.com/po/v1/parse \
--header 'X-API-Key: YOUR_API_KEY' \
--form 'file=@purchase_order.pdf'Endpoints
/fieldsList all fields extracted by the PO parser. No authentication required.
Response
{
"fields": [
{ "name": "po_number", "description": "Purchase order number / reference" },
{ "name": "issue_date", "description": "PO issue date (YYYY-MM-DD)" },
{ "name": "delivery_date", "description": "Requested delivery date (YYYY-MM-DD)" },
{ "name": "currency", "description": "ISO 4217 currency code (e.g. USD, EUR)" },
{ "name": "vendor.name", "description": "Vendor (supplier) name" },
...
],
"line_item_fields": [
{ "name": "line_number", "description": "Sequence number on the document" },
{ "name": "sku", "description": "Item / product code or SKU" },
{ "name": "description", "description": "Text description of the item" },
...
]
}/healthHealth check endpoint. Returns service status.
Response
{ "status": "ok", "service": "po-parser", "version": "0.1.0" }/parseExtract structured data from a purchase order document.
Request
Content-Type: multipart/form-datafile(required): PDF, DOCX, JPEG, PNG, ZIP, or 7z containing the PO document
Response
[
{
"success": true,
"file_name": "purchase_order.pdf",
"pages_processed": 1,
"ocr_used": false,
"data": {
"po_number": "PO-2026-00187",
"issue_date": "2026-03-28",
"delivery_date": "2026-04-18",
"currency": "USD",
"vendor": {
"name": "Global Parts Supply Co.",
"address": "1200 Industrial Pkwy, Houston, TX 77001",
"email": "orders@globalparts.com",
"phone": "+1-713-555-0182",
"tax_id": "EIN 47-1234567"
},
"buyer": {
"name": "Acme Manufacturing Inc.",
"address": "500 Factory Rd, Detroit, MI 48201",
"email": "procurement@acme.com",
"phone": "+1-313-555-0093"
},
"ship_to": {
"name": "Acme Detroit Warehouse",
"address": "700 Warehouse Blvd, Detroit, MI 48202"
},
"line_items": [
{
"line_number": 1,
"sku": "BLT-M8-100",
"description": "M8 Hex Bolts, Grade 8.8, Box of 100",
"quantity": 50,
"unit": "box",
"unit_price": 12.50,
"line_total": 625.00,
"tax_rate": null
}
],
"subtotal": 1655.00,
"discount_amount": null,
"tax_amount": 132.40,
"shipping_cost": 45.00,
"grand_total": 1832.40,
"payment_terms": "Net 30",
"incoterms": null,
"notes": "Please include packing list with shipment.",
"validation": {
"total_mismatch": false,
"line_arithmetic_errors": []
}
}
}
]Response Fields
Document Fields
| Field | Type | Description |
|---|---|---|
po_number | string | null | Purchase order number or reference |
issue_date | string | null | PO issue date (YYYY-MM-DD) |
delivery_date | string | null | Requested delivery date (YYYY-MM-DD) |
currency | string | null | ISO 4217 currency code (e.g. USD, EUR) |
vendor | object | Supplier receiving the order — see Vendor fields below |
buyer | object | Company issuing the order — see Buyer fields below |
ship_to | object | Delivery destination when different from buyer |
line_items | array | Ordered line items — see Line Item fields below |
subtotal | number | null | Pre-tax, pre-shipping subtotal |
discount_amount | number | null | Document-level discount as a flat amount |
tax_amount | number | null | Total tax amount |
shipping_cost | number | null | Freight or delivery charge if itemised |
grand_total | number | null | Final amount due |
payment_terms | string | null | Payment terms as written (e.g. "Net 30") |
incoterms | string | null | Incoterms if stated (e.g. "FOB", "CIF") |
notes | string | null | Free-text notes or special instructions |
validation | object | Server-side computed validation results |
Vendor / Buyer / Ship-To Fields
| Field | Description |
|---|---|
name | Company or individual name |
address | Full address as a single string |
email | Email address (vendor and buyer only) |
phone | Phone number (vendor and buyer only) |
tax_id | VAT, EIN, ABN, or similar tax identifier (vendor only) |
Line Item Fields
| Field | Type | Description |
|---|---|---|
line_number | number | null | Sequence number on the document |
sku | string | null | Item or product code / SKU |
description | string | null | Text description of the item |
quantity | number | null | Quantity ordered |
unit | string | null | Unit of measure — null if not stated on the document |
unit_price | number | null | Price per single unit |
line_total | number | null | Line total as stated on the document |
tax_rate | number | null | Per-line tax rate as a percentage (e.g. 10 for 10%) |
Validation Fields
The validation object is computed server-side after extraction — never by the LLM. Use it to decide whether to accept the data automatically or flag it for manual review.
| Field | Type | Description |
|---|---|---|
total_mismatch | boolean | true when grand_total does not equal subtotal + tax + shipping - discount |
line_arithmetic_errors | array | Lines where quantity * unit_price does not match the stated line_total |
line_arithmetic_errors[].line_number | number | null | Line number from the document |
line_arithmetic_errors[].expected_total | number | Computed: quantity * unit_price |
line_arithmetic_errors[].stated_total | number | line_total as extracted from the document |
line_arithmetic_errors[].delta | number | stated_total minus expected_total (negative = overstated on document) |
Plans
| Feature | BASIC | PRO | ULTRA | MEGA |
|---|---|---|---|---|
| Max file size | 5 MB | 10 MB | 20 MB | 20 MB |
| Max pages | 10 | 25 | 50 | 50 |
| OCR (images / scanned PDFs) | ✗ | ✗ | ✗ | ✓ |
| Max OCR pages | - | - | - | 3 |
API-key users authenticated via X-API-Key always receive MEGA plan limits. Balance is the only constraint.
Error Handling
| Status | Meaning | Fix |
|---|---|---|
| 400 | No file / empty file / corrupted archive | Check the uploaded file |
| 401 | Missing or invalid API key | Pass a valid X-API-Key header |
| 402 | Insufficient balance | Top up at cparse.com/dashboard |
| 413 | File too large or too many pages for your plan | Upgrade plan or reduce file |
| 415 | Unsupported file type or OCR not enabled on plan | Use PDF/DOCX or upgrade to MEGA |
| 500 | Internal server error | Retry or contact support |
Code Examples
Python
import requests
url = "https://api.cparse.com/po/v1/parse"
headers = {"X-API-Key": "YOUR_API_KEY"}
with open("purchase_order.pdf", "rb") as f:
response = requests.post(url, files={"file": f}, headers=headers)
result = response.json()[0]
data = result["data"]
print(data["po_number"]) # "PO-2026-00187"
print(data["vendor"]["name"]) # "Global Parts Supply Co."
print(data["grand_total"]) # 1832.40
# Check validation
if data["validation"]["total_mismatch"]:
print("Warning: grand total does not reconcile")
for err in data["validation"]["line_arithmetic_errors"]:
print(
f"Line {err['line_number']}: "
f"expected {err['expected_total']}, "
f"stated {err['stated_total']} "
f"(delta {err['delta']})"
)
# Print line items
for item in data.get("line_items", []):
print(f" {item['sku']} {item['description']} qty={item['quantity']} total={item['line_total']}")JavaScript (Node.js)
import FormData from 'form-data';
import fs from 'fs';
import axios from 'axios';
const form = new FormData();
form.append('file', fs.createReadStream('purchase_order.pdf'));
const response = await axios.post('https://api.cparse.com/po/v1/parse', form, {
headers: {
...form.getHeaders(),
'X-API-Key': 'YOUR_API_KEY',
},
});
const [result] = response.data;
const { data } = result;
console.log(data.po_number); // "PO-2026-00187"
console.log(data.vendor.name); // "Global Parts Supply Co."
console.log(data.grand_total); // 1832.40
if (data.validation.total_mismatch) {
console.warn('Grand total does not reconcile');
}
data.validation.line_arithmetic_errors.forEach((err) => {
console.warn(
`Line ${err.line_number}: expected ${err.expected_total}, stated ${err.stated_total} (delta ${err.delta})`
);
});