Requests & responses
Conventions shared by every endpoint: how to send data, what comes back, and how to page, retry and stay within the rate limit.
Request format
- HTTPS only. All paths are relative to the base URL.
- Send request bodies as JSON with
Content-Type: application/json, and sendAccept: application/json. - Query parameters are used for filters and options (
?country=MT,?qr=false). - Booleans in the query string:
true/false(also1/0). - Country codes are ISO 3166-1 alpha-2, e.g.
MT,IT,DE.
Response envelope
A successful response wraps the resource in data. Lists add a meta object for paging. 204 No Content responses have no body.
{ "data": { "id": 8842, "external_ref": "cust-4417", "name": "Depot 4417", "created_at": "2026-11-01T10:00:00+00:00", "gp_codes_count": 2 } }
{
"data": [ { "id": 8842, "…": "…" }, { "id": 8839, "…": "…" } ],
"meta": { "current_page": 1, "per_page": 25, "last_page": 4, "total": 87 }
}
Errors use a different envelope with a stable error.code. See Errors.
Dates and numbers
- Timestamps are ISO 8601 with offset:
2026-11-01T10:00:00+00:00. - Coordinates are decimal degrees (WGS 84):
lat−90…90,lng−180…180. - Fields with no value are returned as
null, not omitted.
Pagination
List endpoints return results newest first, in pages.
| Parameter | Default | Description |
|---|---|---|
page | 1 | Page number, starting at 1. |
per_page | 25 | Items per page, 1–100. |
Keep requesting page + 1 until meta.current_page equals meta.last_page.
curl "{{BASE}}/gp-codes?page=2&per_page=100" \
-H "X-GoPeeko-Key: $GPCODE_KEY" \
-H "Authorization: Bearer $GPCODE_SECRET"
$page = 1;
do {
$body = json_decode($client->get('gp-codes', ['query' => ['page' => $page, 'per_page' => 100]])->getBody(), true);
foreach ($body['data'] as $code) {
// …
}
$page++;
} while ($body['meta']['current_page'] < $body['meta']['last_page']);
async function* allCodes() {
for (let page = 1; ; page++) {
const res = await fetch(`${BASE}/gp-codes?page=${page}&per_page=100`, { headers });
const { data, meta } = await res.json();
yield* data;
if (meta.current_page >= meta.last_page) return;
}
}
for await (const code of allCodes()) console.log(code.code);
def all_codes():
page = 1
while True:
body = session.get(f"{BASE}/gp-codes", params={"page": page, "per_page": 100}).json()
yield from body["data"]
if body["meta"]["current_page"] >= body["meta"]["last_page"]:
return
page += 1
Idempotency
Network calls fail. To make POST /gp-codes safe to retry, send an Idempotency-Key header with a value unique to that creation — your order or booking id works well (max. 191 characters).
- For 24 hours, a request with the same key returns the code created by the first request, with status
200instead of201. No second code is created and no quota is used. - If the first request is still running when the retry arrives, the retry gets
409 request_in_progress. Wait a moment and try again. - Keys are scoped to your partner account; other partners' keys never collide with yours.
POST /users needs no header: it is idempotent on external_ref by design. PATCH and DELETE are naturally safe to repeat.
Rate limits
Requests are limited per partner account — 120 requests per minute unless your contract says otherwise. Requests without a recognised API key are limited to 30 per minute per IP address.
Over the limit, the API answers 429 Too Many Requests with a Retry-After header (seconds). This one response uses a plain body rather than the error envelope:
{ "message": "Too Many Attempts." }
Wait for Retry-After seconds before the next request. For bulk jobs, pace requests evenly rather than in bursts.
Retrying safely
| Status | Retry? |
|---|---|
429 | Yes, after Retry-After. |
409 request_in_progress | Yes, after a short pause, with the same Idempotency-Key. |
503 pool_depleted | Yes, later. New standard codes are added to the pool continuously. |
500, timeouts, connection errors | Yes, with exponential back-off. For creations, always resend the same Idempotency-Key. |
Other 4xx | No — fix the request first. |
Versioning
This is version v0 of the Partner API, served under the /api/public/v1 path. Within a version we only make backwards-compatible changes:
- adding endpoints, optional request fields, response fields and error codes;
- changing the wording of
error.message.
Build your client to ignore unknown response fields and to treat unknown error codes by their HTTP status. Breaking changes ship under a new path and are announced in the changelog in advance.