Skip to main content
Retrieval-governance endpoints are mounted under /recall/*, with the versioned audit-log read at /v1/recall/audit-log. The base URL is https://api.visiqlabs.com. Action governance and retrieval governance share a single rule engine — each rule declares which facets it applies to. The endpoints on this page are the retrieval facet: they govern what your agents can see (retrieved documents, tool results, rendered prompts), with decisions allow, deny, redact, and escalate.

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. Either a vq_prod_... / vq_test_... key minted in the dashboard under Settings → Harness Keys, or the allow_... key returned once by POST /allow/agents. On the retrieval surface the operational allowlist covers exactly POST /recall/evaluate and GET /recall/rules/bundle — calling any other endpoint on this page with a harness key 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.
Scoped keys are additionally checked against their granted scopes. Evaluation requires rules:evaluate (or the legacy recall:write); reads require rules:read (or the legacy recall:read); rule mutations require rules:write (or the legacy recall:write); full_access satisfies everything. A key without the required scope receives 403 {"error": "insufficient_scope"} listing the required and granted scopes.

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

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

Evaluation

The endpoints your SDK or harness calls at runtime. Both accept a harness key. The SDK evaluates most retrievals locally from the cached rule bundle — the server and the SDK run the same policy interpreter over the same rule source, so local and remote decisions agree by construction.

POST /recall/evaluate

Evaluate a retrieval against your suppression rules and return a decision. Scope: rules:evaluate or recall:write Request body:
Agent attributes are server-authoritative. The platform looks up agent_id in the agent registry and hydrates its assigned trust tier, categories, and business function into rule input (input.agent.*). An assigned trust tier overrides the request’s trust_tier — the body value is only a fallback for agents with no assigned tier, so varying it in a request cannot spoof a tier-gated rule. Unlike the action facet’s evaluate endpoint, this endpoint does not auto-provision unknown agents.
For an agent in monitor mode, a non-allow decision is normalized to allow before it is recorded and returned, with reason_code: "MONITOR_MODE" and the engine’s original reason preserved behind a Monitor: prefix.
Response:
Decision semantics:
  • allow — the content passes to the agent unchanged.
  • deny — the content is suppressed; the agent never sees it.
  • redact — the content proceeds with the accompanying redaction_rules applied.
  • escalate — hold the content for human review; if no human approves in time, fall back per hitl_fallback.
Redaction directives. Each entry in redaction_rules may include field, pattern, replacement, mode (full | partial | email | custom), keepFirst, keepLast, maskChar, and keepPattern. full replaces the whole match (the default); partial keeps the first/last characters; email keeps the first character of the local part plus the domain; custom keeps every keepPattern match and masks the rest. A redact decision from a rule whose directives resolve to nothing passes content through unchanged — unless the rule is flagged fail-closed, in which case the decision is downgraded to deny server-side. The escalate decision tells your harness to hold the content for human review — this endpoint does not itself notify approvers; register the approval through the human-in-the-loop flow (see Human-in-the-Loop). hitl_fallback tells the harness what to do when no human responds in time: deny (the default, fail-closed) or mask (proceed with the accompanying redaction_rules applied). Reason codes:
The response carries no receipt ID — tamper evidence is asynchronous. For organizations with artifact signing enabled, each decision emits a record envelope that receives an Ed25519 receipt off the request path, is Merkle-batched under a KMS-signed root with an RFC 3161 timestamp, and lands in a hash-chained checkpoint log. Verify a decision’s record envelope server-side via GET /record/envelopes/:id/verify (:id is the record envelope’s UUID, not the decision_id returned here).
Minimum-necessary at rest: the decision is evaluated against the real values, but the persisted copies of resource_metadata and query are masked — first by the matched rule’s redaction directives, then by an always-on value-shape redaction floor. The raw original is discarded by default; organizations can opt in to retaining it encrypted, revealable only via the retained-original endpoint.
Status codes: 200 OK, 400 Bad Request, 401 Unauthorized, 403 Forbidden (insufficient scope), 429 Too Many Requests, 500 Internal Server Error Example:

GET /recall/rules/bundle

Fetch your enabled retrieval rules as one bundle for local evaluation. Clients can cache this bundle and revalidate it with If-None-Match, so retrieval decisions on the hot path never wait on the network. (The VisIQ SDK consumes the unified GET /rules/bundle, which carries both facets.) Request headers: Response:
Response headers: Status codes: 200 OK, 304 Not Modified, 401 Unauthorized, 403 Forbidden (insufficient scope), 500 Internal Server Error

Rules

Rule management. These are management endpoints — a harness key receives 403 harness_key_not_permitted here. The recommended authoring path is the dashboard’s rule editor (natural-language compile, visual condition builder, and a Simulate panel that checks a new rule against your recent traffic — rules projected to inhibit more than 5% of it are blocked). These endpoints are the programmatic equivalent.

GET /recall/rules

List retrieval rules, sorted by priority descending — the same order the engine evaluates them in (first match wins). Query parameters: page (default 1), limit (default 50, max 100) 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 /recall/rules

Create a retrieval rule. Request body:
rego_source holds the rule’s policy source in the platform’s Rego-subset condition language (equality, inequality, set membership, startswith/endswith/contains, regex, counts, and numeric comparisons). The decision (allow, deny, redact, escalate) is derived from the policy body. Prefer POST /recall/rules/compile — the compiler drafts and validates the policy against the live evaluation engine.
Response (201): the created rule object, including rego_source. Status codes: 201 Created, 400 Bad Request, 401 Unauthorized, 500 Internal Server Error

POST /recall/rules/compile

Compile a natural-language description into a retrieval rule. The compiler reads your existing rules for context, drafts the policy source, and validates it against the live evaluation engine. Rate limit: 10 compile requests per minute per organization — exceeding it returns 429. Query parameters: stream=true — stream the compile over Server-Sent Events instead of a single JSON response (also triggered by Accept: text/event-stream). Request body:
Response:
Streaming response (?stream=true): a text/event-stream of JSON events —
On failure the stream emits {"type":"error","message":"..."} and closes. Status codes: 200 OK, 400 Bad Request, 401 Unauthorized, 422 Unprocessable Entity (the compiler could not produce a valid policy — refine the prompt), 429 Too Many Requests, 500 Internal Server Error, 503 Service Unavailable (rule compilation is not configured in this environment)

GET /recall/rules/:id

Get a single rule by UUID, including rego_source. Status codes: 200 OK, 400 Bad Request, 401 Unauthorized, 404 Not Found, 500 Internal Server Error

PUT /recall/rules/:id

Update a rule. Same fields as POST /recall/rules, all optional; at least one is required. Only provided fields change. trust_tier, surface, and principal_exclusions accept null to clear. Response: the updated rule object. Status codes: 200 OK, 400 Bad Request, 401 Unauthorized, 404 Not Found, 500 Internal Server Error

DELETE /recall/rules/:id

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

Emergency Bypass

Temporarily suspend one rule’s enforcement during an incident — see Emergency Bypass for when and why. Bypasses auto-expire; expiry is checked at evaluation time, so there is no revocation lag. Both activation and deactivation are written to the audit log with reason code EMERGENCY_BYPASS.

POST /recall/rules/:id/bypass

Activate an emergency bypass on a rule. While active, any request that reaches the bypassed rule in the priority cascade returns allow with reason code EMERGENCY_BYPASS — even requests the rule’s conditions would not have matched — and lower-priority rules never run for it. Higher-priority rules keep enforcing. Request body:
Response: the full rule object with the bypass fields populated:
Status codes: 200 OK, 400 Bad Request, 401 Unauthorized, 404 Not Found, 409 Conflict (a bypass is already active on this rule), 500 Internal Server Error

DELETE /recall/rules/:id/bypass

Deactivate an active bypass before it expires. Response: the full rule object with the bypass fields cleared. Status codes: 200 OK, 400 Bad Request, 401 Unauthorized, 404 Not Found, 409 Conflict (no active bypass on this rule), 500 Internal Server Error

Audit Log


GET /v1/recall/audit-log

Query the retrieval decision audit log. Every decision made through this endpoint — allow and deny, including monitor-mode observations and bypass activations — is recorded. (Locally evaluated SDK decisions surface through the runtime telemetry pipeline instead.) The log is append-only; no writes are accepted via this API.
Tamper evidence comes from the signed record pipeline, not from the query API: for organizations with artifact signing enabled, each decision emits a record envelope that receives an asynchronous Ed25519 receipt, is Merkle-batched under a KMS-signed root with an RFC 3161 timestamp, and lands in a hash-chained checkpoint log. Verify a decision’s record envelope server-side via GET /record/envelopes/:id/verify.
Scope: rules:read or recall:read (management keys / dashboard session — harness keys cannot query the log) Query parameters: Rows recorded under monitor mode carry reason_code: "MONITOR_MODE"; it is not yet accepted as a filter value. Response:
metadata.query and metadata.resource_metadata are the masked persisted copies (rule directives plus the always-on redaction floor); metadata.floor_detectors names any floor detectors that fired, and metadata.decision_id is the underlying decision’s ID for the retained-original reveal below. receipt_id is a legacy column and is null on new rows — receipts flow through the record pipeline instead. Status codes: 200 OK, 400 Bad Request, 401 Unauthorized, 403 Forbidden (insufficient scope), 429 Too Many Requests, 500 Internal Server Error

Retained Originals


GET /recall/decisions/:id/unmask

Reveal the retained, encrypted original payload for one retrieval decision. Persisted copies are masked at rest; when your organization has enabled retained-original storage (off by default), the raw resource_metadata and query are additionally stored AES-256-GCM-encrypted, and this endpoint decrypts them. This is a management endpoint gated on the payloads:unmask permission — only explicitly-authorized roles can ever see the original values, and every reveal is logged with who requested it. Harness keys receive 403. Path parameter: :id — the decision UUID (decision_id from POST /recall/evaluate, or metadata.decision_id on an audit-log row) Response:
Status codes: 200 OK, 400 Bad Request (invalid UUID), 401 Unauthorized, 403 Forbidden, 404 Not Found (unknown decision, or retention is disabled for your organization), 500 Internal Server Error