Docs / Get started

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 send Accept: application/json.
  • Query parameters are used for filters and options (?country=MT, ?qr=false).
  • Booleans in the query string: true/false (also 1/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.

ParameterDefaultDescription
page1Page number, starting at 1.
per_page25Items 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 200 instead of 201. 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

StatusRetry?
429Yes, after Retry-After.
409 request_in_progressYes, after a short pause, with the same Idempotency-Key.
503 pool_depletedYes, later. New standard codes are added to the pool continuously.
500, timeouts, connection errorsYes, with exponential back-off. For creations, always resend the same Idempotency-Key.
Other 4xxNo — 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.