Skip to main content
The unified rule surface is mounted under /rules/*, with the single evaluation call at /evaluate. The base URL is https://api.visiqlabs.com. One rules collection spans every operation. Each rule declares an operations[] array (its stored applies_to) drawn from action, retrieval, and delegation, and the engine evaluates one event against the operations it carries.
The per-facet management endpoints — /allow/rules (action) and /recall/rules (retrieval) — are additive, permanent compatibility aliases over the same rules table, not deprecated surfaces. A rule created here appears there and vice versa; issued keys keep their existing grants. Use whichever surface fits your integration.

Authentication

All endpoints require a Bearer credential: Authorization: Bearer <key>. Requests without a valid credential receive 401 Unauthorized. Two credential audiences exist:
  • Harness keys — the operational credential your SDK or harness runs with. On this surface a harness key covers exactly GET /rules/bundle and POST /evaluate; calling the rule-management endpoints (GET/POST /rules, GET/PUT/DELETE /rules/:id) with one returns 403 {"error": "harness_key_not_permitted"}.
  • Management keys — general automation credentials governed by explicit permission grants. They can call every endpoint on this page. Management keys are launching soon: they are visible in the dashboard under Settings → API Keys, but creating one is not yet enabled. Until then, drive the management endpoints from the dashboard, which authenticates with your session.
The management CRUD reuses the action-facet permissions (allow_rules:view / :create / :update / :delete), so a key or role that already manages action rules manages unified rules unchanged. Evaluation is scope-gated: POST /evaluate accepts the operation-native rules:evaluate or the legacy allow:write / recall:write; full_access satisfies everything.

Rate limiting

Every API-key request passes a per-key sliding-window rate limit (default 600 requests per 60 seconds). Responses carry X-RateLimit-Limit and X-RateLimit-Remaining headers; exceeding the window returns 429 with a Retry-After header and body {"error": "rate_limited", "detail": "API key rate limit exceeded.", "retryAfter": <seconds>}.

List responses

The list endpoint returns the standard envelope:
Pagination is controlled by page (default 1) and limit (default 50, max 100) query parameters.

Evaluation


POST /evaluate

The single SDK-facing evaluation call for every governed event. The request declares which operations the event performs, and the response carries the union decision vocabulary — so one contract spans every facet. This is a pure projection onto the same plane handlers the per-facet evaluate endpoints use: there is exactly one decision implementation. Scope: rules:evaluate, allow:write, or recall:write Request body (operation-native form):
A delegation operation projects onto the action facet (it therefore requires target_app and action), which runs the delegation-facet policy check — the orchestrate.delegate authority transfer. This is the stateless policy leg; the grant/token lifecycle lives on the delegation endpoints.
A legacy kind-discriminated form is still accepted and behaves identically: { "kind": "action", ... } or { "kind": "retrieval", ... }. The operations[] form is canonical.
Response (single-operation):
The union decision vocabulary: Hybrid events. When operations includes both action and retrieval, both facets are evaluated and the response carries a per-operation results[] plus an overall decision equal to the most restrictive facet outcome (fail-closed combine):
Status codes: 200 OK, 400 Bad Request (invalid body), 401 Unauthorized, 403 Forbidden (insufficient scope), 429 Too Many Requests, 500 Internal Server Error

GET /rules/bundle

Fetch the compiled unified rule bundle for local evaluation — every enabled rule whose applies_to overlaps the action ∪ retrieval facets, plus the agent’s mode and attributes. This is the bundle the tagged SDK runtime pulls; it caches it and revalidates with If-None-Match so decisions on the hot path never wait on the network. Permission: allow_rules:view (a harness key passes the audience gate here) Query parameters: Request headers: Response:
A shut-down agent (operator kill-switch) receives a terminal bundle — enforce mode, zero rules, all-deny no-coverage — so an SDK that honours the shutdown flag stops immediately, and one that ignores it still denies everything.
Response headers: ETag (quoted SHA-256), Cache-Control: private, max-age=60 Status codes: 200 OK, 304 Not Modified, 400 Bad Request (missing/invalid agent_id), 401 Unauthorized, 404 Not Found (unknown agent after provisioning), 500 Internal Server Error

Rules

Management endpoints — a harness key receives 403 harness_key_not_permitted here. One collection over every operation; the applies_to array on each rule decides which facets it governs.

GET /rules

List rules, priority-descending, optionally filtered to a facet. Permission: allow_rules:view Query parameters: page (default 1), limit (default 50, max 100), operations (optional CSV of action,retrieval,delegation — returns only rules whose applies_to overlaps the set) Response:
The list omits rego_source; fetch a single rule to read the policy source. Status codes: 200 OK, 400 Bad Request, 401 Unauthorized, 500 Internal Server Error

POST /rules

Create a rule. The operations[] array is stored as the rule’s applies_to. Permission: allow_rules:create Request body:
Response (201): the created rule object, including rego_source. Status codes: 201 Created, 400 Bad Request (invalid JSON or body), 401 Unauthorized, 409 Conflict (a rule with this name already exists), 500 Internal Server Error

GET /rules/:id

Get a single rule by UUID, including rego_source. Permission: allow_rules:view Status codes: 200 OK, 400 Bad Request (invalid rule ID), 401 Unauthorized, 404 Not Found, 500 Internal Server Error

PUT /rules/:id

Update a rule. Every field from POST /rules is accepted and optional; at least one is required (an empty body returns 400 No fields to update). Only provided fields change; supplying operations rewrites applies_to. Permission: allow_rules:update Response: the updated rule object. Status codes: 200 OK, 400 Bad Request, 401 Unauthorized, 404 Not Found, 409 Conflict (duplicate name), 500 Internal Server Error

DELETE /rules/:id

Delete a rule permanently. Permission: allow_rules:delete Response: { "deleted": true } Status codes: 200 OK, 400 Bad Request (invalid rule ID), 401 Unauthorized, 404 Not Found, 500 Internal Server Error

Errors & conventions

Every endpoint on this page follows the platform-wide REST conventions — the validation-error body shape, the /v1/ vs unversioned split, and the API stability policy. See REST API conventions.