GuardianPrice

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." } }
StatusCodeMeaning
401unauthorizedMissing, unknown, revoked or expired key.
402plan_requiredThe API is not included on your current plan.
402usage_limit_reachedThis billing period's line allowance is spent.
403forbiddenA read-only key attempted a write.
404not_foundNo record with that id in your organization.
413payload_too_largeBody exceeded the ingestion size ceiling. Split the batch.
422validation_failedThe body failed validation; the message names the field.
429rate_limitedRate limit exceeded. Honour the `Retry-After` header.
500internal_errorSomething failed on our side. Safe to retry with backoff.

Suppliers

The vendors you buy from. Everything else hangs off a supplier id.

GET/api/public/v1/suppliers

List 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
}
POST/api/public/v1/suppliersread/write key

Create 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.

GET/api/public/v1/invoices

List 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"
}
GET/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.

GET/api/public/v1/price-lists

List 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.

GET/api/public/v1/variances

List 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.

GET/api/public/v1/claims

List 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
}
POST/api/public/v1/claimsread/write key

Open 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 } }
GET/api/public/v1/claims/{id}

One claim with its lines and history.

Response

{ "data": { "id": "c0d9…", "status": "open", "lines": [], "history": [] } }
PATCH/api/public/v1/claims/{id}read/write key

Advance 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.

GET/api/public/v1/scorecards

Per-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
    }
  ]
}
GET/api/public/v1/usage

Current 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.

POST/api/public/v1/ingest/invoicesread/write key

Submit 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 } }
POST/api/public/v1/ingest/price-listsread/write key

Submit 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 } }
GET/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