Intelligence Layer
All templates

Intelligence Layer

One governed door in front of your organization's data, your own intelligence layer, with every AI agent behind it: a capability registry that is also the routing table, three-level scope checks, an append-only audit trail, and an MCP server so Claude or Cursor can use it directly. Runs end to end with no third-party credentials.

Start building in Xano: Access the prompt at https://go.xano.co/start-xano-with-template-skill and run it here with governed-agent-data-gateway as your template.
ClaudeOpenAI CodexCursorVS Code

Copy and paste the prompt in your local coding agent of choice.

Template Details

11 Tables 27 APIs 18 Functions
Template Preview
Client appClient appWeb · mobile · agentxil_admin APIxil_admin APIagents/registerhealthseedxil_app APIxil_app APIaskauditcapabilitieshistorystepsxil_auth APIxil_auth APIloginmesignupxil_layer APIxil_layer APIaskauditcapabilitiesproxyresolvestepsxil_source_reference APIxil_source_referenceAPIbranches/listclients/gethealthorders/searchreps/performanceBusiness logicBusiness logicXil agent from keyXil build target urlXil check authXil check scopeXil city coordsXil current turnXil emit step+2 moreDataDataref_branchref_clientref_paper_orderref_sales_repxil_agent_keyxil_agentxil_audit_log+2 more

Overview

One governed door in front of your data, with every AI agent behind it — a capability registry, scope checks, and an append-only audit trail, plus an MCP server so Claude or Cursor can use it directly.

You have data in Xano and a growing number of AI agents that want it. Handing each agent database access or vendor API keys does not scale, and it leaves you with no answer to the only three questions that matter in an incident: who asked, were they allowed, and what happened.

This template is that one door. An agent names a capability — orders.search, weather.forecast — and the layer identifies the caller, checks their scopes, forwards the call to whichever workspace or third-party API actually holds the data, and writes an append-only receipt. Agents never hold a downstream credential and never learn the topology. It runs end to end in about four minutes with no third-party credentials: the router uses Xano's free built-in model, the outside calls go to keyless public APIs, and the "downstream system" is a reference API group in the same workspace, reached over real HTTP. That also means a fresh install starts open — setting the two security switches is the first thing you do, and GET health tells you when you are done.

The code is prefixed xil_ throughout (for Xano intelligence layer), which is what the layer is called inside Xano. It is the same thing this page calls the gateway.

What it provides

Once more than one agent needs company data, the usual options both fail. Give each agent a database connection or a vendor API key and you have copied your credentials across every consumer, with no way to revoke one of them and no record of what any of them did. Put a bespoke endpoint in front of each use case and the access rules end up spread across a dozen stacks, each slightly different, each a place governance can be wrong.

This template removes that by making the access decision a single piece of code and the routing a table. Every call — from the console, from curl, from Claude, from an agent tool — goes through one function, xil_proxy_request, which resolves the capability in the xil_capability registry, checks the caller's scopes, attaches the downstream credential the caller never sees, and appends a row to xil_audit_log whether the call was allowed, denied, mocked, or failed. Adding a capability is inserting a row, not shipping a deploy; anything not in the registry is not callable; and because the audit row is written on the same path as the call, the record cannot drift from what actually happened.

Repo layout

backend/
  ai/
    agent/
      xil_router.xs
    mcp_server/
      xil_gateway.xs
    tool/
      xil_country_info.xs
      xil_delivery_weather.xs
      xil_fx_convert.xs
      xil_get_client.xs
      xil_list_branches.xs
      xil_mcp_call_capability.xs
      xil_mcp_list_capabilities.xs
      xil_mcp_recent_audit.xs
      xil_mcp_resolve_intent.xs
      xil_open_ticket.xs
      xil_rep_performance.xs
      xil_search_orders.xs
      xil_warehouse_search.xs
  api/
    xil_admin/
      agents/
        register_post.xs
      api_group.xs
      health_get.xs
      seed_post.xs
    xil_app/
      api_group.xs
      ask_post.xs
      audit_get.xs
      capabilities_get.xs
      history_get.xs
      steps_get.xs
    xil_auth/
      api_group.xs
      login_post.xs
      me_get.xs
      signup_post.xs
    xil_layer/
      api_group.xs
      ask_post.xs
      audit_get.xs
      capabilities_get.xs
      proxy_post.xs
      resolve_post.xs
      steps_get.xs
    xil_source_reference/
      branches/
        list_get.xs
      clients/
        get_post.xs
      orders/
        search_post.xs
      reps/
        performance_post.xs
      api_group.xs
      health_get.xs
  function/
    xil_agent_from_key.xs
    xil_build_target_url.xs
    xil_check_auth.xs
    xil_check_scope.xs
    xil_city_coords.xs
    xil_current_turn.xs
    xil_emit_step.xs
    xil_extract_tool_calls.xs
    xil_header.xs
    xil_mock_response.xs
    xil_proxy_request.xs
    xil_ref_check_source_key.xs
    xil_resolve_caller.xs
    xil_resolve_capability.xs
    xil_run_ask.xs
    xil_seed_data.xs
    xil_source_key.xs
    xil_write_audit.xs
  table/
    ref_branch.xs
    ref_client.xs
    ref_paper_order.xs
    ref_sales_rep.xs
    xil_agent_key.xs
    xil_agent.xs
    xil_audit_log.xs
    xil_capability.xs
    xil_chat_step.xs
    xil_request_log.xs
    xil_user.xs
  workflow_test/
    agent_key_identifies_its_caller_and_can_be_revoked.xs
    failed_call_errors_instead_of_faking_data.xs
    governed_call_is_forwarded_and_audited.xs
    health_reports_the_layer_is_seeded_and_wired.xs
    intent_resolves_to_a_capability_without_a_model.xs
    scope_denied_call_is_logged_not_forwarded.xs
    seeded_source_data_answers_for_real.xs
    unknown_capability_is_refused_and_audited.xs
  workspace/
    xil.xs

How it works

 browser user ── username/password ──► /api:xil-auth ──► /api:xil-app ─┐
 agent / MCP client / curl ── X-Agent-Key ──► /api:xil-layer ─────────┤
                                                                      ▼
 POST /api:xil-{app,layer}/ask ──► xil_router (xano-free) ─► tool ┐
 POST /api:xil-layer/proxy ────────────────────────────────────┤
 MCP  xil_mcp_call_capability ─────────────────────────────────┤
                                                               ▼
                                                   xil_proxy_request
                                   resolve capability → check scope → call → audit → step
                                        │                                   │
                       transport "source"                     transport "external"
                                        ▼                                   ▼
                 <base>/api:xil-source-reference/…        Open-Meteo · CountriesNow
                 (this workspace in the demo,              open.er-api · DummyJSON
                  your source workspaces in production)    JSONPlaceholder (the only write)
  1. Identify the caller. Browser users sign up or log in through xil-auth; Xano hashes their password and issues a one-day token. Machine callers send an agent key (X-Agent-Key, or Authorization: Bearer xil_…) to xil-layer; xil_resolve_caller hashes it and looks it up. The raw key is never stored: what is kept is its SHA-256 hash plus a short, non-secret prefix so an operator can tell keys apart.
  2. Open a turn. POST ask registers a turn in xil_request_log, runs the router agent, and returns the answer with its tool calls, tool errors, and live steps. Browser turns carry user_id, and the app's history, step, and audit queries enforce that owner.
  3. Resolve, check, forward, record. Every tool is one call to xil_proxy_request, the single chokepoint. It looks the capability up in xil_capability, checks the caller's scopes (*:*, service:*, service:operation), builds the target URL, attaches the right downstream credential, calls it, and appends an audit row.
  4. Narrate it. xil_emit_step writes progress to xil_chat_step so a UI can show the turn as it runs (GET steps).

Scopes use a three-level grammar: *:* (everything), service:* (a whole service), service:operation (one capability). xil_check_scope is a pure function — no logging, no side effects — so the decision is easy to read and easy to test.

Multi-workspace routing lives in three columns on xil_capability: base_url (which instance answers), target_canonical (which API group there), and credential_ref (the name of the environment variable holding that workspace's key). Callers see none of it — the capability name is the whole interface, and the audit row is identical no matter which workspace answered.

Intent resolution is layered on purpose. The LLM picks tools when there is an LLM; POST resolve and the MCP xil_mcp_resolve_intent tool map free text to a capability using keyword rules with no model and no credentials at all, which is what keeps the registry usable from scripts, tests, and offline demos.

Mock data and failure are deliberately not the same thing

MOCK_MODE=true (or force_mock inside a workflow test) makes every capability serve a canned response. That is an explicit request for fake data, and mode says mock. The variable is not shipped — add it in the dashboard when you want it, since unset already means off.

A live call that times out or returns a bad status is not silently replaced with canned data. It returns __error with data: null, and an audit row of outcome: "error", mode: "live"; the upstream status and the technical reason travel in the response_status and reason fields rather than in the caller-facing message. A caller can therefore never mistake invented data for a real answer — fake data only ever arrives when it was asked for. The workflow test failed_call_errors_instead_of_faking_data locks this in.

Common use cases

  • An internal assistant over company data. Several teams want a chat assistant that can read orders, customers, and rep performance. Rather than giving the assistant a database connection, you register it as one agent with the read scopes it needs, and the audit trail shows exactly which records it touched.
  • Onboarding a third-party or vendor agent. A vendor's agent needs two capabilities and nothing else. POST agents/register issues it a key scoped to orders:search and clients:get; anything else it tries is refused before a call is forwarded, and the refusal is logged.
  • Letting Claude or Cursor into a real backend, safely. The MCP server exposes four generic governed tools instead of your data endpoints, so an MCP client discovers what it may do, calls through the same chokepoint, and reads back only its own audit rows.
  • Fronting several backend workspaces at once. Sales data lives in one Xano workspace, support data in another. Each capability row points at its own workspace with its own credential, so a leaked key from one does not open the others — and consumers keep calling one gateway.

Quick start

Start with your coding agent. Paste the prompt below into a local MCP-capable coding agent — Claude Code, Cursor, Windsurf, and others. It installs the Xano CLI, connects Xano, and imports this template.

Start building in Xano: Access the prompt at https://go.xano.co/start-xano-with-template-skill and run it here with governed-agent-data-gateway as your template.

The agent will walk you through the setup below.

  1. Deploy backend/ to a workspace (or install with the Xano template skill, which merges it into your existing workspace).
  2. Seed. Admin endpoints are gated by API_AUTH_SECRET when it is set, and answer anyone when it is not — so this call works either way (send the header if your install carries the sample value; omit it after a plain push). Seeding is idempotent; the first run returns two demo agent keys, shown once:
curl -X POST https://<instance>/api:xil-admin/seed -H "X-API-Key: CHANGE_ME-operator-secret"
  1. Check health — this one is public. source_reachable: true means the loopback hop to the reference group works, and seeded: true means step 2 landed. Expect two entries in warnings at this stage — one per security switch, saying it is unset (after a plain push) or still on the public sample value:
curl https://<instance>/api:xil-admin/health
  1. Ask something with the full-access key from step 2:
curl -X POST https://<instance>/api:xil-layer/ask \
  -H "X-Agent-Key: xil_…" -H "Content-Type: application/json" \
  -d '{"message":"Will the Nashua delivery get rained on in the next 3 days?"}'
  1. Show a denial. Repeat step 4 with the read-only key and ask it to file a ticket. The assistant explains the block; GET audit shows outcome: denied.
  2. Deploy the console. Build frontend/index.html with your instance URL and API group canonicals, upload it to Xano Static Hosting, then create a username/password account in it:
node scripts/build-static.mjs https://<instance>.xano.io
xano static_host build push <host-name> -d dist

[!IMPORTANT] A fresh install is wide open, and health will tell you so. The two security switches — API_AUTH_SECRET and XIL_SOURCE_KEY — are declared in backend/workspace/xil.xs with the public sample values CHANGE_ME-operator-secret and CHANGE_ME-source-key, so the wiring is demonstrated rather than merely described. Whether they reach your workspace depends on the install path: xano workspace push does not send environment variables unless you pass --env, so after a plain push both are unset — which is the most open state there is, because an unset API_AUTH_SECRET makes every anonymous caller an operator with *:*. An install that does carry them lands on the sample values, which are printed in this repo and therefore not secrets either.

GET /api:xil-admin/health is the source of truth for which state you are in: it returns demo_mode: true and a warnings array naming each switch that is unset or still on a sample value, and the console shows the same as an amber banner. warnings: [] is the finish line — see Going to production.

Environment variables are listed under Environment variables; none are required to boot, and two are required to be safe. Regenerate the publication bundle after backend edits with node scripts/build-multidoc.mjs.

API surface

xil-layer — caller identified by X-Agent-Key, or X-API-Key, or open when API_AUTH_SECRET is unset

Method Path Purpose
POST ask Question in, answer out via the router agent; returns turn_id, mode, tool_calls, tool_errors, steps
POST proxy Direct governed call: {capability, params, body?}
POST resolve Keyword rules map intent to a capability and report has_scope (no LLM)
GET capabilities Active registry rows with an allowed flag for the caller
GET steps Live step feed for one of your turns (after_id for polling); operators may read any
GET audit Paged audit log — your own rows, or all rows plus an agent_name filter for operators

xil-auth — local browser accounts; passwords are hashed by the xil_user auth table

Method Path Purpose
POST signup Create a username/password account and return a one-day auth token
POST login Verify local credentials and return a one-day auth token
GET me Return the signed-in user's safe profile fields

xil-app — requires Authorization: Bearer <user-token>

Method Path Purpose
POST ask Authenticated chat through the same governed router and proxy
GET history The signed-in user's recent messages and answers
GET capabilities Capability registry for the interactive user
GET steps Live steps, after verifying the turn belongs to the user
GET audit Only audit entries belonging to the signed-in user

xil-adminseed and agents/register require X-API-Key when API_AUTH_SECRET is set

Method Path Purpose
GET health Public. Mode, seeded state, counts, source reachability, the two security switches, uses_sample_secrets, per-source credential status, and a warnings array
POST seed Idempotent demo loader; issues demo agent keys on first run
POST agents/register Create or update an agent's scopes and mint a new key. Omitted fields keep their existing values

xil-source-reference (COPY-ME) — requires X-Xil-Source-Key when XIL_SOURCE_KEY is set

Method Path Purpose
GET health Liveness, record counts, and key_enforced
POST orders/search client_name?, product?, status?, branch?, limit?
POST clients/get company_name (partial)
GET branches/list All branches
POST reps/performance rep_name?, branch?; adds attainment_pct

Capabilities seeded (name → transport): orders.search, clients.get, branches.list, reps.performance → the source reference group; weather.forecast → Open-Meteo; countries.lookup → CountriesNow; fx.latest → open.er-api; catalog.search → DummyJSON; tickets.create → JSONPlaceholder (POST, the only write). All five outside services are keyless.

Header names are matched case-insensitively. Xano re-cases inbound header names (X-API-Key arrives as X-Api-Key), so every header read goes through xil_header, which compares on the lower-cased name. Send whatever casing you like.

Database Tables

Table Role
xil_user Username/password accounts for the hosted console
xil_agent Registered callers and their scope list
xil_agent_key SHA-256 hashes of issued keys (plus a short non-secret prefix); revoke by status
xil_capability The capability registry and routing table: service, operation, transport, target, base_url, credential_ref, path template, required scope, keywords
xil_audit_log Append-only: one row per governed call; indexed by agent_id and user_id so reads can be scoped to an owner
xil_request_log One row per inbound request; carries the turn's owner for private history and step access
xil_chat_step Live progress steps per turn
ref_branch, ref_client, ref_paper_order, ref_sales_rep COPY-ME demo data for Keystone Paper Supply Co.

MCP server

xil-gateway-mcp exposes four tools — xil_mcp_list_capabilities, xil_mcp_resolve_intent, xil_mcp_call_capability, xil_mcp_recent_audit — and nothing else. MCP clients never see the raw data tools; they get the governed generic surface, and every call lands in the same audit log as the console.

Pass agent_key as a tool argument. That is what decides the caller's scopes and whose audit rows come back. Per-tool Xano user auth can additionally be enabled in the dashboard (that setting is not expressible in XanoScript).

There are two ways to connect, and the console's Connect Claude page walks through both:

Client Endpoint Setup
Claude connectors (claude.ai, Claude Desktop) https://<your-worker>.workers.dev/mcp Claude connectors require OAuth and Xano MCP servers do not speak it, so you deploy a small Cloudflare Worker in front of the gateway: Add OAuth to a Xano MCP server. The URL shown in the console (xil-gateway-mcp-oauth-proxy.workers.dev) is an example of the shape yours will take — it is not a live endpoint.
Bearer-token clients (Cursor, MCP Inspector, custom) https://<instance>/x2/mcp/xil-gateway-mcp/mcp/stream Direct streamable HTTP, no Worker needed: Connecting MCP clients.

Going to production

Both security switches are declared with public sample values so the mechanism is demonstrated and closing the door is an edit rather than a discovery — but depending on your install path they may not have reached your workspace at all (see the note in Quick start). GET /api:xil-admin/health tells you where you stand — warnings: [] means production-shaped. The console shows an amber banner and a health chip until then.

Step What it fixes
1. Replace API_AUTH_SECRET in the layer workspace It ships as CHANGE_ME-operator-secret, which is printed in this repo — so until you replace it, treat the instance as open. Callers present X-Agent-Key (an agent) or X-API-Key (the operator), and MCP tools must be passed an agent_key. Deleting the variable entirely is worse, not better: anonymous callers then become operators with *:*.
2. Replace XIL_SOURCE_KEY in the layer and in every source workspace It ships as CHANGE_ME-source-key. Both sides must hold the same value. Deleting it makes xil_ref_check_source_key skip its check, and the xil-source-reference endpoints answer anyone who finds the URL.
3. Issue one narrow agent per consumer via POST agents/register Give each agent the smallest scope list that works. Do not hand out *:*. Keys are stored as SHA-256 hashes (plus a short non-secret prefix); rotate by re-registering, revoke by setting status: "revoked".
4. Give each source workspace its own credential_ref See Connecting your own source workspaces. One key per source, not one key for all of them.
5. Confirm health returns warnings: [], uses_sample_secrets: false, and every source_credentials entry is enforced: true This is the check that the previous four actually took effect.

Who can read the audit log. Reading the record of calls is governed as tightly as making them:

  • /api:xil-layer/audit and the MCP xil_mcp_recent_audit tool return only the calling key's own rows. The response carries scope: "self".
  • A caller holding *:* — an admin agent, the X-API-Key operator, or anyone at all while API_AUTH_SECRET is unset — sees the whole log (scope: "all") and may filter by agent_name.
  • /api:xil-layer/steps returns a turn only to the caller that created it, or to an operator.
  • Console rows belong to a signed-in user and are read through /api:xil-app/audit, filtered on $auth.id.

This is also why step 1 matters: while API_AUTH_SECRET is unset, everyone is an operator, so everyone sees everything.

Not included, and worth adding for a real deployment: per-agent rate limiting, key expiry, and a retention/redaction policy for request_payload.

Connecting your own source workspaces

The repo contains two clearly separated concerns, and only the first belongs in your gateway workspace:

Concern Prefix Stays where
The layer: users, agents, keys, capability registry, proxy, audit, live steps, router agent, MCP server tables/functions/tools xil_*, API groups xil-auth, xil-app, xil-layer, xil-admin your gateway workspace
Downstream reference group: the contract a source workspace exposes to the layer, with demo data for a fictional paper distributor tables ref_*, API group xil-source-reference, function xil_ref_check_source_key copied into each of your source workspaces. Every file is marked COPY-ME.

To point the layer at a real source instead of the loopback demo:

  1. Copy these files into the source workspace, then replace the ref_* queries with your own data:
    • backend/api/xil_source_reference/**
    • backend/table/ref_*.xs
    • backend/function/xil_ref_check_source_key.xs
  2. In the source workspace, set XIL_SOURCE_KEY to a shared secret. Its endpoints then require header X-Xil-Source-Key.
  3. In the layer workspace, set the same XIL_SOURCE_KEY, and set XIL_SOURCE_BASE_URL to the source instance origin.
  4. GET health should report source_reachable: true against the new base URL. Nothing else changes: the proxy, scopes, and audit are identical in loopback and production.

Fanning out to several source workspaces

Step 3 wires up one source. The registry is built to address many, and each one gets its own credential — a leaked key from one workspace must not open the others. Three columns on xil_capability decide where a row lands:

Column Meaning
base_url Instance origin of the workspace that answers this capability. null falls back to XIL_SOURCE_BASE_URL, then to this instance (loopback).
target_canonical API group canonical in that workspace. Defaults to xil-source-reference.
credential_ref Name of the environment variable holding that workspace's shared secret. null means XIL_SOURCE_KEY.

So a two-source install looks like this:

Capability base_url credential_ref Env var set in the layer
orders.search https://sales.xano.io XIL_KEY_SALES XIL_KEY_SALES=…
tickets.search https://support.xano.io XIL_KEY_SUPPORT XIL_KEY_SUPPORT=…

Each source workspace sets its own XIL_SOURCE_KEY to the value the layer holds for it. xil_source_key resolves the reference at call time and xil_proxy_request sends it as X-Xil-Source-Key.

Verify a reference actually resolves before you trust it. GET /api:xil-admin/health returns source_credentials, one entry per distinct credential_ref in the registry, with an enforced flag — names only, never values. An enforced: false on a named reference means that workspace is being called with no credential at all, and health raises a warning for it:

"source_credentials": [
  { "name": "XIL_KEY_SALES",   "enforced": true  },
  { "name": "XIL_KEY_SUPPORT", "enforced": false }
]

Testing

Eight workflow tests ship with the template. All of them are credential-free and LLM-free, so they run in a fresh workspace with nothing configured:

xano workflow_test run_all -w <workspace_id>

Xano plans cap how many workflow tests a workspace can store. If a push fails with Your plan only supports N workflow tests, that is the cap, not the code — push a subset of backend/workflow_test/ (each file is one self-contained test), run it, then swap in the rest. This is how the suite was verified during the build: two passes of four against byte-identical backend code.

Data-and-setup flows, against the seed data the repo ships:

  • seeded_source_data_answers_for_real — seeds the workspace, then reads the data back over real HTTP through the source contract with the shared source key, asserting the actual seeded counts. No mocks.
  • health_reports_the_layer_is_seeded_and_wired — seeds, then asserts health reports the registry populated, the demo data loaded, the loopback hop answering, and a warning that the shipped sample secrets are still in place.

Governance flows:

  • governed_call_is_forwarded_and_audited — an agent with orders:* calls orders.search with force_mock; expects mode: mock, data present, and an audit row of outcome: mock.
  • scope_denied_call_is_logged_not_forwarded — an agent without tickets:create calls it; expects a plain refusal in __error, the missing scope in reason, mode: denied, and an audit row with outcome: denied and no upstream status.
  • unknown_capability_is_refused_and_audited — a caller holding *:* asks for a capability that is not in the registry; expects a refusal and an audit row of outcome: error, proving the registry is the allow-list rather than the scope list.
  • intent_resolves_to_a_capability_without_a_model — free text resolves to orders.search and weather.forecast by keyword rules, nonsense resolves to nothing rather than a guess, and a read-only agent is told it lacks the write scope before any call is made.
  • agent_key_identifies_its_caller_and_can_be_revoked — issues a key, confirms it identifies its agent with exactly the scopes granted and nothing more, that the raw key is not recoverable from what was stored, then revokes it and confirms it stops working.
  • failed_call_errors_instead_of_faking_data — a capability pointed at an unroutable host; expects __error, data: null, mode: live, and an audit row of outcome: error. This is the guard that a broken upstream is never quietly answered with canned data.

What is not covered automatically. The router agent itself (ai.agent.run on xano-free) is exercised by hand against a live workspace, not in the suite — a model's wording is not a stable assertion. The five keyless public APIs are likewise reached by hand and in the console; the suite deliberately does not depend on the open internet. Everything the tests do assert runs unattended, with no credentials.

Environment variables

Declared in backend/workspace/xil.xs and editable in the dashboard under Workspace settings → Environment Variables.

Values in the table below are what backend/workspace/xil.xs declares. A plain xano workspace push does not send environment variables (that needs --env), so after one both switches are unset until you set them in the dashboard — which is also why a push can never overwrite values you already have.

Variable Declared as Effect
API_AUTH_SECRET CHANGE_ME-operator-secret Gates seed and agents/register, and is the operator identity for xil-layer, sent as X-API-Key. Replace it — the shipped value is public. Unset entirely = anonymous callers become operators with *:*.
XIL_SOURCE_KEY CHANGE_ME-source-key Default shared secret the reference group checks in X-Xil-Source-Key. Replace it, in the layer and in every source workspace. Unset entirely = the source endpoints answer anyone.
MOCK_MODE not declared Add it yourself; an unset MOCK_MODE behaves exactly like "false". "true" makes every capability serve canned data instead of calling out. Explicit only — failures are never quietly mocked, and this is the only runtime path to canned data.
XIL_SOURCE_BASE_URL not declared Default instance origin of the source workspace; unset = loopback to this instance.
XIL_KEY_<NAME> not declared Any variable named by a capability's credential_ref, for per-source credentials. See Fanning out.

The router agent runs on xano-free, so no model key is needed. To use another provider, edit backend/ai/agent/xil_router.xs: set type to openai, anthropic, or google-genai and add api_key: $env.<YOUR_KEY>.

Notes and limits

  • API group canonicals are unique per Xano instance, not per workspace. The template ships them blank so Xano assigns a fresh one per install; read the real ones in the dashboard and use them in the frontend build and in any capability target_canonical.
  • The credential boundary is described, not demonstrated, in the shipped template. The reference group lives in the same workspace, so the loopback hop is real HTTP but not a real trust boundary. Connecting your own source workspaces is the production shape.
  • credential_ref is resolved dynamically ($env[<name>]). The default XIL_SOURCE_KEY is read statically and is always correct; confirm any named reference resolves via source_credentials in GET health before relying on it.
  • Tools run inside ai.agent.run lose the endpoint's input context, so the router passes turn_id into every tool call. The fallback in xil_current_turn is used only when exactly one request is running; it never guesses between concurrent users.
  • This is a tested starting point, not a compliance product. It gives you one enforcement point and a complete record of calls; retention, redaction, rate limiting, key expiry, and any formal certification are yours to add.

Frequently asked questions

How do I stop one AI agent from reading data it should not see?
Register the agent with a narrow scope list via POST agents/register, and the gateway refuses anything outside it before a call is forwarded. Scopes use three levels -- *:* for everything, service:* for a whole service, service:operation for one capability -- checked by xil_check_scope on every call. A refused call still writes an audit row with outcome 'denied' and the missing scope, so attempts are visible rather than silent.
The receipt is written on the same code path as the call, inside xil_proxy_request, so the record cannot drift from what happened. Each xil_audit_log row carries the caller, the capability, the outcome (allowed, denied, error, or mock), whether the data was live or canned, the upstream HTTP status, latency, and the request payload. The table is append-only -- the layer never edits or deletes a row.
Yes. Each xil_capability row carries base_url (which instance answers), target_canonical (which API group there), and credential_ref -- the NAME of the environment variable holding that workspace's key. So sales data and support data can live in different workspaces with different secrets, and a leaked key from one does not open the others. GET health lists every credential_ref with an 'enforced' flag, names only, so you can confirm each one resolves before trusting it.
The MCP server xil-gateway-mcp exposes four generic governed tools -- list capabilities, resolve an intent, call a capability, read recent audit rows -- and nothing else. MCP clients never see the underlying data endpoints; they discover what they may do, call through the same chokepoint as every other surface, and read back only their own audit rows. Claude connectors need an OAuth proxy in front (Xano MCP servers speak bearer tokens), which the console's Connect Claude page walks through.
No, and that is deliberate. A live call that times out or returns a bad status comes back as an error with data null and an audit row of outcome 'error', mode 'live'; the status and reason travel in their own fields rather than in the caller's message. Canned data is served only when it was explicitly asked for, via MOCK_MODE or a test's force_mock. The workflow test failed_call_errors_instead_of_faking_data locks this in.
Everything in the demo runs credential-free: the router uses Xano's free built-in model, the five outside services are keyless public APIs, the console uses local username/password accounts, and the downstream system is a reference API group in the same workspace reached over real HTTP. All eight workflow tests pass with nothing configured. What needs your input is production hardening: replace the two shipped sample secrets, issue narrow per-consumer agent keys, and point capability rows at your real source workspaces.
Insert a row in xil_capability -- name, service, operation, transport (source or external), method, path template, required scope, and keywords -- and it is callable immediately, with no deploy. The keywords also make it discoverable by the keyword-rules intent resolver, which runs with no model at all. If you want the router agent to reach for it in chat, add a small tool that makes one call to xil_proxy_request, the way the nine shipped tools do.
Every template runs on any Xano plan, including the free tier. All you need is a Xano account and a coding agent connected to the CLI and Developer MCP.
You can import and run it with your local coding agent — it works against the seed data it ships with. Paste this prompt into your agent: Start building in Xano: Access the prompt at https://go.xano.co/start-xano-with-template-skill and run it here with governed-agent-data-gateway as your template. You can also do a manual clone from Github, still using the Xano CLI and Developer MCP to connect to Xano to build and deploy.
Yes — that's the intent. The backend is plain XanoScript that you can read and AI can build on: add or edit tables, endpoints, and logic, or swap integrations. It ships with tests you can extend as you go.
Xano gives you an enterprise-grade backend — database, APIs, and logic — that AI can build and you can visually inspect, edit, and trust. Hosted, scalable, and production-ready from day one. Speed of generation, without the black box.

Get started for free today

Xano gives you everything you need to ship modern applications—fast, securely, and at scale.

Get started