Skip to main content
An application calling an adapted service gets an AWS-shaped error, because that is what its SDK expects. The error tells you what went wrong. It does not, on its own, tell you where, and at the Max adaptation tier there is more than one candidate: your application’s own authorization, the adapter, or the service backing it in your customer’s cloud. An AccessDenied from a policy that refused the call and an AccessDenied from a backing store that refused it are the same error. Reading one, an operator either goes and audits policies that are fine, or opens a ticket with a cloud provider that did nothing. Start with Debugging Service Adapters when you need more than the failing layer. Explain can show the selected mapping and, for covered requests, the decisions and constraints recorded by the adapter. Error provenance and Explain complement each other: the header below names a failure layer, while an explanation report describes the request path. Neither proves that an uncertain write had no effect.

The x-t9-request-error header

A failed response can carry a header naming the layer that produced the failure:
The header rides beside the AWS error rather than inside it. The status, the code, the message and the envelope your SDK reads are unchanged, so retry and error handling that keys on the error code behaves exactly as it did.

An absent header means the layer is not stated

It is not a hint about which layer failed. The header is present only when the adapter can identify one failure layer without weakening the origin service’s security behavior. Otherwise the response omits it rather than naming a layer that the available evidence does not establish. Treat a response without the header as having unstated provenance. Use Explain, origin request identifiers and target-state checks to narrow the failure without inferring a layer from absence.

Where the header is available

The following service paths can state the listed values. Do not assume the same coverage for an adapter or failure path that is not listed. The authorization row is short on purpose. Telling your own policy denial apart from a refusal by the service behind the adapter is the question this header exists for, and only the adapters listed there answer it. Everywhere else, a policy denial arrives with no header at all. DynamoDB is the one to reach for if you want to see both halves. A refusal from your policies says authorization, a refusal from Spanner or Cosmos says backend, and the two are otherwise byte-identical to your SDK. That distinction matters more than it looks. On an adapter listed under backend but not under authorization, a refusal from the backing service says backend while a refusal from your policies can omit the header, so it is tempting to read an absent header as “not the backend”. Do not. Absence means unstated. This includes responses where a single layer cannot be named safely and S3 paths that relay a store response unchanged. The header tells you something when it is present and nothing when it is absent. S3 needs one more sentence. Where the adapter translates a store failure into an S3 error it states backend. Where it runs against an S3-compatible store and relays that store’s own response unchanged, there is no translation step and no header. Not every refusal states a layer, and that is deliberate. The S3 adapter answers the same AccessDenied for a signature that did not verify and for a bucket owned by another account, so that a refusal tells an unauthenticated caller nothing about which of their guesses was closest. Neither may claim a layer, so neither does: a response with no header is one where naming a layer would have been a guess.