Developers

Turrigan API reference

Turrigan is one HTTP call placed in a path you already control. POST an AI output, or a proposed agent tool call, to /v1/govern and get back allow, block, or escalate in milliseconds, screened by a deterministic core with no LLM in the hot path. Everything else on this page reads back what happened: the decisions, the evidence, the conformity posture and the integrity of the log itself.

This is a readable rendering of the live OpenAPI specification, published here because an interactive reference is JavaScript and cannot be read by anything that does not run it. The interactive version, with request bodies you can send, is at api.turrigan.com/docs, and the raw spec is at api.turrigan.com/openapi.json.

Base URL and authentication

The base URL is https://api.turrigan.com. Governance calls authenticate with a scoped, expiring, revocable API key in the X-API-Key header. Keys are least-privilege: a key minted for Guard ingest can post guard events and read its own entitlement and nothing else, and cannot read decisions or govern. Console and management calls use a bearer token from a user session.

Requests are bounded at 200 KB, rate isolation is per tenant so one caller cannot degrade another, and the governance path fails closed: an unreachable datastore can never silently allow.

The one call that matters

curl https://api.turrigan.com/v1/govern \
  -H "X-API-Key: trg_..." \
  -H "Content-Type: application/json" \
  -d '{
    "input":  "user question",
    "output": "what your AI is about to say",
    "profile": "default",
    "language": "en"
  }'

The response carries the verdict, whether the output may be delivered, which controls were triggered, and which detectors flagged. What is stored is redacted and hash-chained, so the record proves the decision without retaining the personal data that caused it.

Governance

The chokepoint. One call per AI output or per proposed agent action, returning allow, block or escalate.

POST /v1/govern

Govern an AI output (allow / block / escalate)

The inline chokepoint. Submit the model input and output; the deterministic detector floor runs (PII, verifiable claims, prompt injection, safety, uncertainty, fairness tagging) and an immutable, redacted decision is appended to the hash-chained log. `delivered` is true only for `allow`. `escalate` and `block` enter the human-oversight queue. **Advisory endpoint:** the returned verdict is a RECORD; this endpoint does not itself prevent the caller from sending data anywhere. If you supply a `destination_key`, which is decisive only when the destination registry is enabled for your tenant, the destination's approved status is OPERATOR-ATTESTED, not verified by Turrigan, and a raised verdict on a floor-tripping class to a destination attested external-unapproved is evidentiary. How far the verdict is raised depends on the finding: a confirmed special-category finding blocks, which of the Article 9 subtypes means health; a probable special-category finding escalates for biometric identification, health, religious or philosophical belief and sexual orientation; and a probable finding for any other special-category subtype does not raise the verdict at all; a probable criminal-offence finding escalates; high-harm-identifier and children's-data findings escalate rather than blocking, because neither is a measured detection of the output. Egress is enforced only on the governed proxy surface, and what it enforces is ROUTING: the request is forwarded solely to the resolved destination and an unresolved route hard-fails. A destination the operator registered but classed external-unapproved is still forwarded to, and the raised verdict withholds the reply rather than preventing that transfer. **Idempotency:** send an optional `Idempotency-Key` header to make retries safe: a replay of the same key returns the ORIGINAL decision (`idempotent_replay: true`) instead of appending a duplicate; reusing a key with a different body is 422, and a still-in-flight key is 409.

Authentication: scoped API key, bearer token

Clean output → allow

{
  "input": "What are your opening hours?",
  "output": "We are open Sunday to Thursday, 8am to 3pm.",
  "profile": "default",
  "language": "en"
}

Leaks PII (ID + card) → block

{
  "input": "Confirm the account holder for this claim.",
  "output": "The policy holder is Ahmed Al-Rashidi, Emirates ID 784-1990-1234567-1, card 4111 1111 1111 1111.",
  "profile": "default",
  "language": "en"
}

POST /v1/govern/action

Authorize a proposed agent action (tool call)

Plane C: an agent submits a proposed tool call before executing it. Checks: tool allow-list (unlisted blocks), privilege scope (exceeding granted scopes escalates), parameter risk, plus the content floor over the parameters. Turrigan authorizes the DECISION to act; it never executes the action. `granted_scopes` must be attested by the calling service, not forwarded from the agent. If you supply a `destination_key`, its approved status is OPERATOR-ATTESTED, not verified by Turrigan; this endpoint is advisory: it records a decision, it does not prevent egress, and it never executes the action. **Idempotency:** an optional `Idempotency-Key` header makes retries safe: a replay returns the original decision (`idempotent_replay: true`) rather than appending a duplicate.

Authentication: scoped API key, bearer token

Allow-listed tool, in-scope → allow

{
  "tool": "read_record",
  "parameters": {
    "record_id": 4821
  },
  "agent_id": "assistant-1",
  "granted_scopes": [
    "read"
  ],
  "profile": "default"
}

Allow-listed tool needs a scope the agent lacks → escalate

{
  "tool": "update_record",
  "parameters": {
    "record_id": 4821,
    "field": "status",
    "value": "reviewed"
  },
  "agent_id": "assistant-1",
  "granted_scopes": [
    "read"
  ],
  "profile": "default"
}

Turrigan Guard (input-side)

Ingest for the Turrigan Guard browser extension, which governs what goes into an AI app rather than what comes out.

GET /v1/guard/entitlement

Guard subscription entitlement (polled by the extension)

Returns whether the paid Guard capability is active for this tenant, the paid-through instant, and the offline grace window (days) the extension applies before treating an unreachable check as lapsed. On lapse the enterprise extension degrades to on-device blocking; it does not stop protecting the user.

Authentication: scoped API key

GET /v1/guard/events

List Guard audit events for the tenant console (newest first)

Tenant console read of THIS organisation's Guard audit events, strictly org-scoped (an explicit tenant filter, never a request parameter). Masked samples only, never raw PII. Optional filters: `host` (exact AI app host), `q` (free-text over the stamped user/org identifiers), and a `since`/`until` received-at range (ISO 8601). Cursor pagination via `before_id` - pass the response's `next_before_id` for the next older page. Console (JWT) auth only: a `guard:ingest` API key cannot read.

Authentication: bearer token

POST /v1/guard/events

Record a Guard PII-exposure event (enterprise, subscription-gated)

The Turrigan Guard browser extension posts one MINIMISED event here when a user proceeds past both PII-review prompts. The body is the `turrigan.guard.audit/v1` contract: detector types plus MASKED samples only, never raw PII (a non-masked sample is 422). The event is appended to the tenant's tamper-evident, hash-chained guard-events log. **Subscription-gated:** an inactive/expired advance-paid subscription is 402 and a suspended tenant is 403 (nothing is written). **Idempotent:** re-posting the same client event `id` returns the original `event_uid` with `deduped: true` instead of double-recording.

Authentication: scoped API key

User proceeded past both PII prompts (masked samples only)

{
  "schema": "turrigan.guard.audit/v1",
  "id": "b7f3c1a2d4e54f6a8b9c0d1e2f3a4b5c",
  "event": "pii_proceeded",
  "app": {
    "host": "chatgpt.com"
  },
  "findings": [
    {
      "type": "email",
      "count": 1,
      "sample": "\u2022\u2022\u2022\u2022.com"
    },
    {
      "type": "credit_card",
      "count": 1,
      "sample": "\u2022\u2022\u2022\u20224242"
    }
  ],
  "mode": "enterprise",
  "extVersion": "0.3.0"
}

GET /v1/guard/events/verify

Verify the tenant's Guard audit chain is untampered

Recomputes the per-tenant, tamper-evident hash chain over the guard-events log and returns whether every link is intact, plus the event count. Powers the console 'Verify audit chain' button. Org-scoped to the caller's tenant.

Authentication: bearer token

Decisions & Explanations

Read back any governed decision, with a deterministic, human-readable reason for the verdict.

GET /v1/decisions

List governed decisions (newest first)

Authentication: bearer token

GET /v1/decisions/{uid}

Full decision detail (redacted, with evidence and version set)

Authentication: bearer token

GET /v1/decisions/{uid}/content-credential/verify

Verify a decision's content-provenance mark (EU AI Act Art 50(2))

Recomputes the C2PA mark's COSE signature and assertion binding, checks the provenance-hash sealed into the version-set, and confirms the mark re-derives from the decision's recorded fields. ok=false (not an error) when the decision has no mark.

Authentication: bearer token

POST /v1/decisions/{uid}/erase

GDPR erasure of one decision's content, crypto-shred (auditor)

GDPR right-to-erasure via crypto-shredding: destroy this decision's encryption key. The content becomes permanently unrecoverable; the append-only record + hash chain stay intact and verifiable. Logged to the (control-plane) security audit log.

A2: refused (423) while an active legal hold or an open incident covers the decision, a legal hold trumps an erasure request until it is lifted.

Authentication: bearer token

GET /v1/decisions/{uid}/explanation

Right-to-explanation for a decision (deterministic)

Authentication: bearer token

POST /v1/decisions/{uid}/hold

Place a legal/investigation hold on a decision (auditor)

While the hold is active, a crypto-shred of this decision is refused (423), a legal hold trumps an erasure request until it is released. Placing the hold and its reason are recorded in the tamper-evident security log.

Authentication: bearer token

Hold for an open investigation

{
  "reason": "Under regulatory investigation, do not erase",
  "reference": "CASE-2026-0042"
}

GET /v1/holds

List active legal holds for this tenant (auditor)

Active legal holds for this tenant (decision- and tenant-scoped).

Authentication: bearer token

POST /v1/holds/{hold_id}/release

Release a decision-scoped legal hold (auditor)

Releases a DECISION-scoped hold. Tenant-scoped holds are placed and released only by a super-admin, so an auditor cannot lift a platform-level hold.

Authentication: bearer token

Release once the matter is closed

{
  "reason": "Investigation closed; hold lifted"
}

Conformity & Evidence

Live conformity posture per control, and evidence export including the EU AI Act Annex IV core pack.

GET /v1/conformity/posture

Conformity posture for a framework

Per-control coverage (covered / not covered, honestly reported) and live evidence counts, derived from the framework's versioned control mappings.

Authentication: bearer token

GET /v1/evidence/control/{framework}/{control_key}

Evidence items for one control (auditor)

Authentication: bearer token

GET /v1/evidence/export

Bulk evidence export: Annex IV core (JSON) or CSV (auditor)

`format=annexiv` returns the EU AI Act Annex IV core technical-documentation pack as JSON (see the AnnexIVPack schema). `format=csv` returns `text/csv` with X-Evidence-* coverage headers. Every export is written to the security audit log.

Authentication: bearer token

GET /v1/tenant/export

GDPR data-portability export of this tenant's decisions (auditor)

GDPR data-portability: the calling tenant's governed decisions with decrypted detail + explanations. Auditor/admin scoped; logged.

Authentication: bearer token

Audit Integrity

Verify the hash chain, anchor checkpoints, and detect the exact position of any tampering.

POST /v1/audit/anchor

Sign the current chain head into a checkpoint (auditor)

Sign the current chain head into an append-only, externally-publishable checkpoint. For a BYO tenant, also RETAIN the signed head in the control plane (SC1-D) so it survives deletion of the customer-side anchor.

Authentication: bearer token

GET /v1/audit/anchors

List checkpoints and verify signatures against the live chain (auditor)

List checkpoints + verify each signature and that the signed head still matches the live chain (a post-checkpoint rewrite shows head_matches_chain=false).

Authentication: bearer token

GET /v1/audit/control-plane

Full integrity check: hash chain + control-plane anchors (auditor)

Runs BOTH the hash-chain verification (edit/reorder/corruption) and the control-plane anchor cross-check (wholesale rewrite/truncation of the anchored prefix); ok requires both. control_plane.unanchored_suffix is the written-but-not-yet-anchored tail; control_plane.anchored=false means anchoring not yet enabled.

Authentication: bearer token

GET /v1/audit/merkle

Merkle transparency checkpoint over the audit chain (auditor)

A compact Merkle root over every audit-chain record, for periodic publication to an external transparency sink plus O(log n) inclusion proofs (A7).

Authentication: bearer token

GET /v1/audit/verify

Verify the decision log's hash chain

Recomputes content hashes and links over the append-only log. Any edit, delete, or reorder is reported with the break position.

Authentication: bearer token

Human Oversight

The review queue for escalated decisions, with a mandatory justification on resolution.

GET /v1/review/pending

Escalated / blocked decisions awaiting human review

Authentication: bearer token

POST /v1/review/{uid}/claim

Claim a pending decision for review (reviewer)

Authentication: bearer token

POST /v1/review/{uid}/resolve

Resolve a reviewed decision (reviewer; justification is mandatory)

Authentication: bearer token

Approve after human review

{
  "action": "approve",
  "justification": "Reviewed; content is safe to deliver."
}

Reject the output

{
  "action": "reject",
  "justification": "Confirmed PII leak; must not be sent."
}

Incidents (Art. 73)

GET /v1/incidents/open

Open incidents (with 15-day deadlines)

Authentication: bearer token

POST /v1/incidents/{uid}/classify

Classify an incident; 'serious' starts the Art. 73 clock

Classify a candidate incident. 'serious' starts the 15-day Art-73 reporting clock.

Authentication: bearer token

Classify as serious (starts the 15-day clock)

{
  "classification": "serious"
}

POST /v1/incidents/{uid}/dismiss

Dismiss a candidate incident

Dismiss a candidate incident as a false positive - leaves the open queue.

Authentication: bearer token

Dismiss a candidate incident

{
  "reason": "False positive on review."
}

POST /v1/incidents/{uid}/mark-reported

Record that the incident was reported to the authority (auditor)

Authentication: bearer token

Record the authority report

{
  "authority": "MoI",
  "reference": "INC-2026-0142"
}

GET /v1/incidents/{uid}/report

Pre-filled Art. 73 serious-incident report (auditor)

Authentication: bearer token

POST /v1/incidents/{uid}/report

Raise an incident on a decision

Authentication: bearer token

Raise a serious incident

{
  "severity": "serious",
  "description": "Model disclosed customer PII in production."
}

Drift (Art. 72)

POST /v1/drift/baseline

Capture a golden-set baseline of governor behaviour (auditor)

Authentication: bearer token

GET /v1/drift/check

Check current behaviour against the latest baseline (auditor)

Authentication: bearer token

Fairness

GET /v1/fairness

Disparate-impact screening over governed outcomes (auditor)

Batch disparate-impact screening over governed-decision outcomes (auditor-scoped).

Authentication: bearer token

Catalog & Profiles

GET /v1/catalog/controls

Loaded controls

Authentication: bearer token

GET /v1/catalog/detectors

Registered detectors with versions

Authentication: bearer token

GET /v1/catalog/frameworks

Loaded frameworks

Authentication: bearer token

GET /v1/profiles

Sector / jurisdiction profiles (conformity lens)

Sector/jurisdiction profiles (a conformity lens of applicable frameworks).

Authentication: bearer token

Security Log

GET /v1/security/events

This tenant's security audit log (auditor)

The tamper-evident security audit log for the CALLING tenant (auditor/admin): auth, access denials, exports, erasures, integrity checkpoints. Org-scoped (an auditor is a per-tenant role); metadata only - no secrets/PII.

Authentication: bearer token

GET /v1/security/verify

Verify the security log's hash chain (auditor)

Verify the security log's hash chain (any edit/reorder/delete is detected). Integrity is global; the reported length is scoped to the calling tenant.

Authentication: bearer token

Metrics

GET /v1/metrics

Tenant metrics: decisions by verdict, queues, incidents (auditor)

Operational metrics for the calling tenant (org-scoped) + global integrity stats - a text-based surface for dashboards/alerting (a SIEM/Prometheus exporter is the ops add).

Authentication: bearer token

Auth & Session

POST /api/auth/change-password

Change the current user's password

Change the authenticated user's password.

Enforces: - Current password must be correct. - New password must differ from the current one. - New password must not match any of the last PASSWORD_HISTORY_COUNT passwords. - Passwords match (new == confirm). After success, password_changed_at is updated and the old password is archived.

Authentication: bearer token

Rotate the current user's password

{
  "current_password": "old-password",
  "new_password": "a-strong-new-passphrase",
  "confirm_new_password": "a-strong-new-passphrase"
}

POST /api/auth/login

Log in (username + password, optional MFA code)

Username + password

{
  "username": "admin",
  "password": "your-password"
}

With an MFA code

{
  "username": "admin",
  "password": "your-password",
  "mfa_code": "123456"
}

POST /api/auth/logout

Revoke the current session token

Revoke the presented token id (jti) so it can't be reused before expiry.

Authentication: bearer token

GET /api/auth/me

Current session identity

Authentication: bearer token

POST /api/auth/mfa/disable

Disable MFA for the current user

Disable MFA - requires a valid current code (can't be turned off by a stolen session alone).

Authentication: bearer token

Disable with a current TOTP code

{
  "code": "123456"
}

POST /api/auth/mfa/enroll

Start TOTP MFA enrolment (returns the otpauth URI)

Begin TOTP enrolment. Requires encryption (the seed must be sealable). If MFA is ALREADY active, a valid current code is required to re-enrol, and the new seed is only STAGED (mfa_pending_secret) - the active second factor is never cleared until the new seed is proven via /mfa/verify. This blocks a stolen session from turning MFA off.

Authentication: bearer token

First enrolment (no code needed)

{}

POST /api/auth/mfa/verify

Confirm MFA enrolment with a TOTP code

Activate MFA by proving the PENDING (newly enrolled) seed; promote it to active.

Authentication: bearer token

Confirm enrolment with a TOTP code

{
  "code": "123456"
}

Users (self-service)

GET /api/users

List this organization's users

Authentication: bearer token

POST /api/users

Create a user (returns a one-time temporary password)

Create a user in the admin's organization. Returns a one-time temporary password.

Authentication: bearer token

Create a reviewer

{
  "username": "j.doe",
  "email": "j.doe@example.gov",
  "role": "reviewer"
}

GET /api/users/{user_id}

Get one user

Authentication: bearer token

PATCH /api/users/{user_id}

Change a user's role or active flag

Update a user's role and/or active state. An admin cannot change their OWN role or deactivate themselves (prevents accidental self-lockout and self-escalation loops).

Authentication: bearer token

Change role

{
  "role": "auditor"
}

Deactivate (access cutoff)

{
  "active": false
}

POST /api/users/{user_id}/reset-password

Reset a user's password (returns a new one-time temporary password)

Issue a fresh one-time password for a user in the admin's org (returned once).

Authentication: bearer token

API Keys (self-service)

GET /api/keys

List this org's keys

Authentication: bearer token

POST /api/keys

Mint a scoped API key (secret shown once)

Authentication: bearer token

Governance key, 30-day expiry

{
  "name": "prod-assistant",
  "scopes": [
    "govern"
  ],
  "expires_in_days": 30
}

POST /api/keys/cmk

Switch this org to customer-managed key custody

Switch the caller org to customer-managed-key custody (true sovereignty). The tenant TEK is re-wrapped under a customer key from the KMS provider; existing data stays readable. Managed is the default; this is the opt-in upgrade.

Authentication: bearer token

Point at your own KMS key

{
  "key_ref": "aws-kms:arn:aws:kms:me-central-1:123456789012:key/abcd-1234"
}

POST /api/keys/cmk/revoke

Revoke the customer key: crypto-shreds this tenant (irreversible)

Customer-key revocation = crypto-shred this tenant (irreversible). Destroys the customer key so all the tenant's content becomes unrecoverable; the append-only chain stays intact.

A2: refused (423) while an active legal hold or an open incident covers the tenant, the BYOK "delete during retention / dispute" prohibition.

Authentication: bearer token

GET /api/keys/custody

Key custody status (managed or customer-managed)

Authentication: bearer token

POST /api/keys/{key_id}/revoke

Revoke a key immediately

Authentication: bearer token

Datastores (BYO)

GET /api/datastores

Current datastore mode (pooled or byo)

Authentication: bearer token

POST /api/datastores

Register this org's own database (DSN validated, migrated, sealed)

Validate + migrate + register the caller org's client-hosted DB. The DSN is sealed at rest; only non-secret metadata (driver/host) is ever returned or logged.

Authentication: bearer token

Register your own Postgres

{
  "dsn": "postgresql://user:pass@db.internal:5432/turrigan",
  "tls_required": true
}

DELETE /api/datastores

Deregister the client database (data stays with you)

Stop routing to the client DB (future govern goes back to pooled). The customer's data stays in THEIR database - only our registry pointer + cached engine are dropped.

Authentication: bearer token

POST /api/datastores/health

Datastore health: reachability, freshness, anchoring lag (+ ?verify_chain deep check)

On-demand health of the caller org's BYO datastore (D6-SC-2): reachability, freshness (write-lag), and anchoring lag; plus an opt-in deep chain + anchor-signature verification (`?verify_chain=true`).

FAIL-OPEN-STRUCTURED: an unreachable/degraded datastore is a normal 200 body (`reachable=false` + a closed-enum `reason_code`), NOT an error -- so the admin can SEE the state. This is DELIBERATELY unlike the govern chokepoint, which fails CLOSED (503) on an unreachable BYO DB (H-iii); a monitor must read the `reachable`/`status` field, not the HTTP status. The report is content-free and read-only. Rate- limited per org (it opens a live connection to the customer's DB).

Authentication: bearer token

POST /api/datastores/test

Liveness probe for the registered client database

Liveness probe for the caller org's client DB (fails CLOSED if unreachable).

Authentication: bearer token

Health

GET /api/health

Liveness + active LLM provider seam

Request a pilot key