Docs / Get started
Errors
The API uses HTTP status codes for the class of problem and a stable error.code for the exact cause. Branch on error.code; the message is for humans and its wording may change.
Error format
{
"error": {
"code": "ambiguous_code",
"message": "This code exists in more than one country; repeat with ?country=.",
"details": { "countries": ["MT", "IT"] }
}
}
| Field | Description |
|---|---|
error.code | Stable, machine-readable identifier (see below). |
error.message | Human-readable explanation in English. |
error.details | Optional. Extra data, e.g. fields for validation errors or countries for ambiguous_code. |
Validation errors
422 invalid_request lists each problem per field in details.fields. Nested fields use dot notation (user.external_ref).
{
"error": {
"code": "invalid_request",
"message": "The request is invalid.",
"details": {
"fields": {
"lng": ["The lng field is required when lat is present."],
"user.external_ref": ["Use your own customer or order id, not an email address."]
}
}
}
}
HTTP status codes
| Status | Meaning |
|---|---|
200 OK | Success. Also an idempotent replay of a creation. |
201 Created | A resource was created. |
204 No Content | Deleted. No body. |
401 Unauthorized | Credentials missing, wrong, revoked or suspended. |
402 Payment Required | Your code allowance is used up. |
403 Forbidden | Your account lacks the scope this endpoint needs. |
404 Not Found | No such record among yours. |
409 Conflict | The request conflicts with the current state. |
422 Unprocessable Entity | The input is invalid or cannot be resolved. |
429 Too Many Requests | Rate limit reached; see Rate limits. |
500 / 503 | Problem on GP Code's side; retry later. |
Error codes
Authentication & access
| Code | HTTP | Cause and fix |
|---|---|---|
unauthenticated | 401 | A header is missing, the key is unknown, the secret is wrong, or the request comes from outside your IP allowlist. Check both headers and your server's egress IP. |
key_revoked | 401 | The key was revoked. Switch to your current key. |
client_suspended | 401 | Your partner account is suspended. Contact GP Code. |
forbidden_scope | 403 | The endpoint needs a scope your account does not have. |
Request & lookup
| Code | HTTP | Cause and fix |
|---|---|---|
invalid_request | 422 | Validation failed. See details.fields. |
not_found | 404 | The user or code does not exist among yours — including a user_id that belongs to someone else. |
ambiguous_code | 409 | You have this code string in several countries. Repeat with ?country=; details.countries lists them. |
request_in_progress | 409 | A request with the same Idempotency-Key is still running. Retry shortly. |
Location
| Code | HTTP | Cause and fix |
|---|---|---|
address_not_found | 422 | The address could not be geocoded. Send lat and lng instead, or add a country hint. |
country_unresolved | 422 | No country could be determined from the pin, address or hint. Send country. |
GP codes
| Code | HTTP | Cause and fix |
|---|---|---|
code_unavailable | 409 | The custom code already exists in that country, or a move would place the code in a country where the same string exists. Choose another string or location. |
code_protected | 409 | The string is on GP Code's reserved list. Choose another. |
code_rejected | 409 | The string failed content screening. Choose another. |
not_manageable | 409 | The customer moved this code to their own GP Code account. You can still read it, but no longer change or delete it. |
validity_expired | 422 | The code is outside its validity period and can no longer be changed. |
Allowance & service
| Code | HTTP | Cause and fix |
|---|---|---|
quota_exhausted | 402 | Your allowance for this code type is used up. Nothing was written. Delete unused codes or contact GP Code to extend your contract. Check quota first. |
pool_depleted | 503 | No standard code is available right now. Retry later. |
client_misconfigured | 503 | Your partner account has no allowance set up. Contact GP Code. |
server_error | 500 | Unexpected error. Retry with back-off (same Idempotency-Key); contact support if it persists. |
Handling errors
# -w prints the status after the body; -s hides the progress bar.
curl -s -w "\n%{http_code}\n" {{BASE}}/gp-codes/NOPE \
-H "X-GoPeeko-Key: $GPCODE_KEY" \
-H "Authorization: Bearer $GPCODE_SECRET"
use GuzzleHttp\Exception\ClientException;
try {
$client->post('gp-codes', ['json' => $payload]);
} catch (ClientException $e) {
$error = json_decode($e->getResponse()->getBody(), true)['error'] ?? null;
match ($error['code'] ?? null) {
'quota_exhausted' => notifyAccountManager(),
'code_unavailable' => askCustomerForAnotherCode(),
'invalid_request' => logFields($error['details']['fields']),
default => throw $e,
};
}
const res = await fetch(`${BASE}/gp-codes`, { method: 'POST', headers, body: JSON.stringify(payload) });
if (!res.ok) {
const body = await res.json().catch(() => ({}));
switch (body.error?.code) {
case 'quota_exhausted': return notifyAccountManager();
case 'code_unavailable': return askCustomerForAnotherCode();
case 'invalid_request': return logFields(body.error.details.fields);
default: throw new Error(`GP Code API ${res.status}: ${body.error?.message ?? body.message}`);
}
}
res = session.post(f"{BASE}/gp-codes", json=payload)
if not res.ok:
error = res.json().get("error", {})
code = error.get("code")
if code == "quota_exhausted":
notify_account_manager()
elif code == "code_unavailable":
ask_customer_for_another_code()
elif code == "invalid_request":
log_fields(error["details"]["fields"])
else:
res.raise_for_status()