proxy.toml Reference
Placed in app/proxy.toml to configure API routing for Application Modules.
[[route]]
# Path pattern with dynamic parameter capture
path = "/api/v1/pools/{pool_id}/volumes/{vol_id}"
target = "https://backend.internal/v2/pools/{{ request.params.pool_id }}/vols/{{ request.params.vol_id }}"
# Forward these headers from the client
forward_headers = ["Accept", "Authorization"]
# Inject secure headers from the backend
[route.inject_headers]
"Authorization" = "Bearer ${env.API_TOKEN}"
# Inbound payload transformation (client contract -> backend payload)
[route.request_filter]
"tera!" = '{"volume_name": "{{ request.params.vol_id }}", "size_bytes": {{ body.size_gb * 1024 }}}'
# Outbound payload transformation (backend response -> client contract)
[route.response_filter]
"jmespath!" = '{"id": id, "status": state}'
# Status code and header normalization
[route.response_mapping]
status_from_body = [
{ when = "response.status == 'failed'", return_status = 400 }
]
override_headers = { "Content-Type" = "application/json" }
# Graph-driven dynamic routes
[[route]]
origin_resource = "provider"
match_on = [{ property = "type", value = "internal" }]
path = "/api/{{ origin_resource.name }}/*"
target = "{{ origin_resource.api_url }}/"
Variables like ${env.X} use fast regex expansion. Variables like {{ env.X }} use full Tera templating and have access to the request context object (for HMAC signing).
Path Patterns & Dynamic Parameters
Routes support path templates with curly brackets {param_name}, e.g. /api/v1/pools/{pool_id}/volumes/{vol_id}:
- Captured path variables are extracted automatically into
request.params.<key>. request.paramscan be referenced intargetURLs,inject_headers,request_filter, andresponse_filter.
Inbound Request Transformation (request_filter)
The request_filter intercepts incoming client bodies before dispatching upstream:
"tera!": Renders a new payload string. Receivesbody(parsed JSON if valid, else raw string),request.params,request.method, graph context, and secrets."jmespath!"/"jp!": Executes a JMESPath transformation against the inbound JSON body.
Response Status & Header Mapping (response_mapping)
Normalizes backend status codes and headers to enforce a consistent client contract:
status_from_body: Evaluates conditional expressions (when) using Tera against the final response JSON. If condition renderstrue, overrides the HTTP response status withreturn_status.override_headers: Injects or replaces headers returned to the client.
Context Variables
The following context is available during Tera templating (for target, headers, filters, and responses):
request.method: HTTP method (e.g.,GET,POST).request.path: The URI path of the request.request.params: Key-value map of path parameters captured from{param}route placeholders.request.host: TheHostheader orX-Forwarded-Host.request.scheme: The HTTP scheme (httporhttps).request.base_url: Combination of scheme and host.request.query: Object containing all query string parameters.request.query_values: Concatenated values of sorted query arguments (useful for signing).request.headers: Object containing incoming request headers.request.body: The raw text of the incoming HTTP body.request.timestamp: Epoch time in seconds.origin_resource: Properties of the currently matched graph node (iforigin_resourceis defined).
Dynamic Request Signatures:
For APIs that require every request to be cryptographically signed (such as Exoscale’s v2 API)
# app/proxy.toml
[[route]]
path = "/api/exoscale"
target = "https://api-ch-dk-2.exoscale.com/v2"
forward_headers = ["Accept", "User-Agent"]
[route.inject_headers]
"Content-Type" = "application/json"
"Authorization" = """
{%- set expires = request.timestamp + 600 -%}
{%- set msg = request.method ~ " " ~ request.path ~ "\n" ~ request.body ~ "\n" ~ request.query_values ~ "\n\n" ~ expires -%}
{%- set sig = msg | hmac_sha256(key=env.EXOSCALE_API_SECRET, encoding="base64") -%}
EXO2-HMAC-SHA256 credential={{ env.EXOSCALE_API_KEY }}{% if request.signed_query_args %},signed-query-args={{ request.signed_query_args }}{% endif %},expires={{ expires }},signature={{ sig -}}
"""
Action Routing
Proxy requests directly to dynamically exposed Runner services using action://<module_id>/<action_name>/<path>.
[[route]]
path = "/acme/*"
target = "action://cert-manager/start_acme_gateway/acme"
The controller will automatically locate the running service, resolve its dynamically allocated port, and tunnel the traffic.