Debug a failed request
A call to api.revenexx.com came back with an error and you need to know why. This guide takes you from that response to a cause: reproduce it so you can see everything, work out who answered, match the error to what went wrong, and hand over the right details if you still need help.
It is written from the caller's side, so it applies to any code that calls the API: your own server, a Theme's server route, an integration or a script. If you wrote the App that answered, Operating an App covers the same failures from inside the App.
Two terms come up throughout. The gateway is the front door at api.revenexx.com. It checks the tenant, the credential, the route and the request body, then hands the request to whatever serves that route. A capability is a route that an installed App provides, and most routes under /v1 are one. The rest are platform services such as storage and search, which the gateway forwards to directly.
You need the tenant slug (the value in X-Revenexx-Tenant) and the credential the failing call used. Which scope a route needs is in which scope covers which app. If several calls are failing at once, check status.revenexx.com before anything else.
1. Reproduce it with the full response
Most clients show you a status and a message. You need the headers too, and they decide step 3. Send the failing call again with curl -i (with your tenant slug in X-Revenexx-Tenant), and give it a request ID of your own:
curl -i https://api.revenexx.com/v1/products \
-H "X-Revenexx-Tenant: <TENANT_SLUG>" \
-H "X-Revenexx-Api-Key: rvxk_..." \
-H "X-Request-ID: $(uuidgen)"
Send your own ID because when the gateway refuses a request itself, the response carries no X-Request-ID, even if you sent one. The ID comes back only once the request has been handed on. So create one per request, send it, and log it next to the response. Do the same in your production client, and the next failure arrives with an ID you can quote. See Request IDs.
2. Read the status and the error code
Every error the gateway writes has the same JSON body:
{
"error": "missing X-Revenexx-Tenant",
"code": "bad_request",
"message": "missing X-Revenexx-Tenant"
}
- The status decides what your code does next: fix the request, fix the credential, wait, or retry.
codeis the machine-readable part. Branch on it, not on the text.erroris the reason in words. Log it and quote it when you escalate, but don't match on its wording.messagerepeatserrorfor older clients. Don't build anything new on it.
Apps are expected to answer with the same error and code fields, so the shape of the body does not tell you who wrote it. The headers do.
3. Work out who answered
X-Capability-App names the installed App that served a capability route. The gateway sets it once it has handed the request to that App, so it is the header to look at first.
| What you see | Who answered | Next |
|---|---|---|
502, code: dispatch_failed | The gateway. It could not get an answer from what serves the route: unreachable, or too slow. | Step 4, the 502 row |
An error with X-Capability-App | The App named in that header. Its status and body reached you as it sent them. | Step 5 |
A JSON error without X-Capability-App, whose error text is in the step 4 table | The gateway, before anything behind it ran | Step 4 |
A JSON error without X-Capability-App on a platform service route, such as storage or search | That platform service | Its reference page, for example Storage |
404 with the plain text 404 page not found | The gateway: the path is not under /v1 at all | Fix the path |
| Any other body that is not JSON, such as an HTML page | Most likely the edge firewall in front of the gateway | WAF and DDoS |
If the response also carries X-Capability-Degraded: true, the App that normally serves the route failed and a fallback implementation answered instead. See Response headers worth knowing.
This split saves the most time. A refusal from the step 4 table is almost always fixed in your request, not in the App's code. The exception is a contract error caused by a schema that is stricter than it should be, which the App's author has to fix. An error the App wrote is never fixed by changing your credential.
4. Match a gateway error to its cause
The gateway writes these itself, with the reasons you will see in error.
| Status | code | error says | What it means, and what to do |
|---|---|---|---|
400 | bad_request | missing X-Revenexx-Tenant | The tenant header is missing or blank. It is checked before the credential, so you get this even with no key. |
400 | bad_request | request body does not match contract: … | The body failed the route's request schema before anything behind the gateway saw it. The text after the colon names the fields. A POST with no body is checked too and fails, so send a JSON object with the required fields. |
401 | not_authenticated | invalid api key | The key you sent is unknown: a typo, a deleted key, or a key from another environment. |
401 | not_authenticated | not authenticated | No credential arrived, or the Bearer token is not valid. |
403 | forbidden | api key not valid for this tenant | The key is real but belongs to another tenant. A misspelt tenant slug gives this same answer, so check the slug before you check the key. |
403 | forbidden | not a member of this tenant | A valid user token for someone who has no access to this tenant. |
403 | forbidden | api key scope does not permit this capability, or api key missing scope: … on a platform service route | The key works, but its scopes don't cover this route. Use a key that has the scope, or create one in Cockpit that does. See API keys. |
404 | not_found | no capability matches this route | Nothing on this tenant serves that path: either the path is wrong, or the App that provides it is not installed on this tenant (check and install an app). |
405 | method_not_allowed | this route does not accept …; allowed: … | The path exists, but not for this method. The Allow header lists the ones it takes. |
429 | error | rate limit exceeded | The tenant's shared request budget is spent. error is the generic code, not a typo. Wait for Retry-After, then back off. See Rate limits. |
502 | dispatch_failed | capability dispatch failed, or … upstream unreachable | The gateway could not get an answer from what serves the route. A timeout ends here too: the gateway does not send 504. |
503 | unavailable | varies | Something the route depends on is not available. Check status.revenexx.com, then retry later. |
If you sent both an API key and a Bearer token, the key wins and the token is ignored, so a bad key fails with invalid api key even when the token is fine. Send one credential, as API usage says.
Before you retry a failed write
A 502 on a POST, PUT, PATCH or DELETE does not prove that nothing happened. The App may have received the request and applied it before the gateway gave up waiting. The gateway resends a write only when it knows the first attempt never arrived, or when the route has a fallback implementation, which then receives the same request. So don't retry a write blindly. Re-read the resource to see whether it landed, or design the write so that repeating it is harmless. See Retries & idempotency.
5. Read an error from the App
When X-Capability-App is on the response, the status and body are the App's own, passed through as it sent them. The one exception: if the App sent an error with an empty or non-JSON body, the gateway wraps it, keeps the App's status and sets code to upstream_no_body.
The gateway itself never writes 409 or 422. With X-Capability-App on the response, they are the App saying that the request conflicts with existing data (409) or is well formed but breaks one of its rules (422). What each of the App's codes means is part of that route's contract:
- To read the contract, open the route in the API Explorer. Each operation lists the responses it can return. An App can also publish its own OpenAPI document. App errors describes how Apps shape theirs.
- To ask about it,
X-Capability-Appnames the App. If it is one of yours, go to step 6. If not, include the name when you escalate in step 7.
6. Follow the request into your own code
If the App or the Theme that answered is yours, the next place to look is where your code runs.
- An App. Find the invocation in App Studio's Executions. An invocation that is not there never ran, which sends you back to step 4. Operating an App has the status-code ownership table for an App, and Tracing covers exporting spans to your own collector.
- A Theme's server route. The site's request logs record each server-rendered request with its status and duration, plus your
console.logoutput. See Logs. A failed build is a different log. See Deployments.
7. Escalate with what support needs
If status.revenexx.com shows nothing, open a request at support.revenexx.com with:
- the
X-Request-IDyou sent, and the one on the response if there was one - the tenant slug from
X-Revenexx-Tenant - the method, the path and the time in UTC
- the status and the full body, copied rather than paraphrased
- the
X-Capability-Appvalue, or the fact that there was none
The last item tells support straight away whether to look at the gateway or at an App. If you run the App, Operating an App adds the App-side details to include. Never paste the API key or a user token. See Get help for what happens next.
Where to go next
- Errors: the error shape and the statuses, as a reference.
- Retries & idempotency: which failed calls are safe to send again.
- Operate & go live: the rest of what you settle before and after launch.