source

Ardur Personal Hub HTTP API

The Hub is the local service started by `ardur hub`. It accepts evidence

The Hub is the local service started by ardur hub. It accepts evidence from the browser extension, desktop observer, native-messaging host, and CLI adapter, and signs that evidence into the same Execution Receipt chain as the protocol path. This page is the API reference; for setup see ../guides/ardur-personal-hub.md .

Source: python/vibap/personal_hub.py .

Bind Address

Defaults: 127.0.0.1:8765. Loopback only — the Hub is not designed to be exposed beyond the local machine. CORS is restricted to chrome-extension://, moz-extension://, and loopback HTTP origins.

Authentication

Every endpoint except GET /health requires the Hub token written by ardur setup. Provide it one of three ways:

WhereHow
Header (preferred)X-Ardur-Hub-Token: <token>
Header (alternate)Authorization: Bearer <token>
Query (only for GET / and GET /dashboard)?token=<token>

The token is compared with constant-time secrets.compare_digest. Missing or incorrect tokens return:

HTTP/1.1 401 Unauthorized
{
  "ok": false,
  "error": "Ardur Personal Hub token required",
  "error_code": "hub_auth_required"
}

Endpoints

GET /health and GET /healthz

Unauthenticated liveness check.

{
  "ok": true,
  "schema_version": "<hub-schema>",
  "version": "<package-version>"
}

GET /, GET /dashboard

HTML dashboard summarising the latest session reviews. Authentication allowed via header or ?token=. Response is text/html with strict CSP (default-src 'none', no JS).

GET /v1/status

Returns Hub state suitable for ardur status:

{
  "ok": true,
  "schema_version": "...",
  "version": "...",
  "home": "/Users/.../.vibap",
  "verifier_id": "...",
  "hub_url": "http://127.0.0.1:8765",
  "sessions": 0,
  "session_reviews": 0,
  "latest_receipt": { ... } | null,
  "public_key_pem": "-----BEGIN PUBLIC KEY----- ...",
  "adapters": {
    "browser": "available",
    "desktop": "available_with_macos_permissions",
    "cli": "available"
  }
}

GET /v1/export

Returns the full evidence export — sessions, session reviews, latest receipts. Used by ardur status --export and the dashboard view.

POST /v1/sessions/start

Idempotent. Creates or returns a Hub-managed session bound to a Mission Passport. The Hub issues a passport scoped to local-observation tooling (browser_observe, desktop_observe, cli_command, cli_observe) when the caller does not supply one.

Request:

{
  "source": { "type": "browser" | "desktop" | "cli", ... },
  "session": { "title": "..." },
  "mission": {
    "agent_id": "...",
    "mission": "...",
    "allowed_tools": [...],
    "forbidden_tools": [...],
    "resource_scope": [...],
    "max_tool_calls": 5000,
    "max_duration_s": 86400,
    "allowed_side_effect_classes": [
      "none", "internal_write", "external_send", "state_change"
    ]
  }
}

Response:

{
  "ok": true,
  "ardur_session_id": "<jti>",
  "agent_id": "...",
  "mission": "...",
  "source": { ... },
  "title": "...",
  "started_at": "<iso8601>",
  "token": "<JWT>",
  "existing": false
}

existing: true is returned if a session with the same (source, session) key already exists.

POST /v1/events/observe

The main evidence-ingestion endpoint. Implicitly calls start_session if needed, evaluates the policy, runs the governance proxy, and appends a receipt to the chain.

Request schema invariants:

  • source.type MUST be one of browser, desktop, cli.
  • event.content_digest, when set, MUST match sha-256:<hex>.
  • raw_content_included: true is rejected; send digests and consented excerpts only.
  • A text snapshot in event requires event.consent.visible_text == true.

Response includes the policy decision (compliant, violation, insufficient_evidence), the chained receipt id, and an updated session review summary.

POST /v1/policy/check

Evaluate the policy for a candidate event without recording a receipt.

Request mirrors observe. Response:

{
  "ok": true,
  "policy": {
    "decision": "compliant" | "violation" | "insufficient_evidence",
    "reason": "...",
    "tool_name": "...",
    "arguments": { ... }
  }
}

POST /v1/sessions/{ardur_session_id}/attest

Issue a behavioral attestation summarising the receipt chain for an existing session. Used by ardur attest and by browser/desktop adapters that want to end a session cleanly.

Response is the attestation envelope (signed JWT + decoded claims).

Error Format

Every non-success response uses the same shape:

{
  "ok": false,
  "error": "<human-readable>",
  "error_code": "<stable-code>"
}

Stable error codes used today:

CodeStatusMeaning
hub_auth_required401Token missing or incorrect
body_too_large413Body exceeded MAX_BODY_BYTES
state_corrupt500A persisted JSON file failed to parse
hub_unavailableUsed by client helpers when the Hub does not respond
internal_error500Catch-all server boundary

Validation failures from observe use the error field with HTTP 400 and no specific code (the server returns the parser’s exception message).

Security Notes

  • The Hub token is generated locally by ardur setup and stored under the Ardur home directory. Treat it as a local secret.
  • The dashboard uses a strict Content-Security-Policy and serves no JS, so the ?token= query mode is acceptable for browser bookmarking.
  • CORS is intentionally restricted to extension origins and loopback origins. Other origins are denied with no Access-Control-Allow-Origin header.
  • The Hub does not control hidden provider-side behavior. It governs only what local adapters expose to it. See ../known-limitations.md .