Debug a failed request

A call to the API failed. Reproduce it with the full response, work out whether the gateway refused it or the app behind the route answered, match the error to its cause, and escalate with what support needs.

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:

Request
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
{
  "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.
  • code is the machine-readable part. Branch on it, not on the text.
  • error is the reason in words. Log it and quote it when you escalate, but don't match on its wording.
  • message repeats error for 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 seeWho answeredNext
502, code: dispatch_failedThe 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-AppThe 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 tableThe gateway, before anything behind it ranStep 4
A JSON error without X-Capability-App on a platform service route, such as storage or searchThat platform serviceIts reference page, for example Storage
404 with the plain text 404 page not foundThe gateway: the path is not under /v1 at allFix the path
Any other body that is not JSON, such as an HTML pageMost likely the edge firewall in front of the gatewayWAF 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.

Statuscodeerror saysWhat it means, and what to do
400bad_requestmissing X-Revenexx-TenantThe tenant header is missing or blank. It is checked before the credential, so you get this even with no key.
400bad_requestrequest 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.
401not_authenticatedinvalid api keyThe key you sent is unknown: a typo, a deleted key, or a key from another environment.
401not_authenticatednot authenticatedNo credential arrived, or the Bearer token is not valid.
403forbiddenapi key not valid for this tenantThe 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.
403forbiddennot a member of this tenantA valid user token for someone who has no access to this tenant.
403forbiddenapi key scope does not permit this capability, or api key missing scope: … on a platform service routeThe 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.
404not_foundno capability matches this routeNothing 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).
405method_not_allowedthis route does not accept …; allowed: …The path exists, but not for this method. The Allow header lists the ones it takes.
429errorrate limit exceededThe 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.
502dispatch_failedcapability dispatch failed, or … upstream unreachableThe gateway could not get an answer from what serves the route. A timeout ends here too: the gateway does not send 504.
503unavailablevariesSomething 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-App names 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.log output. 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-ID you 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-App value, 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

Was this page helpful?