Developers
REST API reference
Everything the app can read, your systems can read too. The API is versioned at /api/public/v1 and is available on Pro and above.
Authentication
Create a key under Settings, API keys. The key is shown once — store it in your secret manager. Every key belongs to exactly one organization and can never read another tenant's data. Keys are either read-only or read/write; endpoints that change data require a read/write key.
Request
curl https://guardianprice.app/api/public/v1/suppliers \
-H "Authorization: Bearer gp_live_9f3c…"Revoking a key in Settings takes effect immediately. Keys can also carry an expiry date.
Rate limits
Limits are per key, per minute, and scale with your plan: 120 requests on Pro, 300 on Pro Plus, 600 on Pro Enterprise. Over the limit you get a 429 with a Retry-After header in seconds. Retry with exponential backoff rather than a tight loop.
Responses and pagination
Success bodies always carry a data key. List endpoints add has_more and next_cursor; pass the cursor back as ?cursor= to continue. limit defaults to 50 and caps at 200. Dates are ISO‑8601, amounts are plain numbers in your organization's currency.
Error shape
{ "error": { "code": "validation_failed", "message": "invoice_date must be an ISO date." } }| Status | Code | Meaning |
|---|---|---|
| 401 | unauthorized | Missing, unknown, revoked or expired key. |
| 402 | plan_required | The API is not included on your current plan. |
| 402 | usage_limit_reached | This billing period's line allowance is spent. |
| 403 | forbidden | A read-only key attempted a write. |
| 404 | not_found | No record with that id in your organization. |
| 413 | payload_too_large | Body exceeded the ingestion size ceiling. Split the batch. |
| 422 | validation_failed | The body failed validation; the message names the field. |
| 429 | rate_limited | Rate limit exceeded. Honour the `Retry-After` header. |
| 500 | internal_error | Something failed on our side. Safe to retry with backoff. |
Suppliers
The vendors you buy from. Everything else hangs off a supplier id.
/api/public/v1/suppliersList every supplier in your organization.
Query parameters: limit, cursor
Response
{
"data": [
{ "id": "3f0c…", "name": "Acme Supply", "code": "ACME", "created_at": "2026-01-14T18:02:11Z" }
],
"has_more": false,
"next_cursor": null
}/api/public/v1/suppliersread/write keyCreate a supplier. Re-posting the same name returns the existing record.
Request body
{ "name": "Acme Supply", "code": "ACME" }Response
{ "data": { "id": "3f0c…", "name": "Acme Supply", "code": "ACME" } }Invoices
Invoices already loaded, with their lines and match results.
/api/public/v1/invoicesList invoices, newest first.
Query parameters: supplier_id, from, to, po_number, limit, cursor
Response
{
"data": [
{
"id": "b41a…",
"invoice_number": "INV-88213",
"invoice_date": "2026-03-02",
"supplier_id": "3f0c…",
"po_number": "PO-4471",
"total_amount": 18422.55,
"line_count": 64
}
],
"has_more": true,
"next_cursor": "MjAyNi0wMy0wMnxiNDFh"
}/api/public/v1/invoices/{id}One invoice with all of its lines.
Response
{
"data": {
"id": "b41a…",
"invoice_number": "INV-88213",
"lines": [
{
"sku": "SHG-30-CH",
"description": "Architectural shingle, charcoal",
"quantity": 120,
"unit": "BDL",
"invoiced_unit_price": 41.90,
"agreed_unit_price": 38.75,
"extended_variance": 378.00,
"match_method": "exact_sku"
}
]
}
}Price lists
Dated contract pricing. A line is judged against the version in force on its invoice date.
/api/public/v1/price-listsList price list versions and their effective dates.
Query parameters: supplier_id, limit, cursor
Response
{
"data": [
{
"id": "9ac1…",
"supplier_id": "3f0c…",
"name": "Acme 2026 contract",
"effective_from": "2026-01-01",
"effective_to": null,
"item_count": 1842
}
],
"has_more": false,
"next_cursor": null
}Variances
The output of the engine: every line where invoiced price left contracted price.
/api/public/v1/variancesList variance lines. `min_impact` filters by absolute dollar impact.
Query parameters: supplier_id, from, to, status, min_impact, limit, cursor
Response
{
"data": [
{
"line_id": "77e2…",
"invoice_number": "INV-88213",
"invoice_date": "2026-03-02",
"supplier_name": "Acme Supply",
"sku": "SHG-30-CH",
"agreed_unit_price": 38.75,
"invoiced_unit_price": 41.90,
"extended_variance": 378.00,
"direction": "overcharge",
"unmatched_reason": null
}
],
"has_more": true,
"next_cursor": "MjAyNi0wMy0wMnw3N2Uy"
}Claims
Recovery lifecycle: open, disputed, credited, written off.
/api/public/v1/claimsList claims with their current status and amounts.
Query parameters: status, supplier_id, limit, cursor
Response
{
"data": [
{
"id": "c0d9…",
"reference": "CLM-0142",
"supplier_id": "3f0c…",
"status": "open",
"claimed_amount": 4820.15,
"credited_amount": 0,
"opened_at": "2026-03-05T14:20:00Z"
}
],
"has_more": false,
"next_cursor": null
}/api/public/v1/claimsread/write keyOpen a claim over a set of variance lines.
Request body
{
"supplier_id": "3f0c…",
"line_ids": ["77e2…", "77e3…"],
"note": "Q1 contract pricing not applied"
}Response
{ "data": { "id": "c0d9…", "reference": "CLM-0142", "status": "open", "claimed_amount": 4820.15 } }/api/public/v1/claims/{id}One claim with its lines and history.
Response
{ "data": { "id": "c0d9…", "status": "open", "lines": [], "history": [] } }/api/public/v1/claims/{id}read/write keyAdvance a claim or record a credit against it.
Request body
{ "status": "credited", "credited_amount": 4820.15, "note": "Credit memo CM-2291" }Response
{ "data": { "id": "c0d9…", "status": "credited", "credited_amount": 4820.15 } }Scorecards and usage
Aggregates for reporting and for watching your own plan limits.
/api/public/v1/scorecardsPer-supplier accuracy and exposure over a date range.
Query parameters: from, to
Response
{
"data": [
{
"supplier_id": "3f0c…",
"supplier_name": "Acme Supply",
"lines": 4210,
"accuracy_rate": 97.4,
"overcharge_amount": 18422.55,
"recovered_amount": 9110.00
}
]
}/api/public/v1/usageCurrent billing period usage against your plan limits.
Response
{
"data": {
"plan_tier": "pro_plus",
"period_start": "2026-03-01",
"period_end": "2026-03-31",
"invoice_lines_used": 41200,
"invoice_lines_limit": 250000
}
}Ingestion
Push data in instead of uploading a spreadsheet. Batches process asynchronously.
/api/public/v1/ingest/invoicesread/write keySubmit invoices and their lines. Returns a batch id to poll.
Request body
{
"supplier_id": "3f0c…",
"invoices": [
{
"invoice_number": "INV-88213",
"invoice_date": "2026-03-02",
"po_number": "PO-4471",
"lines": [
{ "sku": "SHG-30-CH", "description": "Architectural shingle", "quantity": 120, "unit": "BDL", "unit_price": 41.90 }
]
}
]
}Response
{ "data": { "batch_id": "ba7c…", "status": "queued", "invoices_received": 1, "lines_received": 1 } }/api/public/v1/ingest/price-listsread/write keySubmit a dated price list version and its items.
Request body
{
"supplier_id": "3f0c…",
"name": "Acme 2026 contract",
"effective_from": "2026-01-01",
"items": [
{ "sku": "SHG-30-CH", "description": "Architectural shingle", "unit": "BDL", "unit_price": 38.75 }
]
}Response
{ "data": { "batch_id": "ba7d…", "status": "queued", "items_received": 1 } }/api/public/v1/ingest/batches/{id}Poll a batch until status is `complete` or `failed`.
Response
{
"data": {
"batch_id": "ba7c…",
"status": "complete",
"rows_accepted": 64,
"rows_skipped": 0,
"errors": []
}
}Discovery
A machine-readable index of every route lives at the API root and needs no key.
Request
curl https://guardianprice.app/api/public/v1