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.

Secure Static Proxying

Secrets can be securely injected using Tera templating or regex expansion, hiding them from the browser.

[[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.

[[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"
}
'''