API Exposure Guide

API Proxying & Configuration

Securely proxy and reshape vendor APIs, and configure graph-driven dynamic routes.

API Proxying & Configuration (proxy.toml)

Application Modules in Rescile (data/app/) can host custom front-ends. To securely communicate with external APIs (or bridge declared state and live state), define backend proxy rules in app/proxy.toml.

Note: Proxy routes are automatically evaluated in descending order of their path length. This ensures that more specific, longer paths will always match before shorter, more generic ones.

Secure Static Proxying

Secrets can be securely injected using Tera templating or regex expansion, hiding them from the browser. For more on secrets in proxy configurations, see Security & Secrets and the proxy.toml Reference.

[[route]]
path = "/api/billing"
target = "http://internal-billing-service:8080/v1"
forward_headers = ["Content-Type", "Accept"]
inject_headers = { "Authorization" = "Bearer ${env.BILLING_API_TOKEN}" }

Graph-Driven Dynamic Routing

By defining an origin_resource, the proxy engine iterates over all nodes of that type in the graph. It evaluates the match_on rules for each node. For every match, it dynamically mounts a new, distinct proxy route.

[[route]]
origin_resource = "firewall"
match_on = [{ property = "managed", value = "true" }]
# Generates a unique path for every firewall in the graph
path = "/api/state/firewalls/{{ origin_resource.name }}/rules"
# Routes to the specific firewall's management IP
target = "{{ origin_resource.api_endpoint }}/api/v1/rules"
inject_headers = { "X-Auth-Token" = "${env.FIREWALL_TOKEN_{{ origin_resource.name | upper }}}" }

Modifying the API Response (response_filter)

Often, upstream APIs return bloated or deeply nested JSON that doesn’t fit the schema your frontend expects. The response_filter directive intercepts the upstream response and transforms it before returning it to the client.

You can reshape the response using JMESPath or Tera:

Using JMESPath (jp!): Ideal for fast, structural JSON filtering.

[[route]]
path = "/api/upstream/nodes"
target = "http://internal-api/v2/nodes"

[route.response_filter]
# Extracts just the names of nodes that have status == 'active'
"jp!" = "data.nodes[?status == 'active'].name"

Using Tera (tera!): Ideal for complete schema reshaping, combining secrets, or injecting contextual graph metadata. The upstream JSON is accessible via the response variable, and the full in-memory architecture graph is available via the graph variable.

[[route]]
path = "/api/state/vms"
target = "https://vcenter.internal/api/vms"

# Reshape the deeply nested vCenter JSON response into the standard enterprise contract
[route.response_filter]
"tera!" = '''
{
  "items": [
    {% for vm in response.value %}
    { "name": "{{ vm.name }}", "status": "{{ vm.power_state }}" }{% if not loop.last %},{% endif %}
    {% endfor %}
  ],
  "source": "vCenter"
}
'''