Using AI Middlewares with Messages API
This guide explains how to use Traefik Hub's AI Gateway middlewares with the Anthropic Messages API format.
When messages-api is in the chain, followed by Content Guard, LLM Guard,
Semantic Cache, and Token Rate Limit, these middlewares automatically detect
the Messages API format and adapt their behavior accordingly.
No additional clientRequestFormat configuration is needed.
The Bedrock Mantle middleware produces the same Anthropic Messages API format as messages-api.
Everywhere this guide references messages-api in a middleware chain, you can substitute bedrock-mantle. The auto-detection and ordering behavior described below apply identically.
Overview
The Anthropic Messages API uses a different request/response format compared to Chat Completions and Responses API. For a side-by-side comparison of the three endpoint formats, see Endpoint Format Comparison in the AI Gateway overview.
Prerequisites
Before starting, ensure you have:
-
AI Gateway enabled in your Traefik Hub installation:
helm upgrade traefik traefik/traefik -n traefik --wait \--reset-then-reuse-values \--set hub.aigateway.enabled=true -
An Anthropic API key stored in a Kubernetes Secret:
apiVersion: v1kind: Secretmetadata:name: ai-keysnamespace: appstype: OpaquestringData:anthropic-api-key: sk-ant-XXXXX -
An ExternalName service pointing to Anthropic:
apiVersion: v1kind: Servicemetadata:name: anthropicnamespace: appsspec:type: ExternalNameexternalName: api.anthropic.comports:- port: 443
Auto-Detection of Messages API Format
The Messages API middleware marks the request format in the request context.
The guard and cache middlewares (Content Guard, LLM Guard, Semantic Cache, and Token Rate Limit) read that context and adapt their field extraction and response shaping accordingly.
No clientRequestFormat configuration is needed on them.
This is the same mechanism used by the Chat Completion and Responses API governance middlewares.
For this to work, the Messages API middleware must come first among the AI middlewares in the chain so it sets the format before the other middlewares run:
middlewares:
- name: messagesapi # sets format in context (must be first among other AI middleware)
- name: content-guard-messages # auto-detects Messages API format
- name: llm-guard-messages # auto-detects Messages API format
- name: semantic-cache-messages # auto-detects Messages API format
Using Content Guard
The Content Guard middleware detects and masks PII in requests and responses. When the Messages API middleware is in the chain, it automatically extracts text from relevant fields in this format.
Configuration Example
- Content Guard Middleware
- Messages API Middleware
- IngressRoute
- Service
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: content-guard-messages
namespace: apps
spec:
plugin:
content-guard:
engine:
presidio:
host: http://presidio-analyzer.presidio.svc.cluster.local:5002
language: en
request:
rules:
- entities:
- EMAIL_ADDRESS
- PHONE_NUMBER
- CREDIT_CARD
block: false
mask:
char: "*"
unmaskFromLeft: 2
unmaskFromRight: 2
response:
rules:
- entities:
- PERSON
- EMAIL_ADDRESS
block: false
mask:
char: "*"
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: messagesapi
namespace: apps
spec:
plugin:
messages-api:
apiKey: urn:k8s:secret:ai-keys:anthropic-api-key
model: claude-opus-4-5
allowModelOverride: false
allowParamsOverride: true
params:
temperature: 0.7
maxTokens: 1024
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: messages-with-content-guard
namespace: apps
spec:
entryPoints:
- websecure
routes:
- kind: Rule
match: Host(`ai.localhost`) && Path(`/v1/messages`)
services:
- name: anthropic
port: 443
passHostHeader: false
middlewares:
- name: messagesapi
- name: content-guard-messages
apiVersion: v1
kind: Service
metadata:
name: anthropic
namespace: apps
spec:
type: ExternalName
externalName: api.anthropic.com
ports:
- port: 443
Testing
Send a request with PII to verify the middleware masks it before forwarding:
curl -X POST https://ai.localhost/v1/messages \
-H "Content-Type: application/json" \
-d '{
"model": "claude-opus-4-5",
"messages": [
{"role": "user", "content": "My email is [email protected] and my phone is 555-123-4567"}
]
}'
The PII is masked in the request before it reaches Anthropic.
Using LLM Guard
The LLM Guard middleware analyzes content using an external guard service or LLM. When the Messages API middleware is in the chain, it automatically reads from relevant fields and formats deny responses in the Anthropic Messages API shape.
Configuration Example
- LLM Guard Middleware
- Messages API Middleware
- IngressRoute
- Service
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: llm-guard-messages
namespace: apps
spec:
plugin:
llm-guard:
endpoint: http://sentiment-analyzer.apps.svc.cluster.local:5000/predict
format:
custom: {}
clientConfig:
timeout: 30s
headers:
Content-Type: application/json
request:
template: '{"text":"{{ (index .messages 0).content }}"}'
blockConditions:
- condition: 'JSONGt(".predictions[0].NEGATIVE", 0.6)'
reason: 'Negative sentiment detected'
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: messagesapi
namespace: apps
spec:
plugin:
messages-api:
apiKey: urn:k8s:secret:ai-keys:anthropic-api-key
model: claude-opus-4-5
allowModelOverride: false
allowParamsOverride: true
params:
temperature: 0.7
maxTokens: 1024
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: messages-with-llm-guard
namespace: apps
spec:
entryPoints:
- websecure
routes:
- kind: Rule
match: Host(`ai.localhost`) && Path(`/v1/messages`)
services:
- name: anthropic
port: 443
passHostHeader: false
middlewares:
- name: messagesapi
- name: llm-guard-messages
apiVersion: v1
kind: Service
metadata:
name: anthropic
namespace: apps
spec:
type: ExternalName
externalName: api.anthropic.com
ports:
- port: 443
Testing
Test with negative content:
curl -k -X POST https://ai.localhost/v1/messages \
-H "Content-Type: application/json" \
-d '{
"model": "claude-opus-4-5",
"messages": [{"role": "user", "content": "I hate everything"}]
}'
Requests exceeding your threshold are blocked with a properly formatted Anthropic error response.
Test with positive content to confirm it passes through:
curl -k -X POST https://ai.localhost/v1/messages \
-H "Content-Type: application/json" \
-d '{
"model": "claude-opus-4-5",
"messages": [{"role": "user", "content": "I love learning new things"}]
}'
Using Semantic Cache
The Semantic Cache middleware caches responses based on semantic similarity of the request content. When the Messages API middleware is in the chain, it automatically extracts conversation content from the format for cache key generation.
Configuration Example
- Semantic Cache Middleware
- Messages API Middleware
- IngressRoute
- Service
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: semantic-cache-messages
namespace: apps
spec:
plugin:
semantic-cache:
vectorDB:
redis:
endpoints:
- redis-stack.apps.svc.cluster.local:6379
database: 1
collectionName: ai_messages_cache
maxDistance: 0.6
ttl: 3600 # 1 hour
vectorizer:
openai:
model: text-embedding-3-small
token: urn:k8s:secret:ai-keys:openai-token
dimensions: 1536
readOnly: false
allowBypass: true
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: messagesapi
namespace: apps
spec:
plugin:
messages-api:
apiKey: urn:k8s:secret:ai-keys:anthropic-api-key
model: claude-opus-4-5
allowModelOverride: false
allowParamsOverride: true
params:
temperature: 0.7
maxTokens: 1024
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: messages-with-cache
namespace: apps
spec:
entryPoints:
- websecure
routes:
- kind: Rule
match: Host(`ai.localhost`) && Path(`/v1/messages`)
services:
- name: anthropic
port: 443
passHostHeader: false
middlewares:
- name: messagesapi
- name: semantic-cache-messages
apiVersion: v1
kind: Service
metadata:
name: anthropic
namespace: apps
spec:
type: ExternalName
externalName: api.anthropic.com
ports:
- port: 443
Testing
Make the same request twice to confirm caching works:
First request (cache miss):
curl -ik -X POST https://ai.localhost/v1/messages \
-H "Content-Type: application/json" \
-d '{"model":"claude-opus-4-5","messages":[{"role":"user","content":"What is the capital of France?"}]}'
The response includes X-Cache-Status: Miss.
Second request (cache hit):
curl -ik -X POST https://ai.localhost/v1/messages \
-H "Content-Type: application/json" \
-d '{"model":"claude-opus-4-5","messages":[{"role":"user","content":"What is the capital of France?"}]}'
The response includes X-Cache-Status: Hit and is served without consuming Anthropic tokens.
Complete Integration Example
Here's a complete example combining all middlewares for a production Anthropic Messages API route:
- All Middlewares
- IngressRoute
- Service
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: content-guard-messages
namespace: apps
spec:
plugin:
content-guard:
engine:
presidio:
host: http://presidio-analyzer.presidio.svc.cluster.local:5002
request:
rules:
- entities:
- EMAIL_ADDRESS
- PHONE_NUMBER
- SSN
mask:
char: "*"
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: messagesapi
namespace: apps
spec:
plugin:
messages-api:
apiKey: urn:k8s:secret:ai-keys:anthropic-api-key
model: claude-opus-4-5
allowModelOverride: false
params:
temperature: 0.7
maxTokens: 1024
cacheControl:
ttl: 5m
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: semantic-cache-messages
namespace: apps
spec:
plugin:
semantic-cache:
vectorDB:
redis:
endpoints:
- redis-stack.apps.svc.cluster.local:6379
database: 1
collectionName: ai_messages_cache
vectorizer:
openai:
model: text-embedding-3-small
token: urn:k8s:secret:ai-keys:openai-token
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: messages-complete-stack
namespace: apps
spec:
entryPoints:
- websecure
routes:
- kind: Rule
match: Host(`ai.localhost`) && Path(`/v1/messages`)
services:
- name: anthropic
port: 443
passHostHeader: false
middlewares:
# Order matters! messages-api must be the first of the AI kind, so it sets the format in the context.
- name: messagesapi # 1. Set format context + apply governance
- name: content-guard-messages # 2. Mask PII before forwarding upstream
- name: semantic-cache-messages # 3. Cache responses
apiVersion: v1
kind: Service
metadata:
name: anthropic
namespace: apps
spec:
type: ExternalName
externalName: api.anthropic.com
ports:
- port: 443
Middleware Order
The order of middlewares matters. The Messages API middleware must come first in the chain among other AI middleware so it can mark the request format in the context before the guard and cache middlewares run:
- Messages API: Validates the request, applies governance (model/param enforcement), injects authentication headers, and marks the format context for the downstream middlewares.
- Content Guard / LLM Guard: Reads the format from the context, then masks or blocks content before the request is forwarded upstream.
- Semantic Cache: Caches the final governed response.
If you can't guarantee the Messages API middleware runs first in the chain, set clientRequestFormat: messagesAPI
explicitly on Content Guard, LLM Guard, Semantic Cache, or Token Rate Limit instead of relying on auto-detection.
Streaming Support
Content Guard, LLM Guard, and Semantic Cache do not support true streaming mode.
When "stream": true is set, these middlewares buffer the complete response, process it, then send it to the client as a single chunk.
For real-time streaming, use a separate route with only the messages-api middleware:
routes:
# Non-streaming with full middleware stack
- kind: Rule
match: Host(`ai.localhost`) && Path(`/v1/messages`)
middlewares:
- name: messagesapi
- name: content-guard-messages
- name: semantic-cache-messages
# Streaming with governance only
- kind: Rule
match: Host(`ai.localhost`) && Path(`/v1/messages/stream`)
middlewares:
- name: messagesapi
Related Content
- Learn more about the Messages API middleware configuration options
- Explore Content Guard for PII protection
- Set up Semantic Cache to reduce costs
- Configure LLM Guard for custom policies
- Review OpenTelemetry metrics for monitoring
