Hall API Reference
The Hall API is an HTTP interface to the Hall registry and routing decision log. Each organization runs their own local instance. It is the backing service for Hall Monitor.
Start the Server
pip install pyhall-wcp flaskpython -m hall_api.server# [hall-api] PyHall API v0.3.0 starting on http://127.0.0.1:8765Environment Variables
| Variable | Default | Description |
|---|---|---|
HALL_API_PORT | 8765 | Port to listen on. |
HALL_API_HOST | 127.0.0.1 | Host to bind. |
HALL_DB_PATH | hall.db | Path to the SQLite database. |
CORS is configured to allow any origin. Restrict to your own domain in production.
Endpoints
GET /health
Health check. Returns 200 ok immediately with no database access.
Response 200:
{ "status": "ok", "timestamp": "2026-02-28T12:00:00.000000+00:00", "hall_hash": "sha256:...", "hall_hash_banned": false}curl:
curl http://localhost:8765/healthGET /status
Hall status summary with enrollment and decision counts.
Response 200:
{ "hall_version": "0.3.0", "hall_name": "PyHall", "workers_enrolled": 3, "decisions_total": 142, "decisions_today": 17, "denials_today": 2, "timestamp": "2026-02-28T12:00:00.000000+00:00", "online": true, "account_standing": "ok", "standing_checked_at": "2024-01-01T00:00:00Z"}| Field | Type | Description |
|---|---|---|
hall_version | string | PyHall server version. |
hall_name | string | Always "PyHall". |
workers_enrolled | int | Total workers enrolled in the registry. |
decisions_total | int | Total routing decisions recorded. |
decisions_today | int | Routing decisions made today (UTC). |
denials_today | int | Denied decisions today (UTC). |
timestamp | string | ISO 8601 UTC timestamp. |
online | bool | true when the Hall is serving requests. |
account_standing | string | Account standing from the pyhall.dev registry ("ok", "degraded", etc.). |
standing_checked_at | string | ISO 8601 UTC timestamp of the last standing check. |
curl:
curl http://localhost:8765/statusGET /workers
List all enrolled workers.
Response 200:
{ "workers": [ { "worker_id": "org.example.my-summarizer", "worker_species_id": "wrk.doc.summarizer", "capabilities": ["cap.doc.summarize"], "risk_tier": "low", "enrolled_at": "2026-02-28T10:00:00.000000+00:00", "status": "active" } ], "count": 1}| Field | Type | Description |
|---|---|---|
workers | array | List of enrolled worker objects. |
workers[].worker_id | string | Unique worker instance identifier. |
workers[].worker_species_id | string | WCP worker species ID. |
workers[].capabilities | array[string] | Capability IDs this worker handles. |
workers[].risk_tier | string | Risk tier declared at enrollment. |
workers[].enrolled_at | string | ISO 8601 UTC enrollment timestamp. |
workers[].status | string | Always "active" in v0.1. |
count | int | Total workers returned. |
curl:
curl http://localhost:8765/workersGET /dispatches
Recent routing decisions, newest first.
Query parameters:
| Parameter | Type | Default | Max | Description |
|---|---|---|---|---|
limit | int | 50 | 200 | Number of decisions to return. |
Response 200:
{ "dispatches": [ { "decision_id": "550e8400-e29b-41d4-a716-446655440000", "decided_at": "2026-02-28T12:00:00.000000+00:00", "capability_id": "cap.doc.summarize", "tenant_id": "acme-corp", "env": "dev", "denied": 0, "deny_reason": null, "selected_worker": "wrk.doc.summarizer", "blast_score": 2, "artifact_hash": "sha256:abc123..." } ], "count": 1}| Field | Type | Description |
|---|---|---|
decision_id | string | UUID v4 decision identifier. |
decided_at | string | ISO 8601 UTC timestamp. |
capability_id | string | Capability that was requested. |
tenant_id | string | Requesting tenant. |
env | string | Environment (dev, stage, prod, edge). |
denied | int | 0 = allowed, 1 = denied. |
deny_reason | string|null | Denial reason string, or null. |
selected_worker | string|null | Dispatched worker species, or null. |
blast_score | int|null | Computed blast score (0–100). |
artifact_hash | string|null | SHA-256 of the routing artifact. |
curl:
curl "http://localhost:8765/dispatches?limit=20"GET /dispatches/active
Currently in-flight dispatches. In v0.1, this is a stub that always returns an empty list.
Response 200:
{ "dispatches": [], "count": 0}curl:
curl http://localhost:8765/dispatches/activeGET /alerts
Recent governance alerts, newest first. Returns up to 50 alerts.
Response 200:
{ "alerts": [ { "alert_id": "550e8400-e29b-41d4-a716-446655440001", "created_at": "2026-02-28T12:00:00.000000+00:00", "level": "warning", "code": "DENY_WORKER_TAMPERED", "message": "Worker code hash mismatch detected for wrk.doc.summarizer", "worker_id": "org.example.my-summarizer", "capability_id": "cap.doc.summarize" } ], "count": 1}| Field | Type | Description |
|---|---|---|
alert_id | string | UUID v4 alert identifier. |
created_at | string | ISO 8601 UTC timestamp. |
level | string | Alert severity level. |
code | string | Alert code identifier. |
message | string | Human-readable alert message. |
worker_id | string|null | Affected worker, if applicable. |
capability_id | string|null | Affected capability, if applicable. |
curl:
curl http://localhost:8765/alertsPOST /enroll
Enroll a worker in the registry. If the worker is already enrolled (same worker_id), the record is updated.
Request body:
{ "worker_id": "org.example.my-summarizer", "worker_species_id": "wrk.doc.summarizer", "capabilities": ["cap.doc.summarize"], "risk_tier": "low", "currently_implements": ["ctrl.obs.audit-log-append-only"], "artifact_hash": "sha256:abc123..."}| Field | Type | Required | Description |
|---|---|---|---|
worker_id | string | yes | Unique worker instance identifier. Must not be empty. |
worker_species_id | string | yes | WCP worker species ID. Must not be empty. |
capabilities | array[string] | yes | Non-empty list of capability IDs. |
artifact_hash | string | yes | sha256: + hex digest of the record (without this field, sorted keys). |
risk_tier | string | no | Risk tier. Stored as-is. |
currently_implements | array[string] | no | Control IDs the worker implements. |
| (other fields) | any | no | Any additional fields are stored in the full record. |
Computing artifact_hash:
import hashlib, json
record = { "worker_id": "org.example.my-summarizer", "worker_species_id": "wrk.doc.summarizer", "capabilities": ["cap.doc.summarize"], "risk_tier": "low",}payload = json.dumps(record, sort_keys=True, separators=(",", ":")).encode()artifact_hash = "sha256:" + hashlib.sha256(payload).hexdigest()Response 201 (new enrollment):
{ "enrolled": true, "enrollment_id": "550e8400-e29b-41d4-a716-446655440002", "worker_id": "org.example.my-summarizer", "enrolled_at": "2026-02-28T12:00:00.000000+00:00", "artifact_hash_verified": true}Response 200 (update — worker already enrolled):
{ "enrolled": true, "updated": true, "worker_id": "org.example.my-summarizer", "artifact_hash_verified": true}Response 400 errors:
| Code | Condition |
|---|---|
{"error": "worker_id is required"} | worker_id is empty or missing. |
{"error": "worker_species_id is required"} | worker_species_id is empty or missing. |
{"error": "capabilities must be a non-empty list"} | capabilities is empty or missing. |
{"error": "artifact_hash is required..."} | artifact_hash is missing. |
{"error": "artifact_hash mismatch", "code": "DENY_WORKER_TAMPERED"} | Hash does not match computed value. |
curl:
curl -X POST http://localhost:8765/enroll \ -H "Content-Type: application/json" \ -d '{ "worker_id": "org.example.my-summarizer", "worker_species_id": "wrk.doc.summarizer", "capabilities": ["cap.doc.summarize"], "risk_tier": "low", "artifact_hash": "sha256:YOUR_HASH_HERE" }'POST /decisions/ingest
Record a routing decision produced by the PyHall router. Used to push decisions from your routing code into the Hall API for display in Hall Monitor.
Request body:
{ "decision_id": "550e8400-e29b-41d4-a716-446655440000", "decided_at": "2026-02-28T12:00:00.000000+00:00", "capability_id": "cap.doc.summarize", "tenant_id": "acme-corp", "env": "dev", "denied": false, "deny_reason": null, "selected_worker": "wrk.doc.summarizer", "blast_score": 2, "artifact_hash": "sha256:abc123..."}| Field | Type | Required | Description |
|---|---|---|---|
decision_id | string | no | UUID v4. Generated server-side if omitted. |
decided_at | string | no | ISO 8601 timestamp. Defaults to server time. |
capability_id | string | no | Capability that was requested. |
tenant_id | string | no | Requesting tenant. |
env | string | no | Environment. Defaults to "dev". |
denied | bool | no | Whether the request was denied. |
deny_reason | string|null | no | Denial reason if denied. |
selected_worker | string|null | no | Dispatched worker species. |
blast_score | int|null | no | Blast score (0–100). |
artifact_hash | string|null | no | SHA-256 of the routing artifact. |
Duplicate decision_id values are silently ignored (INSERT OR IGNORE).
Response 201:
{ "recorded": true, "decision_id": "550e8400-e29b-41d4-a716-446655440000"}Response 500:
{ "error": "database error message"}curl:
curl -X POST http://localhost:8765/decisions/ingest \ -H "Content-Type: application/json" \ -d '{ "capability_id": "cap.doc.summarize", "tenant_id": "acme-corp", "env": "dev", "denied": false, "selected_worker": "wrk.doc.summarizer" }'POST /api/route
Allows any language to submit a routing decision to Hall Server over HTTP. Returns the governance decision with selected worker species, denial reasons, and routing metadata.
Auth: Authorization: Bearer <token> header or pyhall_session cookie required.
Request body:
{ "capability_id": "cap.doc.summarize", "env": "dev", "data_label": "PUBLIC", "tenant_id": "org.acme"}| Field | Type | Required | Description |
|---|---|---|---|
capability_id | string | yes | Capability identifier (e.g. cap.doc.summarize) |
env | string | yes | Environment: dev, staging, or prod |
data_label | string | yes | Data classification: PUBLIC, INTERNAL, or CONFIDENTIAL |
tenant_id | string | yes | Tenant or org identifier for routing scope |
tenant_risk | string | no | Tenant risk level. Defaults to "low". |
qos_class | string | no | QoS class for priority routing. Defaults to "P2". |
correlation_id | string | no | Caller-supplied trace ID. Auto-generated (UUID v4) if omitted. |
_rules | object | no | Custom routing rules document. If omitted, rules are auto-generated from enrolled workers. |
Response 200:
Returns a full RouteDecision object as JSON.
{ "decision_id": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-02-28T12:00:00Z", "correlation_id": "abc123", "tenant_id": "org.acme", "capability_id": "cap.doc.summarize", "matched_rule_id": "auto.cap.doc.summarize.wrk.doc.summarizer", "env": "dev", "data_label": "PUBLIC", "denied": false, "deny_reason_if_denied": null, "selected_worker_species_id": "wrk.doc.summarizer", "artifact_hash": "sha256:..."}| Field | Type | Description |
|---|---|---|
decision_id | string | UUID v4 for this routing decision |
timestamp | string | ISO 8601 UTC timestamp of the decision |
correlation_id | string | Propagated from request (or auto-generated) |
capability_id | string | The capability that was requested |
denied | boolean | false = routed; true = blocked by policy |
deny_reason_if_denied | object | null | Denial detail if denied; null when allowed |
selected_worker_species_id | string | null | Assigned worker species ID; null if denied |
artifact_hash | string | null | SHA-256 of the serialized RouteInput |
Response 400: Missing required field. Body: {"error": "missing field: <field_name>"}.
Response 401: Missing or invalid auth token or session cookie.
Response 500: Internal routing error. Body: {"error": "<message>"}.
curl:
curl -X POST http://localhost:8765/api/route \ -H "Authorization: Bearer <session_token>" \ -H "Content-Type: application/json" \ -d '{ "capability_id": "cap.doc.summarize", "env": "dev", "data_label": "PUBLIC", "tenant_id": "org.acme" }'See Integrations for language-specific examples (LangGraph, CrewAI, AutoGen, raw HTTP).
GET /openapi.json
Returns the full OpenAPI 3.0 spec for all Hall Server endpoints. Use this to auto-generate clients or validate route schemas.
Auth: No auth required (read-only spec).
Response 200:
{ "openapi": "3.0.0", "info": { "title": "Hall Server API", "version": "0.3.0", "description": "PyHall Hall Server — local API for Hall Monitor and orchestrator integrations" }, "paths": { "/api/route": { "...": "..." }, "/health": { "...": "..." } }}curl:
curl http://localhost:8765/openapi.jsonDatabase Schema
The server uses SQLite at HALL_DB_PATH (default: hall.db). Three tables are created automatically on startup.
-- Enrolled workersCREATE TABLE enrollments ( enrollment_id TEXT PRIMARY KEY, worker_id TEXT NOT NULL UNIQUE, worker_species_id TEXT NOT NULL, capabilities TEXT NOT NULL, -- JSON array risk_tier TEXT NOT NULL, enrolled_at TEXT NOT NULL, artifact_hash TEXT NOT NULL, record_json TEXT NOT NULL -- full record JSON);
-- Routing decisionsCREATE TABLE decisions ( decision_id TEXT PRIMARY KEY, decided_at TEXT NOT NULL, capability_id TEXT NOT NULL, tenant_id TEXT NOT NULL DEFAULT '', env TEXT NOT NULL DEFAULT 'dev', denied INTEGER NOT NULL DEFAULT 0, deny_reason TEXT, selected_worker TEXT, blast_score INTEGER, artifact_hash TEXT);
-- Governance alertsCREATE TABLE alerts ( alert_id TEXT PRIMARY KEY, created_at TEXT NOT NULL, level TEXT NOT NULL, code TEXT NOT NULL, message TEXT NOT NULL, worker_id TEXT, capability_id TEXT);