> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tensor9.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Adapter Errors

> How to tell which layer failed a request that went through a service adapter.

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](/service-adapters/overview#adaptation-tiers) 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:

```
x-t9-request-error: authorization
```

| Value           | What failed                                                                                                                                                                                         | Where to look                                                                                    |
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| `authorization` | The adapter's own authorization refused the call. Your application's policies, evaluated inside the appliance, did not permit this action on this resource. Nothing was asked of the backing store. | The policies attached to the identity your application runs as                                   |
| `backend`       | The backing store refused the call or could not serve it. The request reached it and it answered.                                                                                                   | The target cloud: the appliance's grants there, whether the resource exists, that cloud's quotas |
| `translation`   | The request could not be carried across. The adapter does not translate this shape, or declines to guess at a faithful translation of it.                                                           | What your application asked for. Not permissions, in either place                                |
| `fabric`        | The request never reached the logic that would have answered it: no route, no reachable adapter.                                                                                                    | Appliance health and connectivity                                                                |

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.

| Value           | Emitted today                                                                                                                                                                                                                                                                                   |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `authorization` | DynamoDB, S3, Secrets Manager, IAM, Lambda, RDS and Bedrock, when your policies refuse a call                                                                                                                                                                                                   |
| `backend`       | DynamoDB, SNS, Kinesis, Firehose, KMS, Systems Manager, EventBridge, ECS, SageMaker and S3 Glacier, when the service backing them refuses, throttles or fails to answer a call. Lambda, when the function's host throttles one. S3, only where it translates a store failure into an S3 error   |
| `translation`   | EC2, for a read outside the self-discovery set or an unsupported filter. SQS and Lambda, for an operation they do not carry across. EKS, Secrets Manager, KMS, Systems Manager, S3, RDS, Route 53 and Application and Network Load Balancer, for a request naming something they do not project |
| `fabric`        | Not yet                                                                                                                                                                                                                                                                                         |

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.

## Related topics

* [**Cross-Cloud IAM**](/fundamentals/cross-cloud-iam): What the `authorization` layer evaluates, and which policies survive adaptation
* [**Service Adapters**](/service-adapters): The adaptation tiers, and which services run through an adapter at all
* [**Observability**](/fundamentals/observability): Routing appliance telemetry to your own sinks
