faz
Usage

REST Endpoints

Every HTTP endpoint faz serve exposes — paths, request shapes, response envelopes, status codes.

faz serve runs a FastAPI app on 127.0.0.1:8787. Every endpoint is under the /v1 prefix. The contract is identical to the MCP tools — same Pydantic schemas, same envelopes — so picking REST over MCP is just a transport choice.

For setting up REST as your client transport, see REST API integration.

Endpoint summary

MethodPathPurpose
GET/v1/healthLiveness probe.
GET/v1/databasesPaginated database list with discovered schemas.
GET/v1/databases/{db}/tables/{table}One table's schema in detail.
POST/v1/query/simpleSingle-database query through the safety pipeline.
POST/v1/queryMulti-step federated query.
GET/v1/results/{request_id}Paginated retrieval of a stored federated result.

GET /v1/health

Liveness probe. Returns instantly without pinging databases.

curl http://localhost:8787/v1/health
response
{
  "status": "ok",
  "version": "0.1.0",
  "databases_connected": 2
}

databases_connected is the count of databases that connected successfully at startup. Use it as a smoke test, not as definitive health for any single connection — for that, hit /v1/databases and check the reachable field per entry.

GET /v1/databases

Paginated list of every configured database — including ones that failed to connect at startup, which appear with reachable: false and an error message. Each entry includes discovered tables and column metadata.

Query paramTypeDefaultNotes
pageinteger11-indexed page number.
sizeinteger4Databases per page. Max 100. Keeps responses bounded.
curl 'http://localhost:8787/v1/databases?page=1&size=10'
response
{
  "databases": [
    {
      "name": "<database>",
      "type": "postgresql",
      "database": "<db-name>",
      "reachable": true,
      "tables": [
        {
          "name": "<table>",
          "fields": [
            {
              "name": "<column>",
              "data_type": "integer",
              "nullable": false,
              "is_primary": true,
              "is_foreign_key": false,
              "description": "",
              "distribution": null
            }
          ],
          "row_count_estimate": 8421,
          "description": ""
        }
      ],
      "error": null
    }
  ],
  "pagination": {
    "page": 1,
    "size": 10,
    "total_databases": 2,
    "total_pages": 1,
    "has_more": false
  }
}

Loop until pagination.has_more is false. Status code is always 200 — connector failures are reported in-line, not as HTTP errors.

GET /v1/databases/{db}/tables/{table}

One table's schema in full detail. Same shape as a single entry in /v1/databases.tables[], returned at the top level.

curl http://localhost:8787/v1/databases/<database>/tables/<table>
response
{
  "name": "<table>",
  "fields": [
    {
      "name": "<column>",
      "data_type": "integer",
      "nullable": false,
      "is_primary": true,
      "is_foreign_key": false,
      "description": "",
      "distribution": {
        "distinct_count": 8421,
        "null_fraction": 0.0,
        "sample_values": [1, 2, 3, 4, 5],
        "min_value": 1,
        "max_value": 8421
      }
    }
  ],
  "row_count_estimate": 8421,
  "description": ""
}

Returns 404 if the database isn't configured or the table isn't found in the connector's discovered schema.

POST /v1/query/simple

Run one query through the safety pipeline. The primary endpoint for single-database operations.

request
curl -X POST http://localhost:8787/v1/query/simple \
  -H 'Content-Type: application/json' \
  -d '{
    "database": "<database>",
    "table": "<table>",
    "language": "sql",
    "query": "SELECT * FROM <table> LIMIT 5"
  }'
FieldTypeRequiredNotes
databasestringyesConnector name from faz.yaml.
tablestringyesDeclared target table.
languagestring | nullnosql, mql, cypher, es_dsl, vector, dynamo, couchdb. Inferred from connector when omitted.
querystringyesNative query body. Format depends on language — see MCP tools for per-language formats.

Success — 200

{
  "request_id": "req_a1b2c3d4e5f6",
  "status": "ok",
  "data": {
    "columns": ["<column-1>", "<column-2>"],
    "rows": [
      { "<column-1>": "<value>", "<column-2>": "<value>" }
    ],
    "row_count": 1
  },
  "safety": {
    "stages_passed": ["PROMPT_GUARD", "RBAC_GATE", "AST_CHECKER", "INJECTION_ANALYSER", "GUARDRAILS"],
    "warnings": []
  },
  "metadata": {
    "database": "<database>",
    "execution_time_ms": 23.4,
    "transport": "rest/local",
    "steps": [],
    "merge_latency_ms": null
  }
}

Blocked — 403

{
  "request_id": "req_a1b2c3d4e5f6",
  "status": "blocked",
  "error": {
    "stage": "RBAC_GATE",
    "reason": "Access denied: <database>.<sensitive-table> requires permission for SELECT; policy grants none",
    "suggestion": null
  }
}

The status code differentiates: 200 for safe execution (with success or warnings), 403 when the safety pipeline rejected the query, 400 for malformed requests, 404 for unknown database, 500 for downstream connector failures during execution.

POST /v1/query

Multi-step federated query. Each step runs against its declared connector; results land in DuckDB tables s0, s1, ... and the optional merge SQL joins them.

request
curl -X POST http://localhost:8787/v1/query \
  -H 'Content-Type: application/json' \
  -d '{
    "steps": [
      {
        "step_id": "s0",
        "database": "<database-1>",
        "table": "<table-1>",
        "query": "SELECT <link-column>, <other-column> FROM <table-1> WHERE <condition>"
      },
      {
        "step_id": "s1",
        "database": "<database-2>",
        "table": "<collection>",
        "language": "mql",
        "query": "{\"operation\": \"find\", \"filter\": {}}",
        "depends_on": ["s0"],
        "link_from": "<link-column>",
        "link_to": "<foreign-key>"
      }
    ],
    "merge": "SELECT s1.<column>, s0.<other-column> FROM s0 JOIN s1 ON s0.<link-column> = s1.<foreign-key>"
  }'

Request body and per-step fields are documented on MCP tools › query. The conceptual model is on Federated queries.

The response envelope is the same QueryResponseSuccess / QueryResponseBlocked shape as /v1/query/simple, but metadata.steps is populated with per-step latencies.

If any step touches a denied table under DenyPolicy.PARTIAL (the default), it's silently stripped and the merge runs without it — metadata.steps will be missing the stripped entry but the request returns 200.

If the request needs to return more rows than the inline response carries (over the row cap), the result is stored in a scratchpad keyed by request_id. Retrieve subsequent pages via GET /v1/results/{request_id}.

GET /v1/results/{request_id}

Paginated retrieval of a stored federated result.

Path paramTypeNotes
request_idstringThe request_id from a previous POST /v1/query response.
Query paramTypeDefaultNotes
pageinteger11-indexed page number.
sizeinteger100Rows per page. Max 10,000.
curl 'http://localhost:8787/v1/results/req_a1b2c3d4e5f6?page=2&size=500'
response
{
  "request_id": "req_a1b2c3d4e5f6",
  "page": 2,
  "size": 500,
  "columns": ["<column-1>", "<column-2>", "<column-3>"],
  "rows": [ { "<column-1>": "<value>", "<column-2>": "<value>", "<column-3>": "<value>" } ],
  "has_more": true
}

Returns 404 if the request_id has expired or never existed. Scratchpads are short-lived; pull pages soon after the initial query.

Status code reference

CodeWhen
200Successful query, allowed by the safety pipeline.
400Malformed request body. Pydantic validation errors, missing required fields, or invalid IR (cycle in depends_on, duplicate step_id, unknown reference).
403Safety pipeline blocked the query. Body includes { status: "blocked", error: { stage, reason, suggestion } }.
404Database not in faz.yaml, table not in the connector's discovered schema, or request_id expired.
500Connector failure during execution (database unreachable mid-query, malformed merge SQL). The request passed safety but failed downstream.

Authentication

There isn't any. faz binds 127.0.0.1 by default. Anything off-loopback prints a warning at startup; if you're exposing faz remotely, put a reverse proxy with auth in front (nginx, Cloudflare Tunnel, Tailscale). See REST API integration for the deployment pattern.

On this page