~netlandish/ponch-api-docs

#Conventions

#Request headers

Header When Value
Authorization always Bearer <api key>. See Authentication
Content-Type requests with a body application/json
Accept-Language recommended en or es. See Language

Send request bodies as JSON. Query parameters go in the URL as usual.

#Data types

  • IDs are UUIDs, sent as strings: "2b4d6f8a-0c2e-4a6c-8e0a-2c4e6a8c0e4d".
  • Timestamps are ISO-8601 in UTC, e.g. "2026-08-11T13:27:59.184Z" or "2026-08-11T13:27:59.184489+00:00". Parse them as ISO-8601 instead of matching one exact format: the Z and +00:00 forms both appear.
  • Barcodes look like SC-1540CD2A802A. Wherever an endpoint takes a barcode_value, you can also pass the whole string scanned from the pass's QR code (a URL ending in /r/SC-…). Ponch pulls the barcode out of it, and case doesn't matter.
  • Slugs (template_slug, store slug) are short URL-safe names, generated from the name when the template or store is created. Renaming it later doesn't change the slug, so a slug is a stable reference.

#Errors

Errors are JSON with a detail key. Most of the time detail is a string:

{"detail": "Card not found"}

A request that fails validation (a missing field, a value of the wrong type or out of range) gets 422, and detail is a list with one entry per problem:

{
  "detail": [
    {
      "type": "int_parsing",
      "loc": ["query", "page"],
      "msg": "Input should be a valid integer, unable to parse string as an integer"
    }
  ]
}

loc gives the location of the bad value, ending with the field name. Handle both shapes. Some errors carry extra keys next to detail (e.g. retry_after, pending), which are listed with each endpoint.

detail is for people, not code. Its text is translated and may change. Branch on the status code and on the documented extra keys.

Status Meaning
200 / 201 / 202 / 204 Success
400 The request was understood but a value is wrong, e.g. an unknown template_slug
401 Missing, invalid or revoked key, or the plan has no API access. Don't retry
403 Not allowed for this card or template, e.g. more stamps than the template allows per visit
404 Not found. Records belonging to another company also return 404
409 Conflicts with the card's current state, e.g. no reward ready to redeem
422 Validation failed; see above
429 Too soon: the card's stamp cooldown is active. Wait for Retry-After seconds
5xx Our fault. Safe to retry if you sent an idempotency_key

#Idempotency

POST /stamping/stamp, POST /stamping/redeem and POST /stamping/set-count accept an optional idempotency_key string in the body. If you send the same key for the same card again, Ponch does not repeat the action. It returns the result of the first call.

Always send one. Networks time out and automation platforms retry, and without a key a retry is a second action:

  • a retried redeem uses up a second reward the customer never got, and that can't be undone over the API
  • a retried stamp adds a stamp the customer didn't earn

Use a value that identifies the real-world event: an order ID, a POS transaction ID, or a UUID you generate once and save before the first attempt. Don't generate a fresh one on each retry. Keys are scoped to a card, so the same order ID on two different cards is two separate actions.

stamp and set-count share one set of keys per card. A set-count sent with a key you already used for a stamp on the same card is treated as a replay, and does nothing. Use a different key for each action, e.g. order-10482-stamp and order-10482-adjust.

On /stamping/stamp, idempotency_key is required whenever stamps is greater than 1.

#Pagination

There are two styles:

  • GET /integrations/customers/search takes page (from 1) and page_size (default 20, max 100), and returns {"items": [...], "total": n, "page": p, "page_size": s}.
  • GET /integrations/events uses a cursor: pass the newest occurred_at you've seen as since. See Polling.

Other list endpoints return a bare JSON array. Some take an optional limit (max 200), and the rest return everything, which is small: a company has few stores, templates and webhooks.

#Language

Human-readable text (detail and the stamping message) is returned in English or Latin American Spanish. The default is Spanish, so send Accept-Language: en if you want English. You can also add ?lang=en to the URL.

Language only affects human-readable text. Field names, event names, status values and slugs are always the same. Text the merchant wrote, like a reward description or store name, is returned exactly as written.

#Rate limits

Ponch doesn't publish fixed rate limits yet, but may add them. Be a good citizen:

  • poll the events feed at most once a minute per connection
  • prefer webhooks to polling
  • back off when you get 429 or 5xx

A 429 from /stamping/stamp is not a rate limit on your key. It's the card's stamp cooldown, a per-template setting the merchant uses to stop the same card being stamped twice in quick succession. The response tells you when the card can be stamped again:

HTTP/1.1 429 Too Many Requests
Retry-After: 60

{"detail": "Cooldown active", "retry_after": 60}

A retry with the same idempotency_key as a call that already succeeded isn't affected by the cooldown. It gets the original result back.

About this wiki

commit 5e140ef5493db7e98830f4b7ef4d620de5419518
Author: Peter Sanchez <peter@netlandish.com>
Date:   2026-10-20T06:45:49-06:00

add proper title and descriptions to each page
Clone this wiki ("main" branch)
https://git.code.netlandish.com/~netlandish/ponch-api-docs (read-only)
git@git.code.netlandish.com:~netlandish/ponch-api-docs (read/write)