# ADR: Error Source Distinction

- **Status**: Accepted
- **Date**: 2026-02-03
- **Deciders**: OAGW Team

## Context and Problem Statement

OAGW acts as a proxy between clients and upstream services. Both OAGW and upstream services can return errors. Clients need a reliable way to distinguish between:

- **Gateway errors**: Generated by OAGW (e.g., rate limiting, route not found, authentication failure, timeout)
- **Upstream errors**: Returned by the upstream service and passed through by OAGW

Without clear distinction, clients cannot determine the appropriate error handling strategy (e.g., retry logic, error reporting, user messaging).

## Decision Drivers

- Client developer experience
- Compatibility with existing HTTP clients and tooling
- Support for non-JSON response bodies
- Minimal overhead for success path
- Works across all supported protocols (HTTP, gRPC, WebSocket, WebTransport, SSE)

## Considered Options

1. Response Header Indicator
2. Wrapped Error Envelope (opt-in)
3. Distinct Status Code Ranges

## Options Analysis

### Option 1: Response Header Indicator

Add a header to every response indicating the error source:

```http
# OAGW-generated error
HTTP/1.1 429 Too Many Requests
X-OAGW-Error-Source: gateway
Content-Type: application/json

{"code": "RATE_LIMIT_EXCEEDED", "message": "..."}

# Upstream error (passthrough)
HTTP/1.1 429 Too Many Requests
X-OAGW-Error-Source: upstream
Content-Type: application/json

<upstream body as-is>
```

| Aspect                | Assessment                                |
|-----------------------|-------------------------------------------|
| Simplicity            | Simple to implement and consume           |
| Non-invasive          | Does not modify response body             |
| Content-type agnostic | Works with any body format                |
| Reliability           | Headers can be stripped by intermediaries |
| Client adoption       | Clients must explicitly check header      |

**Used by**

- Kong Gateway: `X-Kong-Upstream-Status` header
- Apigee: `X-Apigee-fault-source` header

### Option 2: Wrapped Error Envelope (opt-in)

Client opts in via request header; OAGW wraps all responses in an envelope:

```http
# Request
X-OAGW-Response-Mode: envelope

# OAGW error response
HTTP/1.1 200 OK
Content-Type: application/json

{
  "oagw": {
    "source": "gateway",
    "status": 429,
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded"
  }
}

# Upstream response (success or error)
HTTP/1.1 200 OK
Content-Type: application/json

{
  "oagw": {
    "source": "upstream",
    "status": 500
  },
  "body": { ... upstream JSON ... }
}
```

| Aspect            | Assessment                                 |
|-------------------|--------------------------------------------|
| Unambiguous       | Clear structured distinction               |
| Extensible        | Easy to add metadata                       |
| REST semantics    | Breaks HTTP status code semantics          |
| Opt-in complexity | Clients must opt-in and unwrap             |
| Non-JSON bodies   | Problematic for binary/streaming responses |

### Option 3: Distinct Status Code Ranges

OAGW uses specific status codes for gateway-originated errors:

| Source   | Status Codes                                     |
|----------|--------------------------------------------------|
| Gateway  | 560-569 (custom range) or strictly 502, 503, 504 |
| Upstream | Passthrough as-is                                |

| Aspect                | Assessment                                   |
|-----------------------|----------------------------------------------|
| Immediate distinction | Status code alone identifies source          |
| Standards compliance  | Custom codes violate HTTP spec               |
| Collision risk        | Upstream may return same codes (502/503/504) |
| Expressiveness        | Limited error detail in status code          |

## Comparison Matrix

| Criteria              | Option 1 (Header) | Option 2 (Envelope) | Option 3 (Status) |
|-----------------------|:-----------------:|:-------------------:|:-----------------:|
| Simple implementation |        Yes        |       Partial       |        Yes        |
| Simple consumption    |        Yes        |       Partial       |        Yes        |
| Works with non-JSON   |        Yes        |         No          |        Yes        |
| No body modification  |        Yes        |         No          |        Yes        |
| Standards compliant   |        Yes        |       Partial       |        No         |
| Reliable detection    |      Partial      |         Yes         |      Partial      |
| Works with streaming  |        Yes        |         No          |        Yes        |

## Decision Outcome

**Chosen**: Option 1 (Response Header Indicator) - `X-OAGW-Error-Source: gateway|upstream`

**Rationale**:

1. **Simplicity**: Straightforward to implement and consume. No body manipulation required.
2. **Compatibility**: Works with any content type (JSON, binary, streaming). Does not break existing HTTP clients.
3. **Industry standard**: Kong and Apigee use the same pattern. Developers are familiar with this approach.
4. **Non-invasive**: Upstream responses passed through unchanged. No risk of breaking upstream error formats.
5. **Streaming support**: Works with SSE, WebSockets, gRPC streams. Envelope approach (Option 2) fails here.

**Trade-offs accepted**:

- Headers can be stripped by intermediaries (rare in practice, acceptable risk).
- Clients must check header explicitly (simple conditional logic).

Option 2 (envelope) rejected: breaks HTTP semantics, problematic for non-JSON/streaming, adds unwrap complexity.

Option 3 (status codes) rejected: custom 5xx codes violate HTTP spec, collision risk with upstream errors.

## Links

- [OAGW Design Document](../DESIGN.md)
- [Plain HTTP Example](../examples/1.http.positive.md)
- [Kong X-Kong-Upstream-Status header](https://developer.konghq.com/gateway/configuration/#headers)
- [Apigee X-Apigee-fault-source header](https://docs.apigee.com/api-platform/troubleshoot/runtime/503-service-unavailable-backend-server)
