For the complete documentation index, see llms.txt. Markdown versions of all docs pages are available by appending .md to any docs URL.
Inject response headers
Verified Code examples on this page have been automatically tested and verified.Extract values from a request header and inject it as a header to your response.
Use CEL expressions to inject, modify, and remove response headers. The example uses the request.headers[] context variable to extract a request header value and injects the value into a response header. You also explore how to combine set, add, and remove operations in a single transformation.
Before you begin
- Set up an agentgateway proxy.
- Install the httpbin sample app.
Inject response headers
The gateway intercepts the upstream response and modifies its headers before returning them to the client. You can combine set, add, and remove operations in a single policy so that the gateway applies all three operations in one pass. This configuration is useful when you need to enrich responses with values from the original request or strip internal headers that should not reach the client.
In this example, all three operations are applied together:
x-gateway-response(set): Reads the value of thex-gateway-requestrequest header and sets it as a response header.x-response-raw(set): Set to the static valuehello.access-control-allow-origin(add): Addshttps://example.com. Because httpbin already returns theaccess-control-allow-origin: *header, anotheraccess-control-allow-originheader is added to the response with thehttps://example.comvalue. To not add multiple headers with the same key to a response, use thesetoperation instead. This operation overwrites the value of any existing headers that are sent in the response.access-control-allow-credentials(remove): Strips the header from the response before it reaches the client.
Send a request to the httpbin app. The
access-control-allow-originheader exists before setting the AgentgatewayPolicy.curl -vi http://$INGRESS_GW_ADDRESS:80/response-headers \ -H "host: www.example.com:80"Example output:
... * Request completely sent off < HTTP/1.1 200 OK HTTP/1.1 200 OK < access-control-allow-origin: * access-control-allow-origin: * < content-type: application/json; encoding=utf-8 content-type: application/json; encoding=utf-8 < content-length: 3 content-length: 3Create an AgentgatewayPolicy resource with your transformation rules.
kubectl apply -f- <<EOF apiVersion: agentgateway.dev/v1alpha1 kind: AgentgatewayPolicy metadata: name: transformation namespace: httpbin spec: targetRefs: - group: gateway.networking.k8s.io kind: HTTPRoute name: httpbin traffic: transformation: response: set: - name: x-gateway-response value: 'request.headers["x-gateway-request"]' - name: x-response-raw value: '"hello"' add: - name: access-control-allow-origin value: '"https://example.com"' remove: - access-control-allow-credentials EOFSend a request to the httpbin app and include the
x-gateway-requestrequest header. Verify the following:- You get back a 200 HTTP response code.
- The response includes the injected headers.
- The response contains two
access-control-allow-originvalues. - The response omits
access-control-allow-credentials.
curl -vi http://$INGRESS_GW_ADDRESS:80/response-headers \ -H "host: www.example.com:80" \ -H "x-gateway-request: my-custom-value"Example output:
... * Request completely sent off < HTTP/1.1 200 OK HTTP/1.1 200 OK < x-response-raw: hello x-response-raw: hello < access-control-allow-origin: * access-control-allow-origin: * < access-control-allow-origin: https://example.com access-control-allow-origin: https://example.com < content-type: application/json; encoding=utf-8 content-type: application/json; encoding=utf-8 < content-length: 3 content-length: 3 < x-gateway-response: my-custom-value x-gateway-response: my-custom-valueaccess-control-allow-originappears twice: the original*from httpbin and the appendedhttps://example.comadded by the transformation.access-control-allow-credentialsis absent because it was removed.
Explain a gateway error response
The examples so far read the request, which means they run on a response that a backend actually returned. When the gateway cannot get a response at all, it synthesizes the error itself, and the proxy.error context variable holds why. A response transformation can copy that onto the reply, so a client or a browser network tab shows the cause instead of a bare 503.
| Field | Description |
|---|---|
proxy.error.reason | Broad classification of the failure, such as UpstreamFailure or Timeout. This value is the same value set that the reason label on the gateway’s request metrics uses, so a response header, a log field, and a metric series can be grouped the same way. |
proxy.error.message | The human-readable detail, such as upstream call failed: .... Treat the exact text as unstable, because it can change between releases. Match on reason instead. |
proxy.error is set only when the gateway synthesized the response from a failure. The variable is absent when the backend answered, and absent for a direct response, which is a reply that you configured rather than an error.
Create an AgentgatewayPolicy resource that copies both fields onto the response.
kubectl apply -f- <<EOF apiVersion: agentgateway.dev/v1alpha1 kind: AgentgatewayPolicy metadata: name: explain-gateway-error namespace: httpbin spec: targetRefs: - group: gateway.networking.k8s.io kind: HTTPRoute name: httpbin traffic: transformation: response: set: - name: x-error-reason value: proxy.error.reason - name: x-error-message value: proxy.error.message EOFSend a request to a route whose backend cannot be reached, such as a Service with no ready endpoints. The gateway returns a 503, and the headers now name the cause.
< HTTP/1.1 503 Service Unavailable < x-error-reason: UpstreamFailure < x-error-message: upstream call failed: ...
reason takes one of the following values. The set covers every failure the gateway classifies, not only backend failures, so the same header explains a rejected request as well as an unreachable backend.
| Value | The gateway returned an error because |
|---|---|
NotFound | No bind, listener, route, or service matched the request. |
NoHealthyBackend | No endpoint was eligible to receive the request, including when DNS did not resolve. |
UpstreamFailure | The call to the backend itself failed. |
Timeout | The request timed out. |
InvalidRequest | The client sent a request the gateway could not accept. |
JwtAuth | JWT authentication failed. |
Oidc | OIDC processing failed. |
BasicAuth | Basic authentication failed. |
APIKeyAuth | API key authentication failed. |
ExtAuth | An external authorization service rejected the request. |
Authorization | An authorization policy denied the request, including a failed CSRF check. |
ExtProc | External processing failed. |
RateLimit | A rate limit was exceeded. |
Overload | The frontend was overloaded and shed the request. |
Guardrail | An LLM guardrail rejected the request. |
MCP | An MCP protocol error occurred that was not a rate limit. |
Internal | Processing failed inside the gateway. This is the fallback, so it covers unrelated causes, and the message is the only way to tell them apart. |
Note
Two values that appear on the request metric’s reason label never appear here. Upstream means the backend answered, and DirectResponse means the reply was configured rather than failed, and neither sets proxy.error.
The same variable works in an access log field, which is usually the better place for it, because it keeps the reason without exposing internal detail to clients. For more information, see Add and remove log fields.
Cleanup
You can remove the resources that you created in this guide.kubectl delete AgentgatewayPolicy transformation -n httpbin