application/problem+json, the standard
shape of RFC 9457:
{
"type": "urn:rfc:problem:core.forbidden",
"status": 403,
"title": "Forbidden",
"code": "core.forbidden",
"params": { "permission": "core.exports.run" },
"requestId": "5d0c2b7e-8f3a-4c61-9e2d-1b7a4f6c3e90"
}
| Field | What it holds |
|---|---|
code | What went wrong, as <module>.<error>. It never changes for the same problem: decide by it. |
params | The values that explain it, such as the permission the key lacks. Each code has its own. |
title | A fixed English summary of the code, the same on every answer, for you to read while you build. Never show it to your users, and never decide by it. |
status | The HTTP status, the same as the answer’s. Several codes share a status. |
type | urn:rfc:problem: and the code. |
requestId | This call’s id. Quote it when you report a problem to us. |
errors | Only with 422 core.validation_failed: each field the request got wrong (below). |
Decide by the code
Each operation’s page in the API reference lists the codes it may answer, such as Start an export’s. Readcode, and params when the code has them:
curl --silent "https://app.readyforcommerce.com/api/v1/orgs/$RFC_ORG_ID/core/org" \
--header "Authorization: Bearer $RFC_API_KEY" | jq '{code, params, requestId}'
import os
import requests
response = requests.get(
f"https://app.readyforcommerce.com/api/v1/orgs/{os.environ['RFC_ORG_ID']}/core/org",
headers={"Authorization": f"Bearer {os.environ['RFC_API_KEY']}"},
)
if not response.ok:
problem = response.json()
if problem["code"] == "core.forbidden":
print("The key lacks", problem["params"]["permission"])
else:
print(problem["code"], problem["requestId"])
<?php
$curl = curl_init('https://app.readyforcommerce.com/api/v1/orgs/' . getenv('RFC_ORG_ID') . '/core/org');
curl_setopt_array($curl, [
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('RFC_API_KEY')],
CURLOPT_RETURNTRANSFER => true,
]);
$body = json_decode(curl_exec($curl), true);
if (curl_getinfo($curl, CURLINFO_RESPONSE_CODE) >= 400) {
if ($body['code'] === 'core.forbidden') {
echo 'The key lacks ' . $body['params']['permission'];
} else {
echo $body['code'] . ' ' . $body['requestId'];
}
}
const response = await fetch(
`https://app.readyforcommerce.com/api/v1/orgs/${process.env.RFC_ORG_ID}/core/org`,
{ headers: { Authorization: `Bearer ${process.env.RFC_API_KEY}` } },
);
if (!response.ok) {
const problem = await response.json();
if (problem.code === 'core.forbidden') console.log('The key lacks', problem.params.permission);
else console.log(problem.code, problem.requestId);
}
A request with wrong fields
422 core.validation_failed lists in errors each part of the request it refused:
{
"type": "urn:rfc:problem:core.validation_failed",
"status": 422,
"title": "Validation failed",
"code": "core.validation_failed",
"requestId": "a3e1f9c2-4b7d-4e08-8a6f-2c5d9b1e7f34",
"errors": [
{
"location": "body",
"pointer": "#/name",
"code": "invalid_type",
"params": { "expected": "string" }
}
]
}
locationis the part of the request:body,querystring,params(the path) orheaders.pointeris a JSON Pointer to the field,#for the whole part.codesays what is wrong with it, andparamsits limits, such asminimumormaximum. The value you sent never comes back.
The common codes
These codes come from what every call goes through, or from a kind of call many operations share; each operation’s page lists the ones it may answer:| Status | Code | What happened | What to do |
|---|---|---|---|
400 | core.bad_request | The request could not be read: a body that is not JSON, or a wrong Idempotency-Key. | Fix the request. |
401 | core.invalid_credential | The key is unknown, revoked, expired or another organization’s. | Send a live key of this organization (Authentication). |
403 | core.forbidden | The key lacks the permission in params.permission. | Use a key that holds it: an existing key’s permissions never grow. |
403 | core.module_not_enabled | Your organization has not activated the module the operation belongs to; store connections need catalog or pricing. | Someone who may activate modules activates it in the app. |
403 | core.module_read_only | The module in params.module is read-only: your organization went over its free allowance with no card. Reads, exports and deletions still pass. | Someone who manages billing adds a card in the app, or deleting records brings usage back within the allowance. |
403 | core.human_only | Only a person does this, in the app; params.reason says which group of actions. | Do it in the app. |
404 | core.not_found, or the module’s own, such as core.job_not_found | The path does not exist, or your organization holds no such record. | Check the path and the id. |
409 | core.org_deletion_scheduled | The organization’s deletion is scheduled, so every call is refused. | Stop calling. The owner can restore the organization in the app until it is deleted. |
409 | core.version_conflict | A change sent with an expectedVersion the record has left behind: someone saved it after you read it, and nothing of your change was written. | Read the record again, apply your change to what it holds now, and send it with its new version. |
422 | core.validation_failed | A field is wrong; errors says which. | Fix the fields as the operation’s page describes them. |
429 | core.rate_limited | Too many calls (Rate limits). | Wait the seconds Retry-After says, then call again. |
429 | core.job_limit_reached | Too many unfinished jobs (Rate limits). | Wait the seconds Retry-After says, or until a job ends. |
500 | core.internal | Something failed on our side. | Retry later; if it persists, tell us the requestId. |
503 | core.unavailable, core.surface_disabled | The API is briefly unavailable, or paused for everyone. | Wait the seconds Retry-After says, then call again. |
A
4xx other than 429 answers the same until you change the request or the key: do not retry it
unchanged. A 429 or a 503 says how long to wait in Retry-After: wait that long, never in a
tight loop. To retry a write without doing it twice, send an
Idempotency-Key.