Policies API
Quick example
- curl
- Python
- Node.js
curl "$TRINITITE_BASE/v1/policies" \
-H "Authorization: Bearer $TRINITITE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "acme-pii-and-tone",
"source_documents": ["s3://acme-policies/handbook.pdf"],
"auto_generate_tests": true,
"auto_train": true
}'
import os, requests
resp = requests.post(
f"{os.environ['TRINITITE_BASE']}/v1/policies",
headers={"Authorization": f"Bearer {os.environ['TRINITITE_API_KEY']}"},
json={
"name": "acme-pii-and-tone",
"source_documents": ["s3://acme-policies/handbook.pdf"],
"auto_generate_tests": True,
"auto_train": True,
},
)
print(resp.json())
const resp = await fetch(`${process.env.TRINITITE_BASE}/v1/policies`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.TRINITITE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'acme-pii-and-tone',
source_documents: ['s3://acme-policies/handbook.pdf'],
auto_generate_tests: true,
auto_train: true,
}),
});
console.log(await resp.json());
See Policy Intelligence for the architecture, and Cookbook → Multi-tenant fleet for the end-to-end recipe.
Overview
Ingest regulatory and organisational policy documents into the platform's knowledge graph. Each document is decomposed into Policy Nodes — atomic, structured rule / definition / principle records — that are embedded for retrieval and RAG-injected into Guardian context at inference time.
The flow is:
- Create & ingest (async). Source can be inline content, a URL, or a third-party integration.
- Review & edit the AI-extracted nodes. Every edit is audit-logged.
- Finalize to lock the document. Optionally triggers downstream test-data generation, scenario densification, and Guardian retraining.
- Reingest when the underlying source changes; this creates a new version that starts back in draft.
- Pin a
policy_version_idwhen running test suites or simulations to make governance behaviour deterministic across releases.
Inference-time RAG is fully transparent: the platform embeds every governed conversation, retrieves relevant nodes, and injects them into the Guardian's context. Callers never need to fetch or send nodes manually.
Authentication: Authorization: Bearer <session_token | api_key> with the relevant policies:* permission.
Endpoints
Document lifecycle
| Method | Path | Permission |
|---|---|---|
POST | /v1/policies | policies:create |
GET | /v1/policies | policies:read |
GET | /v1/policies/{document_id} | policies:read |
PATCH | /v1/policies/{document_id} | policies:update |
DELETE | /v1/policies/{document_id} | policies:delete |
GET | /v1/policies/{document_id}/status | policies:read |
POST | /v1/policies/{document_id}/reingest | policies:update |
POST | /v1/policies/{document_id}/finalize | policies:finalize |
GET | /v1/policies/{document_id}/impact | policies:read |
GET | /v1/policies/{document_id}/edit-history | policies:read |
Versions & nodes
| Method | Path | Permission |
|---|---|---|
GET | /v1/policies/{document_id}/versions | policies:read |
GET | /v1/policies/{document_id}/nodes | policies:read |
PATCH | /v1/policies/{document_id}/nodes/{node_id} | policies:update |
Test-suite linking
| Method | Path | Permission |
|---|---|---|
GET | /v1/test-suites/{suite_id}/policies | test_suites:read |
POST | /v1/test-suites/{suite_id}/policies | test_suites:update |
DELETE | /v1/test-suites/{suite_id}/policies/{document_id} | test_suites:update |
GET | /v1/policies/{document_id}/test-suites | test_suites:read |
Lifecycle
Every document has two orthogonal lifecycles.
Ingestion lifecycle (status)
pending → ingesting → ready
↘ failed → (POST /reingest) → ingesting
| Status | Meaning |
|---|---|
pending | Metadata persisted, ingestion not yet started |
ingesting | Extraction + embedding pipeline running |
ready | All nodes extracted; embeddings stored; available for governance |
failed | Pipeline error; inspect ingestion_error on the detail response |
Review lifecycle (finalized_at / is_editable)
draft (is_editable=true) → finalized (is_editable=false)
↘ POST /reingest → new version starting in draft
Finalisation is irreversible. A reingest creates a new version that starts in draft.
POST /v1/policies
Creates a policy document and queues ingestion. Returns 202 Accepted immediately.
{
"name": "GDPR Article 33 — Notification of a personal data breach",
"description": "Notification timing, content requirements, supervisory authority routing.",
"source": {
"type": "url",
"url": "https://gdpr-info.eu/art-33-gdpr/",
"format": "html"
},
"tags": ["gdpr", "breach-notification"],
"skip_extraction": false
}
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Display name |
description | string | No | Free-form description |
source | object | Yes | Discriminated union — see below |
tags | string[] | No | Organizational tags |
skip_extraction | boolean | No | Persist the source without running node extraction. Default false |
Source types
// Inline
{ "type": "inline", "content": "...raw text or base64...", "format": "pdf | docx | html | markdown | plain_text | csv | xlsx | json | xml | rtf", "filename": "..." }
// URL — backend fetches at ingestion time
{ "type": "url", "url": "https://example.com/policies/handbook.pdf", "authorization": "Bearer optional", "format": "optional, auto-detected" }
// Integration
{ "type": "integration", "provider": "confluence | sharepoint | google_drive | notion", "resource_id": "...", "params": { "workspace": "acme-corp" } }
Response — 202 Accepted
{
"document_id": "pol_01JF8RPDC1A2B3C4D5E6F7G8H9I",
"name": "GDPR Article 33 — Notification of a personal data breach",
"status": "pending",
"is_editable": true,
"finalized_at": null,
"created_at": "2026-05-01T22:14:00Z",
"poll_url": "/v1/policies/pol_01JF8RPDC1A2B3C4D5E6F7G8H9I/status"
}
Poll /status at 2 s intervals; most ingestions complete in 30 s – 5 min depending on document size.
GET /v1/policies
| Query | Type | Description |
|---|---|---|
status | string | pending, ingesting, ready, failed |
tag | string | Filter by tag |
is_editable | boolean | true for drafts, false for finalized |
limit / cursor | — | Cursor pagination |
{
"data": [
{
"document_id": "pol_01JF8RPDC1A2B3C4D5E6F7G8H9I",
"name": "GDPR Article 33 — Notification of a personal data breach",
"status": "ready",
"is_editable": false,
"finalized_at": "2026-05-01T22:42:00Z",
"current_version_id": "pver_01JF8RPVR1A2B3C4D5E6F7G8H9I",
"node_count": 47,
"tags": ["gdpr", "breach-notification"],
"created_at": "2026-05-01T22:14:00Z"
}
],
"page": { "next_cursor": null, "has_more": false }
}
GET /v1/policies/{document_id}
Full document detail including the raw markdown and current version metadata.
{
"document_id": "pol_01JF8RPDC1A2B3C4D5E6F7G8H9I",
"name": "GDPR Article 33 — Notification of a personal data breach",
"description": "Notification timing, content requirements, supervisory authority routing.",
"status": "ready",
"is_editable": false,
"finalized_at": "2026-05-01T22:42:00Z",
"current_version_id": "pver_01JF8RPVR1A2B3C4D5E6F7G8H9I",
"node_count": 47,
"markdown_content": "# Article 33\n\nIn the case of a personal data breach...",
"source": { "type": "url", "url": "https://gdpr-info.eu/art-33-gdpr/", "format": "html" },
"tags": ["gdpr", "breach-notification"],
"ingestion_error": null,
"created_at": "2026-05-01T22:14:00Z",
"updated_at": "2026-05-01T22:42:00Z"
}
GET /v1/policies/{document_id}/status
Lightweight status poll — progress is 0.0–1.0 with intermediate stage labels.
{
"document_id": "pol_01JF8RPDC1A2B3C4D5E6F7G8H9I",
"status": "ingesting",
"progress": 0.42,
"stage": "node_extraction",
"estimated_completion": "2026-05-01T22:35:00Z"
}
Stages: fetching, parsing, node_extraction, embedding, indexing.
PATCH /v1/policies/{document_id}
Update document-level fields while a document is draft. Returns 409 conflict if the document is finalized.
{
"name": "GDPR Art. 33 — Breach Notification (Q2 2026)",
"description": "Updated cross-references for Q2 2026.",
"tags": ["gdpr", "breach-notification", "q2-2026"]
}
POST /v1/policies/{document_id}/reingest
Re-run extraction against an updated source. Creates a new version that starts in draft. The previous version is preserved and remains pinnable.
{
"source": { "type": "url", "url": "https://gdpr-info.eu/art-33-gdpr/" },
"skip_extraction": false
}
Returns 202 Accepted with the new version ID and a fresh poll_url.
POST /v1/policies/{document_id}/finalize
Lock the document. After finalisation it cannot be edited; further changes require a reingest.
{
"automation": {
"regenerate_test_data": true,
"densify_scenarios": true,
"retrain_guardians": ["gov_01JF8R3M3X4N5Q6T7V8W9Y0Z1A"],
"pipeline_id": null
}
}
| Field | Type | Description |
|---|---|---|
automation.regenerate_test_data | boolean | Generate fresh adversarial scenarios from the new policy nodes |
automation.densify_scenarios | boolean | Run scenario densification |
automation.retrain_guardians | string[] | Guardian IDs to retrain after finalisation |
automation.pipeline_id | string | null | Reuse an existing finalisation pipeline (else a new one is started) |
Response — 200 OK:
{
"document_id": "pol_01JF8RPDC1A2B3C4D5E6F7G8H9I",
"finalized_at": "2026-05-01T22:42:00Z",
"is_editable": false,
"pipeline_id": "fpl_01JF8RFPL1A2B3C4D5E6F7G8H9I"
}
Track the downstream pipeline via GET /v1/finalization-pipelines/{pipeline_id} (see Test Suites).
GET /v1/policies/{document_id}/versions
Every successful ingest/reingest produces an immutable version snapshot.
{
"document_id": "pol_01JF8RPDC1A2B3C4D5E6F7G8H9I",
"versions": [
{
"version_id": "pver_01JF8RPVR1A2B3C4D5E6F7G8H9I",
"version_number": 2,
"content_hash": "sha256:e3b0c4...",
"node_count": 47,
"is_current": true,
"created_at": "2026-05-01T22:42:00Z"
},
{
"version_id": "pver_01JF8RPVR0A1B2C3D4E5F6G7H8",
"version_number": 1,
"content_hash": "sha256:9b5a17...",
"node_count": 45,
"is_current": false,
"created_at": "2026-04-15T11:08:00Z"
}
]
}
When running a Test Suite or Simulation, pin a specific version_id for deterministic, reproducible results.
Policy Nodes
GET /v1/policies/{document_id}/nodes
{
"document_id": "pol_01JF8RPDC1A2B3C4D5E6F7G8H9I",
"nodes": [
{
"node_id": "gdpr_art33_sec1_a",
"statement_type": "obligation",
"modality": "must",
"subject": "controller",
"action": "notify the supervisory authority",
"conditions": ["a personal data breach has occurred"],
"exceptions": ["unless the breach is unlikely to result in a risk to natural persons"],
"consequences": ["administrative fines under Article 83(4)(a)"],
"raw_excerpt": "In the case of a personal data breach, the controller shall...",
"tags": ["gdpr", "art33"]
}
]
}
PATCH /v1/policies/{document_id}/nodes/{node_id}
Edit a node while the document is in draft. Every change appends an entry to the document's edit-history (see below).
{
"modality": "must",
"conditions": ["a personal data breach has occurred", "and is likely to result in risk to natural persons"]
}
GET /v1/policies/{document_id}/edit-history
Append-only audit trail of every human edit applied to the document or its nodes.
{
"data": [
{
"edit_id": "pedit_01JF8RPED1A2B3C4D5E6F7G8H9I",
"actor": { "user_id": "usr_01JF8RUS1A2B3C4D5E6F7G8H9I", "display_name": "Jane Doe" },
"target": { "type": "node", "node_id": "gdpr_art33_sec1_a" },
"before": { "modality": "should" },
"after": { "modality": "must" },
"rationale": "Aligned with Art. 83(4)(a) penalty surface.",
"occurred_at": "2026-05-01T22:33:00Z"
}
],
"page": { "next_cursor": null, "has_more": false }
}
The edit log is append-only at the database layer — it cannot be modified or deleted via the API.
GET /v1/policies/{document_id}/impact
Before finalizing a policy update, use this endpoint to understand exactly which Guardians will be affected and whether a retrain is recommended. This prevents surprise coverage gaps in production.
Query parameters
| Param | Type | Description |
|---|---|---|
next_version_id | string | The draft version to evaluate (defaults to current draft) |
compare_to_version_id | string | Baseline to diff against (defaults to the active finalized version) |
{
"document_id": "pol_01JF8RPDC1A2B3C4D5E6F7G8H9I",
"next_version_id": "pver_01JF8RPVR2B3C4D5E6F7G8H9I0J",
"compare_to_version_id": "pver_01JF8RPVR1A2B3C4D5E6F7G8H9I",
"changed_nodes": 14,
"added_nodes": 3,
"removed_nodes": 1,
"affected_guardians": [
{
"guardian_id": "gov_01JF8R3M3X4N5Q6T7V8W9Y0Z1A",
"guardian_name": "PII-Redactor",
"impact": "high",
"changed_node_count": 11,
"reason": "11 policy nodes this Guardian enforces have changed",
"recommendation": "Retrain before finalizing this policy version",
"estimated_retrain_min": 45
},
{
"guardian_id": "gov_01JF8R3M4Y5N6Q7T8V9W0Y1Z2B",
"guardian_name": "SQL-Safety",
"impact": "low",
"changed_node_count": 3,
"reason": "3 ancillary policy nodes changed; core enforcement nodes unchanged",
"recommendation": "Optional retrain; current version remains valid"
}
],
"test_suites_to_rerun": [
"suite_01JF8RSTS1A2B3C4D5E6F7G8H9I",
"suite_01JF8RSTS2B3C4D5E6F7G8H9I0J"
]
}
Always call this endpoint before POST /v1/policies/{id}/finalize on a high-value compliance document. It takes milliseconds and surfaces retrain candidates automatically — you can include the guardian_ids directly in the automation.retrain_guardians field of your finalize request.
DELETE /v1/policies/{document_id}
Soft-delete. Returns 204 No Content. The document is excluded from list responses and inference-time RAG, but the underlying versions, nodes, and edit history are retained for compliance.
Errors
{
"error": {
"code": "validation_error",
"message": "source.format is required for inline content.",
"details": { "field": "source.format" },
"request_id": "req_01J..."
}
}
| HTTP | error.code | Cause |
|---|---|---|
400 | validation_error | Body or query failed schema validation |
400 | bad_request | Unsupported format, conflicting source fields |
401 | unauthenticated | Missing or invalid credential |
403 | forbidden | Caller lacks the required policies:* permission |
404 | not_found | Document, node, or version not found in your organization |
409 | conflict | Attempted to PATCH a finalized document; document is currently ingesting |
410 | resource_gone | Document was hard-deleted |
422 | unprocessable_entity | Source could not be fetched / parsed |
429 | rate_limited | Per-organization rate limit exceeded |
Next steps
- Validate a policy with adversarial scenarios → Test Suites
- Train a Guardian on a finalized policy → Training API
- Pin a version for deterministic simulation → Simulation API
- Understand how policy documents become enforcement geometry → Policy Intelligence
- Include finalized policies in compliance evidence bundles → Attestation & Compliance