API reference
Every endpoint on both sides of the integration. Bookmark this page; you will read it more than once.
Endpoints you host (Pattern A)
| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | /oauth/authorize |
Merchant session | Display consent screen, redirect with code. |
| POST | /oauth/token |
Client credentials | Exchange code or refresh token for an access token. |
| POST | /oauth/revoke |
Client credentials | Invalidate a token server-side on disconnect. |
| GET | /v1/merchants/me |
Bearer access token | Merchant profile (name, country, tax number). |
| GET | /v1/locations |
Bearer access token | List the merchant's locations. |
| GET | /v1/orders/{id} |
Bearer access token | Fetch a completed order. |
| GET | /v1/customers/{id} |
Bearer access token | Resolve a POS customer's email/phone (optional but recommended). |
Endpoints Paperless hosts
| Method | Path | Auth | Pattern | Purpose |
|---|---|---|---|---|
| GET | /integrations/{provider}/oauth/callback |
Public (CSRF via state) |
A | OAuth redirect URI. Receives code and state. |
| POST | /webhooks/{provider} |
HMAC signature | A | Receive signed event envelopes. |
| POST | /ingest/{provider}/receipts |
API key + HMAC signature | B | Receive fully-formed receipt pushes. |
| POST | /debug/verify-signature |
HMAC signature | A, B | Sandbox/staging only. Echoes back signature validity for testing. |
Status codes (summary)
| Code | Meaning | Retry? |
|---|---|---|
200 | Accepted (idempotent paths) or duplicate. | No. |
201 | Created (push receipt only). | No. |
400 | Validation error in payload. | No — fix and re-send. |
401 | Bad signature / bad token / skew. | No — investigate. |
403 | Authenticated but not authorized for this merchant. | No. |
404 | Unknown provider, merchant, or order. | No. |
409 | Conflict (e.g., same external_id with different totals). | No — fix and re-send under a new external_id. |
413 | Payload too large (> 256 KB). | No. |
422 | Semantically invalid (e.g., subtotal + tax ≠ total). | No. |
429 | Rate limited. | Yes — honor Retry-After. |
5xx | Transient infra issue. | Yes — exp. backoff. |
Error response shape
All Paperless error responses use a stable envelope:
{
"error": {
"code": "invalid_signature",
"message": "X-Paperless-Signature did not match the expected value.",
"request_id": "req_01HZX0K2A7B6C5D4E3F2G1H0J9"
}
}
| Field | Notes |
|---|---|
error.code | Machine-readable. Stable across versions. |
error.message | Human-readable. Wording may change. |
error.request_id | Give this to ops when filing a ticket. Logs are indexed by it. |
Common error codes
| Code | HTTP | What it means |
|---|---|---|
invalid_signature | 401 | HMAC mismatch. |
signature_skew | 401 | X-Paperless-Timestamp is outside the ±5-minute window. |
unknown_provider | 404 | {provider} slug not recognized. |
unknown_merchant | 404 | merchant_id not recognized. |
schema_validation | 400 | Field missing / wrong type. error.message names the field. |
totals_mismatch | 422 | subtotal + tax ≠ total or sum(payments) ≠ total. |
currency_mismatch | 422 | Mixed currencies in the same order. |
pan_in_last4 | 400 | last4 looks like a PAN. Receipt rejected as a possible leak. |
duplicate_external_id | 409 | Same external_id, different body. Use a new ID for a new receipt. |
rate_limited | 429 | Per-merchant or per-provider rate limit hit. |
Rate limits
| Surface | Default | Notes |
|---|---|---|
POST /webhooks/{provider} | 200 req/s/provider | Soft. Bursts to 500/s are accepted. |
POST /ingest/{provider}/receipts | 100 req/s/merchant | Hard. 429 with Retry-After. |
Order fetch from Paperless to your /v1/orders/{id} | ~30 req/s/merchant | You should accept bursts. Paperless will back off on 429. |
| Token refresh | 1 req per merchant per day in steady state | Burst on rotation events. |
Request a higher ceiling through your solutions engineer if your peak load exceeds these.
Versioning
- This spec is at v1.0. Breaking changes increment the major. Additive optional fields do not.
- Paperless will announce planned changes at least 90 days in advance and run the old version in parallel for 90 days after.
- Paperless will never break the wire shape of an existing field. New fields are added as optional.
Idempotency-Key header
Supported on every POST Paperless hosts. If a request with the same
Idempotency-Key arrives within 24 hours of a prior success, Paperless replays
the prior response without re-executing.
- For
/webhooks/{provider}: use the body'sexternal_event_id. - For
/ingest/{provider}/receipts: use the receipt'sexternal_id.
Time format
Every timestamp is ISO 8601. Always include a timezone. Examples Paperless accepts:
2026-05-15T17:21:19Z 2026-05-15T17:21:19+00:00 2026-05-15T13:21:19-04:00
Paperless rejects naive timestamps without a zone offset.