Skip to content

Programmatic policy management

The programmatic management API lets you create, update, publish, and retire policies from outside the InPolicy web app — from a CI/CD pipeline, a Terraform run, or an AI coding assistant. The same operations available in the editor are available over REST, keyed by an API key you control.

Management scopes are not included on governance keys by default. You need a separate key.

  1. Go to Settings → API Keys and click New API Key.
  2. Give it a name (e.g. ci-policy-sync).
  3. Under Scopes, select the scopes you need:
ScopeWhat it allows
policies:readList and fetch policies
policies:writeCreate and update policies
policies:publishPublish drafts; required for publish=true in bulk upsert
policies:deleteSoft-delete policies
documents:writeUpload files for AI extraction; register public URL sources
webhooks:writeRegister and manage webhook subscriptions
  1. Copy the key immediately — it’s shown once. Store it in your secrets manager or CI environment as INPOLICY_API_KEY.

The Admin bundle (all six scopes above) is the recommended choice for a CI/CD key. Keep it separate from the runtime governance keys used by the browser extension and Mac app — if a management key leaks, it can’t be used for governance checks, and vice versa.


The CLI is the fastest way to sync a directory of Markdown policies.

Terminal window
npm install -g @inpolicy/cli
# One-time auth setup
export INPOLICY_API_KEY=inp_live_...
# Sync all .md files in ./policies/ (creates or updates by externalId)
inpolicy policies sync ./policies/
# Preview without sending anything
inpolicy policies sync ./policies/ --dry-run
# Sync and immediately publish
inpolicy policies sync ./policies/ --publish

Each Markdown file is upserted by externalId. The externalId defaults to the filename (without .md) sanitized to [A-Za-z0-9._/:-]. You can override it in frontmatter:

---
externalId: data-classification/v2
title: Data Classification Policy
severity: 4
enforcement: warning
confidenceRequired: 0.8
tags: [data, privacy]
policyAreaId: <uuid from Settings → Policy Areas>
publish: false
---
Employees must classify all files containing customer PII before sharing externally...

Supported frontmatter fields: externalId, title, severity (1–5), enforcement (fix | warning | audit), confidenceRequired (0.0–1.0), tags, policyAreaId, rationale, effectiveDate, expiryDate, publish.

Terminal window
inpolicy policies list # tabular list
inpolicy policies get <id> # fetch one policy
inpolicy policies publish <id> # publish a draft
inpolicy policies upload ./policy.pdf # upload a document for AI extraction

The policy admin MCP server lets Claude Desktop, Claude Code, or Cursor manage your policy catalog through natural language. It is distinct from the governance MCP server (which only reads policies to check content).

Add to ~/Library/Application Support/Claude/claude_desktop_config.json:

{
"mcpServers": {
"inpolicy-policy-admin": {
"command": "npx",
"args": ["-y", "@inpolicy/policy-admin-mcp"],
"env": {
"INPOLICY_API_KEY": "inp_live_..."
}
}
}
}

Restart Claude Desktop. You can then ask: “List my published data policies”, “Update the data classification policy to severity 4”, or “Publish the draft ‘AI Usage Policy’”.

Terminal window
# Add to your project's MCP config
INPOLICY_API_KEY=inp_live_... npx @inpolicy/policy-admin-mcp

Or in .claude/settings.json:

{
"mcpServers": {
"inpolicy-policy-admin": {
"command": "npx",
"args": ["-y", "@inpolicy/policy-admin-mcp"],
"env": { "INPOLICY_API_KEY": "inp_live_..." }
}
}
}

Required scopes: policies:read, policies:write, policies:publish, policies:delete, documents:write.


Base URL: https://api.inpolicy.ai/api/v1/agent. Authenticate with Authorization: Bearer <api-key>.

All endpoints in this section are mounted under /api/v1/agent (the public Agent Governance namespace). Use the full paths as listed in each section.

Paginated list. Requires policies:read.

Query params: status (DRAFT | PUBLISHED | DEPRECATED), externalId, tag, source, cursor, limit (max 100).

GET /api/v1/agent/policies?status=PUBLISHED&limit=50

Returns { data: PolicyV1[], nextCursor: string | null }.

Fetch one policy. Accepts an InPolicy UUID or external:<externalId> (e.g. /api/v1/agent/policies/external:data-classification/v2). Requires policies:read.

Idempotent bulk upsert. Up to 100 entries per call. Requires policies:write. Requires policies:publish if any entry has publish: true.

POST /api/v1/agent/policies/bulk
{
"policies": [
{
"externalId": "data-classification/v2",
"title": "Data Classification Policy",
"text": "Employees must classify...",
"severity": 4,
"enforcement": "warning",
"publish": false
}
]
}

Response: { results: UpsertResult[], created, updated, unchanged, published, errors }. Each UpsertResult has action: created | updated | unchanged | published | error. Partial failures are non-fatal — one bad row does not abort the rest.

Partial update. Accepts InPolicy UUID or external:<externalId>. Requires policies:write. All fields are optional; omitted fields are unchanged.

Soft-delete. Sets status=DEPRECATED and isActive=false. Requires policies:delete. Hard delete is only available from the admin UI.

Publishes a DRAFT or SUGGESTION policy. Triggers embedding regeneration via the AI service (same as clicking Save & publish in the editor). Requires policies:publish. Republishing an already-PUBLISHED policy returns 400.


Upload a file and let the AI pipeline extract policies from it.

Async — returns 202 immediately. Requires documents:write.

Terminal window
curl -X POST https://api.inpolicy.ai/api/v1/agent/documents \
-H "Authorization: Bearer $INPOLICY_API_KEY" \
-F "file=@./security-handbook.pdf" \
-F "mode=stage" \
-F "policyAreaId=<uuid>"
FieldRequiredValuesDefault
fileyesPDF, DOCX, TXT, MD (max 25 MB)
titlenostringfilename
policyAreaIdnoUUID
modenostage | draft | publishstage
externalDocumentIdnostring

Modes:

  • stage — extracted policies go to the Policy Inbox for human review before any action is taken.
  • draft — extracted policies are created as DRAFT; reviewable in the web app.
  • publish — runs the full publish pipeline immediately. Requires policies:publish on the key.

Response: { documentId: string, checkUrl: string, status: "pending" }.

Poll for status. Requires documents:write.

{
"id": "doc_...",
"status": "completed",
"extractedPolicies": [
{ "id": "pol_...", "title": "Data Retention", "status": "DRAFT" }
]
}

Status values: pendingprocessingcompleted | failed. Poll until terminal. Typical completion time: 10–60 seconds depending on file size.


Register a public URL for recurring sync. Useful for policies hosted in Confluence, Notion, or a public handbook URL. InPolicy fetches the URL, extracts policies, and re-syncs on a daily schedule.

Returns 202. Requires documents:write.

POST /api/v1/agent/sources
{
"url": "https://handbook.acme.com/security-policy",
"title": "Security Handbook",
"mode": "stage",
"schedule": "daily",
"policyAreaId": "<uuid>"
}

The initial fetch is enqueued immediately. Subsequent fetches run on the configured schedule.

Note: v1 supports public URLs only — no authentication headers, no Confluence OAuth, no Notion tokens. For authenticated sources, upload directly with POST /api/v1/agent/documents.

List registered sources. Requires documents:write.

Fetch one source. Requires documents:write.

Stop syncing. The daily refresh is disabled; previously extracted policies remain in their current state. Requires documents:write.


Receive real-time events when policies or documents change.

Register a webhook. Requires webhooks:write.

POST /api/v1/agent/webhooks
{
"url": "https://your-server.example.com/inpolicy-hook",
"events": ["policy.published", "document.processing.completed"],
"description": "Notify CI when policies publish"
}

Response: { id, url, events, secret, createdAt }. The secret is returned once — store it immediately to verify the X-InPolicy-Signature header on incoming deliveries.

EventWhen it fires
policy.createdNew policy saved (any status)
policy.updatedPolicy body or metadata changed
policy.publishedStatus set to PUBLISHED
policy.retiredStatus set to DEPRECATED
document.processing.completedDocument extraction finished
document.processing.failedDocument extraction failed
source.syncedSource URL re-fetched successfully
source.failedSource URL fetch failed

InPolicy signs every delivery with HMAC-SHA256 using the secret returned at registration. Verify it on the receiver before processing:

import { createHmac, timingSafeEqual } from 'crypto';
function verifyInPolicySignature(
body: Buffer,
signatureHeader: string,
secret: string,
): boolean {
const expected = createHmac('sha256', secret)
.update(body)
.digest('hex');
const expected_buf = Buffer.from(expected, 'utf8');
const received_buf = Buffer.from(signatureHeader, 'utf8');
if (expected_buf.length !== received_buf.length) return false;
return timingSafeEqual(expected_buf, received_buf);
}

The X-InPolicy-Signature header value is the raw hex digest (no sha256= prefix).

  • InPolicy retries failed deliveries with exponential back-off. Your endpoint should return 2xx within 30 seconds.
  • Each delivery has a unique deliveryId — safe to use as an idempotency key.
  • Use GET /api/v1/agent/webhooks/:id/deliveries to inspect recent delivery attempts.
GET /api/v1/agent/webhooks — list subscriptions
GET /api/v1/agent/webhooks/:id — fetch one subscription
PATCH /api/v1/agent/webhooks/:id — update url, events, or description
DELETE /api/v1/agent/webhooks/:id — delete subscription
GET /api/v1/agent/webhooks/:id/deliveries — delivery history (last 50)

All management endpoints share the same rate limit as the governance API. The default limit is 300 requests per minute per API key. Bulk upsert counts as one request regardless of how many policies it contains.

API usage is metered per tenant. You can view usage in Settings → API Keys.


  • externalId is your primary key, not a label. Changing an externalId in frontmatter creates a new policy rather than updating the old one. If you need to rename, use PATCH /api/v1/agent/policies/:id to update externalId explicitly, then delete the old entry.
  • publish=true in bulk upsert requires policies:publish on the API key, not just policies:write. The request will fail with 403 if the key lacks that scope.
  • mode=publish on document upload also requires policies:publish. The same scope check applies.
  • DELETE is soft. Deleted policies stay in the database as DEPRECATED. Use the admin UI for a hard delete.
  • Embeddings regenerate on publish. If you publish 50 policies in one bulk call, 50 embedding jobs are queued. There may be a brief window (30–60 seconds) where the new policies aren’t yet enforced by the extension and Mac app.
  • POST /api/v1/agent/policies/:id/publish is idempotent on DRAFT → PUBLISHED but not on PUBLISHED → PUBLISHED. Calling it on an already-published policy returns 400. Check status first if needed.
  • Webhook secrets are stored and returned once. There is no GET /api/v1/agent/webhooks/:id/secret endpoint. If you lose the secret, delete the subscription and re-register.