Environments and authentication
Every request authenticates with an API key in the header:
Authorization: Bearer pgz_live_...
Authorization: Bearer pgz_test_...- The key determines the environment. There is no mode header on the external surface:
pgz_live_operates in production;pgz_test_operates in the isolated sandbox. - Sandbox isolation is total. A
pgz_test_request never reaches the payment provider, never creates a real charge, and never appears in production data. Resources created with a key from one environment are invisible to the other. - Key issuance. The merchant generates and revokes sandbox and live keys in the authenticated cockpit. The plaintext key is shown only at creation; after that the platform stores only
key_hash. - Invalid or revoked key →
401(invalid_api_key). Insufficient scope →403(insufficient_scope). Permission never returns404. - Every key carries scopes. The scopes per endpoint are in the Reference.
Idempotency
Every POST that creates a resource requires the header:
Idempotency-Key: 3f1a9b2c-6d4e-4a71-9c2f-8e5b1d0a7c33- Required on
POST /v1/payment_links,POST /v1/quotes,POST /v1/quotes/{id}/accept, andPOST /v1/payment_intents. Missing or empty →400(missing_idempotency_key); over 255 chars →400(invalid_idempotency_key). Any unique string works — we recommend a UUID v4, but the format is not required (ADR-0010). - 24h TTL, in a dedicated idempotency table. A replay of the same key within the TTL returns the original response, byte for byte, with the
Idempotent-Replay: trueheader. - Same key with a different body →
409(idempotency_key_reuse). - After 24h the key is released.
Error envelope
Every error response has the same shape. Every response (success or error) carries the X-Request-Id header.
json
{
"error": {
"code": "invalid_request",
"message": "...",
"param": "amount",
"request_id": "req_9f8c..."
}
}paramis the field name when the error is about a specific field;nullotherwise.request_idis in the body and in theX-Request-Idheader — cite it in any support request.
Schemas are strict. An unknown key in the body of any request → 400 with error.code = "unknown_parameter" and error.param = the key name. There is never a silent drop.
Pagination
Listings are cursor-based:
GET /v1/payments?limit=50&starting_after=pay_9f8c...limit: integer, default20, maximum100. Above100→400(invalid_request) — it is not silently truncated, so you do not paginate thinking you received the whole page.starting_after: id of the last object from the previous page.- Response:
{ "data": [ ... ], "has_more": true }.
Versioning and deprecation
- The version lives in the path:
/v1/. A breaking change requires a new path version (/v2/). - Adding a field to a response is not breaking and happens within
/v1/without notice. Clients should ignore unknown fields. - Deprecation: a deprecated version gets at least 12 months notice. During that period, the version's responses carry the
Sunsetheader (RFC 8594) with the shutdown date and aDeprecation: trueheader. TheSunsetdate is never moved earlier.