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.

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 does not mean the layer is unknown, and it is not a hint about which one it was. An error that has not been taught its layer says nothing rather than guessing, because a wrong layer costs more than no layer: it sends you to read the wrong system. Treat a response without the header the way you would have before it existed.

What states a layer today

Three of the four values have a producer today. fabric is defined and reserved: it is what the header will carry once an adapter states it, and none does. Do not branch a runbook on a value that is not in this list. 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 says nothing, so it is tempting to read an absent header as “not the backend”. Do not. Absence means unstated, and you get it from every adapter that has not been taught, from every refusal that spans layers, and from S3’s own proxy tiers. 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.
  • Cross-Cloud IAM: What the authorization layer evaluates, and which policies survive adaptation
  • Service Adapters: The adaptation tiers, and which services run through an adapter at all
  • Observability: Routing appliance telemetry to your own sinks