Web Search Tavily Scoped Credentials
A scoped-credentials variant of full-flow-agentic.yaml, over Tavily
Versions marked “overview” do not contain this page. Selecting one opens that version’s documentation overview.
Category: Setup-dependent integration
Task: A scoped-credentials variant of full-flow-agentic.yaml, over Tavily
Prerequisites: The external service, credentials, or certificates referenced by this configuration.
Run it: Use ghcr.io/praxis-proxy/ai:0.8.0 and follow the container quickstart to mount and start the configuration.
This configuration comes from the selected release. The example has not been run here; external services are not bundled.
Download the source file.
Companion resources from the same snapshot:
# Anthropic Messages Web Search over Tavily with Per-User Callout Credentials
#
# A scoped-credentials variant of full-flow-agentic.yaml, over Tavily. Accepts
# Anthropic Messages requests with hosted web search against a native Messages
# backend, and stages each caller's own Tavily key captured from a trusted
# ingress header:
#
# * state_owner maps trusted ingress identity headers (x-auth-tenant /
# x-auth-user) into a normalized owner the web-search callout projects
# into its nested provider subrequest.
# * callout_credentials captures a PER-USER provider key from the trusted
# ingress header x-user-tavily-key into the `tavily_search` slot and strips
# the header so it never reaches an upstream. anthropic_web_search stages
# that per-user secret as the Tavily `Authorization: Bearer` token on the
# provider callout (issue #1389) instead of the shared api_key. A request
# missing the header fails closed with a 401 authentication_error before any
# provider callout runs.
#
# Both filters run in the OUTER chain (before the iterative_request_router):
# core threads their extensions into every IRR step, and the ingress header is
# stripped before the IRR clones request headers.
#
# Requires WEB_SEARCH_API_KEY, the shared Tavily key. The per-user slot is
# mandatory here (user_credential, below), so the shared key never authenticates
# a real request; it is only the config-time fallback the filter requires.
#
# curl -N http://127.0.0.1:8080/v1/messages \
# -H 'content-type: application/json' \
# -H 'x-auth-tenant: acme' -H 'x-auth-user: alice' \
# -H "x-user-tavily-key: $ALICE_TAVILY_KEY" \
# -d '{"model":"openai/gpt-oss-20b","max_tokens":1024,"stream":true,"messages":[{"role":"user","content":"Use web search to look up potato, then summarize in one sentence."}],"tools":[{"name":"WebSearch","description":"Search the web","input_schema":{"type":"object","properties":{"query":{"type":"string"}},"required":["query"]}}]}'
listeners:
- name: anthropic-web-search-tavily-scoped
address: "127.0.0.1:8080"
filter_chains: [web-search-tavily-scoped]
filter_chains:
- name: web-search-tavily-scoped
filters:
- filter: state_owner
# Deployment-specific trusted ingress identity. The authentication
# boundary must overwrite these and prevent clients from spoofing them.
mode: trusted_headers
tenant:
header: x-auth-tenant
issuer:
static: urn:example:gateway
subject:
header: x-auth-user
- filter: callout_credentials
# Capture the per-user Tavily key from a trusted ingress header into the
# `tavily_search` slot and strip the header so it never reaches an
# upstream. anthropic_web_search stages this per-user secret instead of
# the shared api_key below.
#
# SECURITY: each source_header is trusted-boundary-owned. The
# authentication boundary MUST unconditionally delete then set every
# source_header on every request, so a client cannot spoof another
# user's credential by supplying the header itself.
credentials:
- slot: tavily_search
source_header: x-user-tavily-key
- filter: anthropic_messages_request
on_invalid: reject
- filter: iterative_request_router
initial_step: inference
max_iterations: 6
timeout_ms: 90000
steps:
- name: inference
filters:
- filter: anthropic_web_search
provider: tavily
api_key:
env_var: WEB_SEARCH_API_KEY
# Require the per-user Tavily key captured into the `tavily_search`
# slot by callout_credentials. A request without it fails closed
# with a 401 authentication_error before any provider callout. The
# secret rides the Tavily `Authorization: Bearer` header (issue
# #1389), staged by the executor only at the resolved provider
# authority — never on the in-chain request or in its body.
user_credential: tavily_search
default_context_size: medium
timeout_ms: 10000
# Each Tavily provider callout executes through this outbound
# chain; the filtered-subrequest executor enforces destination
# authority, DNS/SSRF, TLS/SNI, and Host centrally.
outbound_chain:
name: anthropic-web-search-outbound
filters:
- filter: request_id
- filter: anthropic_messages_protocol
default_version: "2023-06-01"
- filter: router
routes:
- path_prefix: "/v1/messages"
cluster: messages-backend
- filter: load_balancer
clusters:
- name: messages-backend
endpoints: ["127.0.0.1:8000"]
on_result:
- filter: anthropic_web_search
key: action
value: loop
next: inference
- default: true
done: true
insecure_options:
allow_private_endpoints: true # example proxies to local backends