Complyee API

Query a Complyee workspace from your own systems. The API answers with the same documents, grounding mode and instructions as the assistant inside Complyee.

Quick start

Every call goes to a single endpoint and names an operation. Replace <tenant> with your workspace address and <key> with an API key created in Workspace settings > API access.

Endpoint

POST https://<tenant>.complyee.ai/api/public/v1/gateway

Authenticate with the x-api-key header. The key identifies the workspace, so you never pass a workspace id — and a key can never reach another workspace's data.

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "prompt",
    "parameters": { "question": "What does our travel policy say about booking flights?" }
  }'

Request and response format

Requests are JSON: an operation name and a parameters object. Every response carries statusCode (matching the HTTP status — failures are never returned as 200), the operation, a requestId you can quote in support requests, and one payload key — or an error object with a stable code.

{
  "statusCode": 200,
  "operation": "prompt",
  "requestId": "8f3c1e2a-…",
  "answer": "…"
}

Operations

Thirty-six operations cover the current API surface. Call listOperations with any key to list them all — each entry is marked callable: true or false depending on that key's scopes, so an integration can discover what exists and what to ask its administrator for.

listOperations

Required scope: Always allowed

Lists every operation this API serves. Each entry carries `callable: true` when the calling key's scopes allow it, so an integration can discover the full surface and see which operations to ask its administrator to grant.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "listOperations",
    "parameters": {}
  }'

Example response

{
  "statusCode": 200,
  "operation": "listOperations",
  "requestId": "8f3c1e2a-…",
  "operations": [
    {
      "operation": "prompt",
      "parameters": "question (string, required), history (array, optional)",
      "payloadKey": "answer",
      "description": "Answers a question using this workspace's approved documents.",
      "scope": "prompt",
      "callable": true
    },
    {
      "operation": "putRuleSet",
      "parameters": "rules (array, required), note (string, optional)",
      "payloadKey": "ruleSet",
      "description": "Replaces this workspace's policy rule set.",
      "scope": "policy.admin",
      "callable": false
    }
  ]
}

Common errors: invalid_api_key · network_not_allowed · rate_limited

describeSystem

Required scope: Always allowed

Returns a machine-readable record of this AI system as deployed for this workspace: purpose and excluded uses, model, processing region, grounding mode, data categories, retention, access controls, human-oversight surfaces, the dependency list under components, and sub-processors. Built for registering Complyee as a configuration item in a CMDB or AI system registry, and for the system description an AI Act deployer or a NIST AI RMF GOVERN review is expected to hold. recordVersion is bumped whenever the shape of the record changes, so an integration can pin to it; recordHash changes only when the content changes, so a poll can skip a no-op sync.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "describeSystem",
    "parameters": {}
  }'

Example response

{
  "statusCode": 200,
  "operation": "describeSystem",
  "requestId": "1b7d94c0-…",
  "system": {
    "recordVersion": 3,
    "recordHash": "9f2c…",
    "generatedAt": "2026-08-31T19:00:00.000Z",
    "system": { "name": "Complyee", "vendor": "Complyee (Prodengo AB)", "kind": "Retrieval-augmented question answering over customer-supplied documents." },
    "workspace": { "id": "…", "slug": "acme", "name": "Acme", "address": "https://acme.complyee.ai" },
    "purpose": { "intendedUse": "…", "excludedUses": ["…"], "automatedDecisionMaking": "None. …" },
    "model": { "provider": "Google Cloud Vertex AI", "model": "gemini-2.5-flash", "regionPreference": "eu", "grounding": { "mode": "strict" } },
    "data": { "documentCount": 42, "retention": { "auditEvents": "365 days", "conversationRecords": "90 days" } },
    "accessControls": { "singleSignOn": "Required", "networkRestrictions": { "enabled": true, "ranges": 2 } },
    "humanOversight": ["…"],
    "components": [{ "kind": "model", "name": "Gemini 2.5 Flash", "provider": "Google Cloud Vertex AI", "location": "European Union", "role": "Generates the answer from the retrieved passages." }],
    "deployerRegistry": { "provided": true, "externalSystemId": "CI-004217", "owner": { "name": "…", "email": "…" } },
    "subProcessors": [{ "name": "Google Cloud EMEA Limited", "location": "EU by default; the region is fixed per workspace." }]
  }
}

Common errors: invalid_api_key · network_not_allowed · rate_limited

listSystemChanges

Required scope: audit.read

Returns the configuration changes that matter to a registry, newest first: model, processing region, grounding mode, conversation logging, session limits, API keys and network rules, role changes, and edits to the deployer's own governance fields. Each entry carries a plain-language summary of what changed, so a CMDB can record why the record hash moved. Derived from the workspace audit stream in your own storage bucket.

Parameters

NameTypeDescription
fromstringrequiredStart date, YYYY-MM-DD.
tostringrequiredEnd date, YYYY-MM-DD.
limitnumberoptionalMaximum number of changes to return, 1–500. Defaults to 100.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "listSystemChanges",
    "parameters": { "from": "2026-08-01", "to": "2026-08-31", "limit": 50 }
  }'

Example response

{
  "statusCode": 200,
  "operation": "listSystemChanges",
  "requestId": "6c0a1d92-…",
  "changes": [
    {
      "at": "2026-08-24T09:12:44.000Z",
      "category": "configuration",
      "action": "settings.updated",
      "summary": "model: gemini-2.5-flash → gemini-2.5-pro; grounding mode: balanced → strict",
      "fields": ["model", "grounding mode"],
      "actor": "admin@acme.com",
      "outcome": "success"
    },
    {
      "at": "2026-08-19T14:03:01.000Z",
      "category": "registry",
      "action": "system_registry.updated",
      "summary": "Deployer-supplied governance fields were updated.",
      "fields": ["external_system_id", "owner_name"],
      "actor": "admin@acme.com",
      "outcome": "success"
    }
  ],
  "truncated": false
}

Common errors: scope_denied · invalid_parameters · invalid_api_key · rate_limited

getPostureSnapshot

Required scope: Always allowed

Returns the workspace security checklist as structured data: identity, network restrictions, sessions, people, data handling and retention, each row with its current value, a status of good, review or not-configured, and the explanation shown in the product. Read live from configuration at the moment of the call, with the timestamp it was read.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "getPostureSnapshot",
    "parameters": {}
  }'

Example response

{
  "statusCode": 200,
  "operation": "getPostureSnapshot",
  "requestId": "9a41c7e0-…",
  "posture": {
    "tenantSlug": "acme",
    "tenantName": "Acme",
    "generatedAt": "2026-09-02T08:14:22.000Z",
    "summary": { "good": 11, "review": 2, "notConfigured": 3 },
    "groups": [
      {
        "id": "identity",
        "title": "Identity",
        "rows": [
          { "id": "sso", "label": "Single sign-on", "value": "Required", "status": "good", "note": "…" }
        ]
      }
    ]
  }
}

Common errors: invalid_api_key · network_not_allowed · rate_limited

getSystemDossier

Required scope: audit.read

Returns the dated AI system dossier for a period: a plain-language cover, the full system record, the deployer's governance fields, the posture checklist as read at that moment, the configuration changes in the period, and a manifest counting the audit records held for it. Intended for archiving evidence on a quarterly or annual cadence, not for routine polling — use describeSystem and recordHash for that. The audit records themselves are not included; they stay in your own storage bucket.

Parameters

NameTypeDescription
fromstringrequiredStart date, YYYY-MM-DD.
tostringrequiredEnd date, YYYY-MM-DD.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "getSystemDossier",
    "parameters": { "from": "2026-07-01", "to": "2026-09-30" }
  }'

Example response

{
  "statusCode": 200,
  "operation": "getSystemDossier",
  "requestId": "b7d1f004-…",
  "dossier": {
    "dossierVersion": 1,
    "generatedAt": "2026-10-01T06:00:11.000Z",
    "period": { "from": "2026-07-01", "to": "2026-09-30" },
    "cover": { "title": "AI system dossier", "workspace": "Acme", "preparedBy": "API key \"Archive job\"", "about": "…", "contents": ["…"], "limitations": "…" },
    "systemRecord": { "recordVersion": 3, "recordHash": "9f2c…", "…": "…" },
    "posture": { "summary": { "good": 11, "review": 2, "notConfigured": 3 }, "groups": ["…"] },
    "changes": { "entries": [{ "at": "2026-08-24T09:12:44.000Z", "summary": "grounding mode: balanced → strict" }], "truncated": false },
    "auditManifest": {
      "bucketConfigured": true,
      "streams": [
        { "stream": "admin", "events": 214, "earliest": "2026-07-01T…", "latest": "2026-09-30T…", "byAction": [{ "action": "document.uploaded", "count": 62 }] },
        { "stream": "chat", "events": 1840, "truncated": true, "byAction": [{ "action": "chat.query", "count": 920 }] }
      ]
    }
  }
}

Common errors: scope_denied · invalid_parameters · invalid_api_key · rate_limited

getSystemRegistry

Required scope: Always allowed

Returns the governance fields this workspace has supplied about the system — the facts only the deployer knows: the ID it carries in their CMDB or AI registry, its owner and technical contact, intended and excluded uses, their own risk classification, deployment context and review dates. The same values are embedded in describeSystem under deployerRegistry.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "getSystemRegistry",
    "parameters": {}
  }'

Example response

{
  "statusCode": 200,
  "operation": "getSystemRegistry",
  "requestId": "5c2a77d1-…",
  "registry": {
    "externalSystemId": "CI-004182",
    "ownerName": "Priya Raman",
    "ownerEmail": "priya.raman@acme.example",
    "technicalContactName": "Jonas Berg",
    "technicalContactEmail": "jonas.berg@acme.example",
    "businessPurpose": "Answering policy questions for the compliance team.",
    "excludedUses": "No use in hiring or disciplinary decisions.",
    "riskTier": "limited",
    "riskNotes": "Assistive only; answers are reviewed before they are acted on.",
    "businessUnits": "Group Compliance, Internal Audit",
    "approximateUsers": 140,
    "humanReviewed": "always",
    "lastReviewedOn": "2026-06-01",
    "nextReviewDue": "2026-12-01",
    "updatedAt": "2026-06-01T09:12:00.000Z"
  }
}

Common errors: invalid_api_key · network_not_allowed · rate_limited

updateSystemRegistry

Required scope: system.write

Replaces this workspace's governance fields, so a CMDB or AI registry can stay the master record and push values down rather than have them re-typed here. Every field is optional, and fields you omit are cleared — send the whole object each time. Changes are written to the workspace audit log.

Parameters

NameTypeDescription
externalSystemIdstringoptionalThe CI or asset ID this workspace carries in your registry.
ownerName / ownerEmailstringoptionalThe accountable owner of the system on your side.
technicalContactName / technicalContactEmailstringoptionalWho to reach for integration and configuration questions.
businessPurposestringoptionalWhat the workspace is used for, in your own words.
excludedUsesstringoptionalUses you have explicitly ruled out.
riskTierunclassified | minimal | limited | high | prohibitedoptionalYour own classification. Complyee does not assign or verify it.
riskNotesstringoptionalThe reasoning behind the classification.
businessUnitsstringoptionalWhich parts of the organisation use the workspace.
approximateUsersintegeroptionalRoughly how many people use it.
humanReviewedunspecified | always | sometimes | neveroptionalWhether answers are reviewed by a person before they are acted on.
lastReviewedOn / nextReviewDueYYYY-MM-DDoptionalYour internal review dates for this system.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "updateSystemRegistry",
    "parameters": {
      "externalSystemId": "CI-004182",
      "ownerName": "Priya Raman",
      "ownerEmail": "priya.raman@acme.example",
      "riskTier": "limited",
      "humanReviewed": "always",
      "nextReviewDue": "2026-12-01"
    }
  }'

Example response

{
  "statusCode": 200,
  "operation": "updateSystemRegistry",
  "requestId": "9d41f0b8-…",
  "registry": {
    "externalSystemId": "CI-004182",
    "ownerName": "Priya Raman",
    "riskTier": "limited",
    "humanReviewed": "always",
    "nextReviewDue": "2026-12-01",
    "updatedAt": "2026-09-01T10:20:00.000Z"
  }
}

Common errors: invalid_parameters · scope_denied · rate_limited

prompt

Required scope: prompt

Answers a question using this workspace's approved documents. Pass prior exchanges in history to give the assistant conversation context.

Parameters

NameTypeDescription
questionstring (1–2000 chars)requiredThe question to answer from the workspace's documents.
historyarray of { question, answer } (max 20)optionalEarlier exchanges, oldest first, for follow-up questions.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "prompt",
    "parameters": {
      "question": "What does our travel policy say about booking flights?"
    }
  }'

Example response

{
  "statusCode": 200,
  "operation": "prompt",
  "requestId": "8f3c1e2a-…",
  "answer": "According to the travel policy, flights must be booked …",
  "sources": [
    { "documentId": "d4e5f6a7-…", "title": "Travel Policy.pdf", "page": 4 }
  ]
}

Common errors: invalid_parameters · scope_denied · rate_limited

listDocuments

Required scope: documents.read

Lists the documents uploaded to this workspace, newest first.

Parameters

NameTypeDescription
statusuploaded | indexing | indexed | failedoptionalFilter by processing status.
limitnumber (1–100, default 50)optionalPage size.
cursorstringoptionalPass the previous response's nextCursor to fetch the next page.
externalIdstringoptionalReturn only the document carrying this external id from your own system.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "listDocuments",
    "parameters": {
      "status": "indexed",
      "limit": 25
    }
  }'

Example response

{
  "statusCode": 200,
  "operation": "listDocuments",
  "requestId": "8f3c1e2a-…",
  "documents": [
    {
      "id": "d4e5f6a7-…",
      "externalId": "sharepoint:sites/hr/1042",
      "filename": "Travel Policy.pdf",
      "status": "indexed",
      "sizeBytes": 248112,
      "createdAt": "2026-08-18T09:41:22.000Z"
    }
  ],
  "nextCursor": null
}

Common errors: invalid_parameters · scope_denied · rate_limited

createDocumentUpload

Required scope: documents.write

Opens a short-lived upload URL for one file. PUT the file bytes to it, then call finalizeDocumentUpload. File bytes never travel through this API.

Parameters

NameTypeDescription
filenamestringrequiredThe file name, including extension (pdf, doc, docx, txt, md, html).
contentTypestringoptionalMIME type of the file, e.g. application/pdf.
externalIdstringoptionalYour own identifier for the document. Finalizing with an externalId that already exists replaces that document.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "createDocumentUpload",
    "parameters": {
      "filename": "Travel Policy.pdf",
      "contentType": "application/pdf",
      "externalId": "sharepoint:sites/hr/1042"
    }
  }'

Example response

{
  "statusCode": 200,
  "operation": "createDocumentUpload",
  "requestId": "8f3c1e2a-…",
  "upload": {
    "uploadId": "3b1e…",
    "uploadUrl": "https://storage.googleapis.com/upload/…",
    "method": "PUT",
    "expiresAt": "2026-08-31T11:00:00.000Z",
    "externalId": "sharepoint:sites/hr/1042"
  }
}

Common errors: invalid_parameters · scope_denied · rate_limited · internal_error

finalizeDocumentUpload

Required scope: documents.write

Registers the uploaded file and starts indexing it. If the upload carried an externalId, the previous document with that id is removed from storage and the search index first.

Parameters

NameTypeDescription
uploadIduuidrequiredThe uploadId returned by createDocumentUpload.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "finalizeDocumentUpload",
    "parameters": {
      "uploadId": "3b1e…"
    }
  }'

Example response

{
  "statusCode": 200,
  "operation": "finalizeDocumentUpload",
  "requestId": "8f3c1e2a-…",
  "document": {
    "id": "d4e5f6a7-…",
    "externalId": "sharepoint:sites/hr/1042",
    "filename": "Travel Policy.pdf",
    "sizeBytes": 248112,
    "status": "indexed",
    "replacedDocumentId": "a1b2c3d4-…"
  }
}

Common errors: invalid_parameters · scope_denied · not_found · rate_limited · internal_error

deleteDocument

Required scope: documents.write

Permanently deletes a document from storage and the search index. This cannot be undone.

Parameters

NameTypeDescription
documentIduuidoptionalThe id of the document to delete, as returned by listDocuments. Pass either this or externalId.
externalIdstringoptionalYour own identifier for the document. Pass exactly one of documentId or externalId.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "deleteDocument",
    "parameters": {
      "externalId": "sharepoint:sites/hr/1042"
    }
  }'

Example response

{
  "statusCode": 200,
  "operation": "deleteDocument",
  "requestId": "8f3c1e2a-…",
  "document": {
    "id": "d4e5f6a7-…",
    "externalId": "sharepoint:sites/hr/1042",
    "filename": "Travel Policy.pdf",
    "deleted": true
  }
}

Common errors: invalid_parameters · scope_denied · not_found · rate_limited

listAuditEvents

Required scope: audit.read

Reads audit events for this workspace from its own storage bucket — including who asked what and when. Grant this scope only to systems allowed to see that.

Parameters

NameTypeDescription
streamadmin | chatrequiredWhich audit stream to read.
fromstring (YYYY-MM-DD)requiredStart of the date range, inclusive.
tostring (YYYY-MM-DD)requiredEnd of the date range, inclusive.
limitnumber (1–2000, default 500)optionalMaximum events to return; truncated is true when more exist.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "listAuditEvents",
    "parameters": {
      "stream": "chat",
      "from": "2026-08-01",
      "to": "2026-08-30",
      "limit": 100
    }
  }'

Example response

{
  "statusCode": 200,
  "operation": "listAuditEvents",
  "requestId": "8f3c1e2a-…",
  "events": [
    {
      "at": "2026-08-29T14:03:11.000Z",
      "action": "chat.ask",
      "outcome": "success",
      "actor": "jane@example.com"
    }
  ],
  "truncated": false,
  "bucketConfigured": true
}

Common errors: invalid_parameters · scope_denied · rate_limited

listAgents

Required scope: Always allowed

Lists the agents Complyee runs for this workspace: key, name, version, whether each is enabled, the scopes it uses, which policy engine decides what it may do, and its most recent runs. Register these alongside describeSystem so a CMDB knows what acts inside the workspace, not only what is deployed.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "listAgents",
    "parameters": {}
  }'

Example response

{
  "statusCode": 200,
  "operation": "listAgents",
  "requestId": "2c58a1d4-…",
  "agents": [
    {
      "agentKey": "library-curator",
      "displayName": "Library curator",
      "version": "1",
      "enabled": false,
      "purpose": "Describes each document in the workspace library …",
      "scopes": ["documents.read", "documents.write"],
      "policyEngineMode": "local",
      "policyEngineHost": null,
      "lastRunAt": null,
      "runs": []
    }
  ]
}

Common errors: invalid_api_key · network_not_allowed · rate_limited

listAgentRuns

Required scope: policy.read

Lists agent runs, newest first: which agent ran, what triggered it, how it ended, when, and how many items it touched. Operational bookkeeping only — what a run actually read or wrote stays in your own storage.

Parameters

NameTypeDescription
agentstringoptionalRestrict to one agent key, e.g. library-curator.
limitnumberoptional1–200, default 25.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "listAgentRuns",
    "parameters": { "agent": "library-curator", "limit": 10 }
  }'

Example response

{
  "statusCode": 200,
  "operation": "listAgentRuns",
  "requestId": "a4f0e912-…",
  "runs": [
    {
      "agent": "library-curator",
      "id": "…",
      "trigger": "manual",
      "status": "succeeded",
      "startedAt": "2026-09-04T08:00:00.000Z",
      "finishedAt": "2026-09-04T08:01:12.000Z",
      "itemCount": 14,
      "summary": "14 documents described."
    }
  ]
}

Common errors: invalid_api_key · scope_denied · rate_limited

listPolicyDecisions

Required scope: policy.read

Lists the decisions the policy engine made for this workspace, newest first. The index is deliberately thin: agent, action, resource reference, effect, rule set version, which engine answered, and the path of the full record in your own bucket. Rationales and request context never leave your storage.

Parameters

NameTypeDescription
limitnumberoptional1–200, default 25.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "listPolicyDecisions",
    "parameters": { "limit": 10 }
  }'

Example response

{
  "statusCode": 200,
  "operation": "listPolicyDecisions",
  "requestId": "7d21b0aa-…",
  "decisions": [
    {
      "decisionId": "1f0e…",
      "agentKey": "library-curator",
      "action": "model.invoke",
      "resourceRef": "gemini-2.5-flash",
      "effect": "grant",
      "policyVersion": "builtin:builtin-1",
      "engineMode": "local",
      "engineEndpoint": null,
      "issuedAt": "2026-09-04T08:00:03.000Z",
      "expiresAt": "2026-09-04T08:05:03.000Z",
      "recordPath": "policy/decisions/2026/09/04/2026-09-04T08:00:03.000Z-9ab21c4d.json"
    }
  ]
}

Common errors: invalid_api_key · scope_denied · rate_limited

getDecision

Required scope: policy.read

Returns one decision from the index by its id — the fastest way to answer "why was this allowed?" when an audit quotes a decision id. A policy-engine operation: if the engine is moved into its own application, the same call is served there, unchanged.

Parameters

NameTypeDescription
decisionIduuidrequiredThe decision id returned by decide.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "getDecision",
    "parameters": { "decisionId": "1f0e5c2b-…" }
  }'

Example response

{
  "statusCode": 200,
  "operation": "getDecision",
  "requestId": "0be71c33-…",
  "decision": {
    "decisionId": "1f0e5c2b-…",
    "agentKey": "library-curator",
    "action": "document.read",
    "resourceRef": "doc_8891",
    "effect": "grant",
    "policyVersion": "builtin:builtin-1",
    "engineMode": "local",
    "issuedAt": "2026-09-04T08:00:01.000Z",
    "expiresAt": "2026-09-04T08:05:01.000Z",
    "recordPath": "policy/decisions/2026/09/04/…json"
  }
}

Common errors: invalid_api_key · scope_denied · not_found · rate_limited

listRules

Required scope: policy.read

Returns the rule set in force: its version, its default effect and its ordered rules. Evaluation is first-match-wins, so reading the list top to bottom tells you exactly what an agent may do. A workspace may replace the built-in set by writing policy/rules.json into its own bucket; a file that cannot be read refuses everything rather than falling back.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "listRules",
    "parameters": {}
  }'

Example response

{
  "statusCode": 200,
  "operation": "listRules",
  "requestId": "5cd0f7a2-…",
  "rules": {
    "version": "builtin-1",
    "source": "builtin",
    "defaultEffect": "reject",
    "valid": true,
    "rules": [
      { "id": "curator-read-documents", "agent": "library-curator", "action": "document.read", "effect": "grant" }
    ]
  }
}

Common errors: invalid_api_key · scope_denied · rate_limited

decide

Required scope: policy.decide

Asks whether an agent may perform one exact action on one exact payload. The answer is a grant or a reject with a rationale, the rule set version, and a short-lived signature bound to payloadHash — so the code that acts can prove the decision it holds belongs to the payload in front of it. This is the operation an external policy engine implements; pointing a workspace at one is a configuration change, not a code change.

Parameters

NameTypeDescription
agentstringrequiredAgent key asking for permission.
actionstringrequireddocument.read, model.invoke or library.write.
resourceRefstringoptionalWhat it wants to act on — a document id, a model name, a path.
payloadHashstringrequiredSHA-256 hex over the exact payload that will be acted on.
contextobjectoptionalNon-sensitive context the rules may use.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "decide",
    "parameters": {
      "agent": "library-curator",
      "action": "document.read",
      "resourceRef": "doc_8891",
      "payloadHash": "9f2c1b…"
    }
  }'

Example response

{
  "statusCode": 200,
  "operation": "decide",
  "requestId": "b5f24c81-…",
  "decision": {
    "decisionId": "1f0e5c2b-…",
    "effect": "grant",
    "rationale": "The library curator is allowed to read documents of its own workspace.",
    "policyVersion": "builtin:builtin-1",
    "issuedAt": "2026-09-04T08:00:01.000Z",
    "expiresAt": "2026-09-04T08:05:01.000Z",
    "signature": "6c1a…",
    "engine": { "mode": "local", "endpoint": null }
  }
}

Common errors: invalid_api_key · invalid_parameters · scope_denied · rate_limited

runAgent

Required scope: agents.run

Starts a run of an agent in this workspace and returns how it ended. Triggering a run grants nothing: every step inside it is checked against the policy engine first, and a step without a valid, unexpired decision bound to its exact payload is refused and logged as blocked. Today the library curator runs in dry-run mode — it asks for every permission the real job needs and performs nothing.

Parameters

NameTypeDescription
agentstringrequiredAgent key, e.g. library-curator.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "runAgent",
    "parameters": { "agent": "library-curator" }
  }'

Example response

{
  "statusCode": 200,
  "operation": "runAgent",
  "requestId": "0f21b7c4-…",
  "run": {
    "runId": "8f1c…",
    "status": "succeeded",
    "itemCount": 4,
    "summary": "Dry run: 4 document(s) checked, 12 decision(s) requested, nothing changed."
  }
}

Common errors: invalid_api_key · scope_denied · invalid_parameters · rate_limited

stopAgentRun

Required scope: agents.run

Asks a running agent run to stop. The run ends at its next enforcement point, never in the middle of a step, so nothing is left half-done. Turning the agent off in Workspace security has the same effect on any run in progress.

Parameters

NameTypeDescription
runIduuidrequiredThe run to stop.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "stopAgentRun",
    "parameters": { "runId": "8f1c…" }
  }'

Example response

{
  "statusCode": 200,
  "operation": "stopAgentRun",
  "requestId": "1b90d5aa-…",
  "stopped": { "runId": "8f1c…", "requested": true }
}

Common errors: invalid_api_key · scope_denied · invalid_parameters · rate_limited

getAgentRun

Required scope: policy.read

Returns one agent run with its steps: for each step the action, the resource, the decision that allowed or refused it, and whether it was performed or blocked. This is the evidence that no step happened without a decision — the reasoning behind each decision stays in your own storage.

Parameters

NameTypeDescription
runIduuidrequiredThe run to read.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "getAgentRun",
    "parameters": { "runId": "8f1c…" }
  }'

Example response

{
  "statusCode": 200,
  "operation": "getAgentRun",
  "requestId": "5cc0e4a1-…",
  "run": {
    "id": "8f1c…",
    "agent": "library-curator",
    "trigger": "api",
    "status": "succeeded",
    "itemCount": 4,
    "steps": [
      {
        "seq": 1,
        "action": "document.read",
        "resourceRef": "b21f…",
        "decisionId": "3d47…",
        "effect": "grant",
        "status": "performed",
        "errorCode": null
      }
    ]
  }
}

Common errors: invalid_api_key · scope_denied · not_found · rate_limited

listDocumentAbstracts

Required scope: documents.read

Returns the short description the library curator wrote for each document in this workspace, with keywords, the model that produced it and when it was generated. Documents the curator has not described yet are absent — nothing is inferred about them.

Parameters

NameTypeDescription
limitintegeroptional1-500, default 100.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "listDocumentAbstracts",
    "parameters": { "limit": 50 }
  }'

Example response

{
  "statusCode": 200,
  "operation": "listDocumentAbstracts",
  "requestId": "1b90c7d4-…",
  "abstracts": [
    {
      "documentId": "b21f…",
      "filename": "Travel Policy 2026.pdf",
      "abstract": "Rules for booking, expensing and approving business travel.",
      "keywords": ["travel", "expenses", "approval"],
      "language": "English",
      "model": "gemini-2.5-flash",
      "abstractVersion": 1,
      "generatedAt": "2026-09-04T09:12:44Z"
    }
  ]
}

Common errors: invalid_api_key · scope_denied · invalid_parameters · rate_limited

getDocumentAbstract

Required scope: documents.read

Returns one document's description together with the identifiers of the three decisions that authorised reading the document, calling the model and writing the result. This is the provenance of a generated abstract.

Parameters

NameTypeDescription
documentIduuidrequiredThe document to describe.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "getDocumentAbstract",
    "parameters": { "documentId": "b21f…" }
  }'

Example response

{
  "statusCode": 200,
  "operation": "getDocumentAbstract",
  "requestId": "77e0aa31-…",
  "abstract": {
    "documentId": "b21f…",
    "abstract": "Rules for booking, expensing and approving business travel.",
    "keywords": ["travel", "expenses", "approval"],
    "decisionIds": ["3d47…", "5aa1…", "9c02…"]
  }
}

Common errors: invalid_api_key · scope_denied · invalid_parameters · not_found · rate_limited

refreshLibraryAbstracts

Required scope: agents.run

Runs the library curator over documents whose description is missing or out of date and returns how the run ended. Unchanged documents are skipped, so calling this repeatedly is safe. Every read, model call and write inside the run is checked against the policy engine first; a run where some steps were refused ends as succeeded_with_blocks.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "refreshLibraryAbstracts",
    "parameters": {}
  }'

Example response

{
  "statusCode": 200,
  "operation": "refreshLibraryAbstracts",
  "requestId": "0d4b1f77-…",
  "run": {
    "runId": "8f1c…",
    "status": "succeeded",
    "itemCount": 3,
    "summary": "3 described, 0 unchanged, 0 refused."
  }
}

Common errors: invalid_api_key · scope_denied · rate_limited · internal_error

describeAgentEcosystem

Required scope: policy.read

Returns the agent ecosystem of this workspace as one registration object a CMDB or AI system registry can store: the agents and what each may ask for, the policy engine and rule set that decide, the enforcement guard, library coverage and recent activity. The ecosystemHash covers only the governed setup, so it moves on a real change and not on activity. No document text, descriptions or rationales are included.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "describeAgentEcosystem",
    "parameters": {}
  }'

Example response

{
  "statusCode": 200,
  "operation": "describeAgentEcosystem",
  "requestId": "7a1c9f30-…",
  "ecosystem": {
    "viewVersion": 1,
    "ecosystemHash": "b91f4c02a7d5…",
    "workspace": { "slug": "acme", "name": "Acme" },
    "agents": [
      {
        "agentKey": "library-curator",
        "displayName": "Library curator",
        "version": "1.0.0",
        "enabled": true,
        "policyActions": ["document.read", "model.invoke", "library.write"]
      }
    ],
    "policyEngine": { "mode": "local", "ruleSetVersion": "2026-09-01", "defaultEffect": "reject" },
    "enforcement": { "guard": "pep-1" },
    "library": { "described": 18, "total": 20 }
  }
}

Common errors: invalid_api_key · scope_denied · rate_limited

listEcosystemChanges

Required scope: audit.read

Lists dated changes to the agent ecosystem — an agent turned on or off, the engine repointed, a rule set replaced or found unreadable — newest first, so a registry can narrate why describeAgentEcosystem changed.

Parameters

NameTypeDescription
fromdaterequiredFirst day of the period, YYYY-MM-DD.
todaterequiredLast day of the period, YYYY-MM-DD.
limitnumberoptional1-500, default 100.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "listEcosystemChanges",
    "parameters": { "from": "2026-08-01", "to": "2026-09-04" }
  }'

Example response

{
  "statusCode": 200,
  "operation": "listEcosystemChanges",
  "requestId": "1b0d5e64-…",
  "changes": [
    {
      "at": "2026-09-03T09:12:44Z",
      "kind": "agent",
      "summary": "Library curator was turned on.",
      "before": "false",
      "after": "true"
    }
  ],
  "truncated": false,
  "bucketConfigured": true
}

Common errors: invalid_api_key · scope_denied · invalid_parameters · rate_limited

getAgentDossier

Required scope: audit.read

Returns the dated agent evidence file for a period: the ecosystem view, the changes in the period, runs and steps by outcome, and a count of the decision records held in this workspace's own storage. Intended for archiving evidence, not for routine polling.

Parameters

NameTypeDescription
fromdaterequiredFirst day of the period, YYYY-MM-DD.
todaterequiredLast day of the period, YYYY-MM-DD.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "getAgentDossier",
    "parameters": { "from": "2026-08-01", "to": "2026-09-04" }
  }'

Example response

{
  "statusCode": 200,
  "operation": "getAgentDossier",
  "requestId": "5e77a1c8-…",
  "dossier": {
    "dossierVersion": 1,
    "period": { "from": "2026-08-01", "to": "2026-09-04" },
    "ecosystem": { "ecosystemHash": "b91f4c02a7d5…" },
    "runs": { "total": 6, "byStatus": { "succeeded": 5, "succeeded_with_blocks": 1 } },
    "steps": { "performed": 52, "blocked": 3 },
    "decisionRecords": { "indexed": 55, "stored": 55 }
  }
}

Common errors: invalid_api_key · scope_denied · invalid_parameters · rate_limited

describePolicyEngine

Required scope: policy.read

The policy engine describes itself: identity, mode, rule set version and source, default effect, and every action it answers for with the payload fields each decision is bound to. This is the contract an external engine must implement to be swappable. A policy-engine operation — when the workspace uses an external engine, that engine's own answer is returned.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "describePolicyEngine",
    "parameters": {}
  }'

Example response

{
  "statusCode": 200,
  "operation": "describePolicyEngine",
  "requestId": "9c4e2af1-…",
  "engine": {
    "engine": "complyee-builtin",
    "mode": "local",
    "ruleSetVersion": "2026-09-01",
    "ruleSetSource": "workspace",
    "defaultEffect": "reject",
    "ruleCount": 4,
    "ruleSetValid": true,
    "selfDescribed": true,
    "actions": [
      {
        "action": "model.invoke",
        "label": "Ask the model",
        "boundPayload": "model, prompt hash and the hash of the exact excerpt sent"
      }
    ]
  }
}

Common errors: invalid_api_key · scope_denied · rate_limited

explainDecision

Required scope: policy.read

Returns why one decision came out the way it did: the effect, the rule set version, the engine that answered and where the full rationale record is stored. A policy-engine operation — it moves with the engine if the engine is relocated.

Parameters

NameTypeDescription
decisionIduuidrequiredThe decision to explain.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "explainDecision",
    "parameters": { "decisionId": "3d47…" }
  }'

Example response

{
  "statusCode": 200,
  "operation": "explainDecision",
  "requestId": "cc31be04-…",
  "explanation": {
    "decisionId": "3d47…",
    "effect": "grant",
    "action": "document.read",
    "policyVersion": "2026-09-01",
    "engineMode": "local",
    "recordPath": "policy/decisions/2026/09/3d47….json",
    "rationaleLocation": "The full rationale is in this workspace's own storage at the path shown."
  }
}

Common errors: invalid_api_key · scope_denied · invalid_parameters · not_found · rate_limited

verifyDecision

Required scope: policy.read

Checks whether a decision is still a live grant for exactly this payload: right id, right payload hash, not expired, and a grant rather than a refusal. A policy-engine operation — an enforcement point running outside Complyee calls this before acting on a decision it was handed.

Parameters

NameTypeDescription
decisionIduuidrequiredThe decision to check.
payloadHashstringrequiredSHA-256, 64 hex characters, of the exact payload about to be acted on.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "verifyDecision",
    "parameters": { "decisionId": "3d47…", "payloadHash": "9f2c…" }
  }'

Example response

{
  "statusCode": 200,
  "operation": "verifyDecision",
  "requestId": "aa71f0c2-…",
  "verification": {
    "valid": false,
    "reason": "Decision does not match this payload.",
    "decision": { "decisionId": "3d47…", "effect": "grant", "action": "document.read" }
  }
}

Common errors: invalid_api_key · scope_denied · invalid_parameters · rate_limited

listApprovals

Required scope: policy.read

Lists the steps waiting for a person, and how earlier requests were answered. A rule with the effect require_approval does not refuse and does not grant: it holds the step and records a request naming the agent, the action, the resource and the fingerprint of the exact payload — never the payload itself. A policy-engine operation.

Parameters

NameTypeDescription
statusstringoptionalpending (default), approved, rejected, expired or all.
limitnumberoptional1-200, default 50.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "listApprovals",
    "parameters": { "status": "pending" }
  }'

Example response

{
  "statusCode": 200,
  "operation": "listApprovals",
  "requestId": "aa71f0c2-…",
  "approvals": [
    {
      "id": "7c1e…",
      "agentKey": "library-curator",
      "action": "library.write",
      "resourceRef": "document:41b9…",
      "payloadHash": "9f2c…",
      "status": "pending",
      "requestedAt": "2026-09-05T08:00:01.000Z",
      "expiresAt": "2026-09-06T08:00:01.000Z"
    }
  ]
}

Common errors: invalid_api_key · scope_denied · invalid_parameters · rate_limited

decideApproval

Required scope: policy.admin

Answers one waiting step. An approval is not itself a grant: it lets the agent's next attempt at exactly that payload receive one short-lived, single-use decision. A different payload needs a new request, and an approval can never bless work already attempted. A policy-engine operation.

Parameters

NameTypeDescription
approvalIduuidrequiredThe waiting request.
approvebooleanrequiredtrue to approve, false to refuse.
reasonstringoptionalRecorded in the audit trail.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "decideApproval",
    "parameters": { "approvalId": "7c1e…", "approve": true, "reason": "Reviewed by the data office." }
  }'

Example response

{
  "statusCode": 200,
  "operation": "decideApproval",
  "requestId": "aa71f0c2-…",
  "approval": { "id": "7c1e…", "status": "approved", "expiresAt": "2026-09-05T08:15:01.000Z" }
}

Common errors: invalid_api_key · scope_denied · invalid_parameters · not_found · rate_limited

revokeDecision

Required scope: policy.admin

Takes a grant back before it is used. The enforcement point refuses any step presenting a revoked decision, even inside its validity window. A policy-engine operation.

Parameters

NameTypeDescription
decisionIduuidrequiredThe grant to withdraw.
reasonstringoptionalRecorded in the audit trail.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "revokeDecision",
    "parameters": { "decisionId": "3d47…", "reason": "Issued during an incident." }
  }'

Example response

{
  "statusCode": 200,
  "operation": "revokeDecision",
  "requestId": "aa71f0c2-…",
  "decision": { "decisionId": "3d47…", "effect": "grant", "revokedAt": "2026-09-05T08:20:00.000Z" }
}

Common errors: invalid_api_key · scope_denied · invalid_parameters · not_found · rate_limited

putRuleSet

Required scope: policy.admin

Replaces the rule set the policy engine applies for this workspace. Rules are ordered and the first match wins; each has an effect of grant, reject or require_approval. The set is validated against the agents and actions that actually exist before it is stored in the workspace's own bucket — a rule about something that does not exist is a governance illusion, not a control. A policy-engine operation.

Parameters

NameTypeDescription
versionstringrequiredYour name for this rule set, quoted in every rationale.
defaultEffectstringrequiredgrant or reject, applied when no rule matches.
rulesarrayrequiredOrdered rules of { id, agent, action, resourcePrefix?, effect, reason? }.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "putRuleSet",
    "parameters": { "version": "workspace-3", "defaultEffect": "reject", "rules": [ { "id": "curator-read", "agent": "library-curator", "action": "document.read", "effect": "grant" }, { "id": "curator-write-needs-approval", "agent": "library-curator", "action": "library.write", "effect": "require_approval" } ] }
  }'

Example response

{
  "statusCode": 200,
  "operation": "putRuleSet",
  "requestId": "aa71f0c2-…",
  "engine": { "mode": "local", "ruleSetVersion": "workspace-3", "ruleCount": 2, "defaultEffect": "reject" }
}

Common errors: invalid_api_key · scope_denied · invalid_parameters · rate_limited

pauseAgents

Required scope: policy.admin

Holds every agent in this workspace at its next step, without changing which agents are enabled — so resuming restores the setup exactly, rather than a guess at it. Stays in Complyee: it governs the agents, not the engine.

Parameters

NameTypeDescription
reasonstringoptionalRecorded in the audit trail.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "pauseAgents",
    "parameters": { "reason": "Incident 4471" }
  }'

Example response

{
  "statusCode": 200,
  "operation": "pauseAgents",
  "requestId": "aa71f0c2-…",
  "agents": { "paused": true }
}

Common errors: invalid_api_key · scope_denied · rate_limited

resumeAgents

Required scope: policy.admin

Lets the workspace's agents run again. Each returns to the enabled state it had before the pause. Stays in Complyee.

Parameters

NameTypeDescription
reasonstringoptionalRecorded in the audit trail.

Example request

curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "resumeAgents",
    "parameters": { "reason": "Incident 4471 closed" }
  }'

Example response

{
  "statusCode": 200,
  "operation": "resumeAgents",
  "requestId": "aa71f0c2-…",
  "agents": { "paused": false }
}

Common errors: invalid_api_key · scope_denied · rate_limited

Synchronizing from a document management system

Policy documents live in systems like SharePoint, Documentum or FileNet and change often. Complyee does not manage documents — it mirrors them. Push a file in three calls: createDocumentUpload returns a short-lived upload URL, you PUT the file bytes straight to it, then finalizeDocumentUpload registers the document and starts indexing. File bytes never travel through this JSON API, so large documents are no problem.

Pass your own identifier as externalId (for example the SharePoint item id). Complyee keeps one live version per externalId: finalizing a new upload with the same externalId removes the previous file from storage and the search index and puts the new one in its place. There is no version history — the latest file you push is what the assistant answers from. Mirror a deletion by calling deleteDocument with the same externalId — no need to store Complyee ids on your side.

# 1. ask for an upload URL
curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "createDocumentUpload",
    "parameters": {
      "filename": "Travel Policy.pdf",
      "contentType": "application/pdf",
      "externalId": "sharepoint:sites/hr/1042"
    }
  }'

# 2. send the file bytes to the returned upload.uploadUrl
curl -X PUT "$UPLOAD_URL" \
  -H "content-type: application/pdf" \
  --data-binary @"Travel Policy.pdf"

# 3. register and index it
curl -X POST https://<tenant>.complyee.ai/api/public/v1/gateway \
  -H "content-type: application/json" \
  -H "x-api-key: cmp_live_xxxxxxxxxxxxxxxx" \
  -d '{
    "operation": "finalizeDocumentUpload",
    "parameters": { "uploadId": "3b1e…" }
  }'

The sync loop

Run a scheduled job — nightly is enough for most policy libraries. Ask your document system for the documents that should be searchable, push anything new or changed, and delete anything that has disappeared. Because Complyee keys on your externalId, the job is stateless towards Complyee: the only thing you keep on your side is a small table of what you have already sent.

state = { externalId -> lastModified }   # your own store, e.g. a SharePoint list

for item in dms.listDocuments(library):
    externalId = "sharepoint:" + item.siteId + "/" + item.id

    if externalId not in state or item.modified > state[externalId]:
        upload = complyee.createDocumentUpload(
            filename   = item.name,
            contentType= item.mimeType,
            externalId = externalId)
        http.put(upload.uploadUrl, body = dms.getFileContent(item))
        complyee.finalizeDocumentUpload(uploadId = upload.uploadId)
        state[externalId] = item.modified        # only on success

# mirror deletions: anything we sent before but the DMS no longer lists
for externalId in state.keys() - seenThisRun:
    complyee.deleteDocument(externalId = externalId)
    remove externalId from state

Keep the sync state minimal: an externalId and the source system's last-modified value per document. Only write the state after finalizeDocumentUpload succeeds — a failed upload then simply retries on the next run, and re-sending an unchanged file is harmless because it replaces itself. Space the calls out if you sync many files at once: on 429 respect the Retry-After header rather than retrying immediately. A full re-listing every run is the simplest and safest option; switch to a delta or change-log query only when the library is large enough to make that slow.

Example: Power Automate and SharePoint

Most Microsoft 365 customers can build the loop above without writing code. The flow below mirrors a SharePoint document library into Complyee. The API key lives in the flow's HTTP headers — server-side in your tenant, never in a browser.

  1. Trigger: Recurrence — for example once every night.
  2. Action: SharePoint "Get files (properties only)" on the policy library, so you get names, item ids and Modified timestamps.
  3. Action: read your sync state — a plain SharePoint list with columns ExternalId and LastModified works well.
  4. Apply to each file: condition — the file is new, or its Modified value is later than the stored LastModified.
  5. If yes — HTTP action 1: POST to the gateway with operation createDocumentUpload, externalId set to sharepoint:<site>/<item id>. Parse the response and keep upload.uploadUrl and upload.uploadId.
  6. Action: SharePoint "Get file content" for the same item.
  7. HTTP action 2: PUT the file content to upload.uploadUrl with the file's content type as the only header. No API key on this call.
  8. HTTP action 3: POST to the gateway with operation finalizeDocumentUpload and the uploadId. On success, update the item's row in your sync state list.
  9. After the loop: for every row in the sync state whose ExternalId was not returned by SharePoint this run, HTTP action POST deleteDocument with that externalId, then remove the row.
  10. Add Configure run after / retry policy on the HTTP actions so one failed file does not stop the run — it will be picked up next time.
// HTTP action 1 — open an upload
POST https://<tenant>.complyee.ai/api/public/v1/gateway
Headers: { "content-type": "application/json", "x-api-key": "cmp_live_xxxxxxxxxxxxxxxx" }
Body:
{
  "operation": "createDocumentUpload",
  "parameters": {
    "filename": "@{items('Apply_to_each')?['{FilenameWithExtension}']}",
    "contentType": "application/pdf",
    "externalId": "sharepoint:hr/@{items('Apply_to_each')?['ID']}"
  }
}

// HTTP action 2 — send the bytes (no API key)
PUT @{body('Parse_upload')?['upload']?['uploadUrl']}
Headers: { "content-type": "application/pdf" }
Body: @{body('Get_file_content')}

// HTTP action 3 — register and index
POST https://<tenant>.complyee.ai/api/public/v1/gateway
Headers: { "content-type": "application/json", "x-api-key": "cmp_live_xxxxxxxxxxxxxxxx" }
Body:
{
  "operation": "finalizeDocumentUpload",
  "parameters": { "uploadId": "@{body('Parse_upload')?['upload']?['uploadId']}" }
}

// After the loop — mirror a deletion
POST https://<tenant>.complyee.ai/api/public/v1/gateway
Body:
{
  "operation": "deleteDocument",
  "parameters": { "externalId": "sharepoint:hr/1042" }
}

The same loop fits anything else: a scheduled PowerShell, Python or Node script, an iPaaS such as Zapier, Make or Workato, or a small .NET or Java service for on-prem Documentum or FileNet. Only the first step — asking the source system which documents exist and when they last changed — differs; the three Complyee calls and the externalId bookkeeping are identical everywhere.

Registering Complyee in your CMDB or AI registry

Complyee describes itself. describeSystem returns one machine-readable record of this AI system as deployed for your workspace: what it is for, which model and processing region it uses, how it is grounded, what it processes, how long records are kept, who can reach it, which human-oversight surfaces exist, and which sub-processors are involved. It is written to be stored as a configuration item — we report configuration, we do not assess it and we make no claim that your organisation is compliant.

The framing is the deployer's, not ours: EU AI Act Article 26 expects the organisation operating an AI system to know its purpose, its oversight arrangements, its logging and who is accountable for it, and NIST AI RMF GOVERN expects documented ownership, intended use, risk tiering, third parties and change control. Those facts should come from the system itself rather than from a spreadsheet that ages. getSystemRegistry and updateSystemRegistry hold your side of the record — external system ID, owner, technical contact, intended and excluded uses, your own risk classification and review dates — so a CMDB can be the master and push values down.

The polling pattern

Poll on a schedule — nightly is enough. Every record carries recordHash, a stable hash over the record with the timestamp removed, so an unchanged system is a cheap no-op. When the hash moves, pull listSystemChanges for the narrative of what changed and write both to the configuration item.

state = { recordHash, lastSyncedAt }      # stored on your CI

record = complyee.describeSystem()

if record.recordHash != state.recordHash:
    ci.update(map(record))                       # see the field mapping below
    changes = complyee.listSystemChanges(        # needs the audit.read scope
        from = state.lastSyncedAt.date(),
        to   = today())
    for change in changes:
        ci.addChangeEntry(change.at, change.category, change.summary, change.actor)
    state.recordHash = record.recordHash

state.lastSyncedAt = now()

Field mapping

A workable default mapping onto a typical CI. Everything under deployerRegistry is your own statement, so it maps back to the fields your registry already owns.

system.name, system.vendorCI name and manufacturer / vendor
workspace.slug, workspace.addressInstance name and URL
deployerRegistry.externalSystemIdYour own CI or asset ID
deployerRegistry.owner, technicalContactBusiness owner and technical owner
purpose.intendedUse, purpose.excludedUsesIntended use and use restrictions
deployerRegistry.riskClassificationRisk tier and classification notes
model.model, model.regionPreference, model.groundingTechnical configuration attributes
components[]Dependency / relationship records (model, region, storage, interface, sub-processors)
data.retention, data.conversationLoggingRetention and logging attributes
accessControlsAccess control attributes (SSO, network, sessions, API)
recordVersion, recordHash, generatedAtSchema version, change detection and last-verified timestamp

There is no push or webhook: polling is what CMDB integrations do, and it means we never hold your endpoints or secrets. describeSystem, getSystemRegistry and getPostureSnapshot need no scope beyond a valid key; listSystemChanges and getSystemDossier need audit.read and updateSystemRegistry needs system.write.

Evidence for auditors: the AI system dossier

A registry records what the system is today. An auditor also asks what it was over a period and what changed. getSystemDossier answers that in one file: a plain-language cover, the full system record with your governance fields, the security checklist as it read at that moment, the configuration changes in the period, and a manifest counting the audit records held for it — by stream and by action. The audit records themselves are not copied; they stay in your own storage bucket, and the manifest tells an auditor what exists there.

Archive it quarterly or annually rather than nightly; describeSystem with recordHash is the cheap daily check. Workspace administrators can download the same file from the Security page without an integration. getPostureSnapshot returns just the checklist, if you want it on its own dashboard. Nothing in the dossier asserts that your organisation is compliant — it states what the system is, how it was configured and what evidence exists.

Agent governance

Complyee can run agents inside a workspace: bounded workers with their own identity, their own scoped key and their own run log. An agent never decides what it is allowed to do. Before it acts it asks a policy engine, and the engine answers from an ordered rule set — the same question always gets the same answer, and every answer is recorded with its reason.

Two families of operations. listAgents, listAgentRuns, listPolicyDecisions, listDocumentAbstracts, getDocumentAbstract, refreshLibraryAbstracts, describeAgentEcosystem, listEcosystemChanges and getAgentDossier describe or drive what happens inside the workspace; they stay in Complyee. decide, getDecision, listRules, putRuleSet, verifyDecision, explainDecision, listApprovals, decideApproval, revokeDecision and describePolicyEngine are the policy engine's own interface; pauseAgents and resumeAgents govern the agents themselves and stay in Complyee. Today the engine runs inside Complyee and serves them here; a workspace can be pointed at an external engine instead, in which case the same calls are served there with the same request and response shapes. Integrators write against one contract either way. The approval queue follows the engine: listApprovals and decideApproval are served by whichever engine the workspace points at, exactly as decide is, and an engine that cannot be reached blocks the waiting step rather than letting it through.

Decisions are bound to a payload

A decision carries a payloadHash, an expiry and a signature over both. The code that performs the side effect re-hashes what it is about to act on and refuses if the hash or the expiry does not match, so a grant for one document can never be replayed for another. Decision records — including the rationale and the request context — are written to the workspace's own storage under policy/decisions/; the database keeps only a non-sensitive index.

Nothing happens without a decision

Enforcement is not advisory. Every privileged step an agent takes runs through one guard: it asks the policy engine, verifies that the answer is a grant, is unexpired and is signed over the hash of exactly what the step is about to touch, records the step, and only then performs the work. A refusal, an expiry, a payload that does not match, a revoked decision, a step still waiting for a named person to approve it, a disabled agent, a paused workspace, a stop request or an engine that cannot answer all end the same way — the step is blocked and logged, never performed. Grants are single-use and short-lived, so one cannot be carried to the next step. Use getAgentRun to see, step by step, which decision allowed what.

Writing your own rules

A workspace can replace the built-in rule set by writing policy/rules.json into its own bucket. Rules are evaluated in order and the first match wins; if none match, defaultEffect applies. A file that cannot be parsed refuses every request rather than silently reverting to the built-in set.

{
  "version": "acme-2026-09",
  "defaultEffect": "reject",
  "rules": [
    { "id": "curator-read", "agent": "library-curator", "action": "document.read", "effect": "grant",
      "reason": "The curator may read documents of this workspace." },
    { "id": "no-hr-documents", "agent": "*", "action": "*", "resourcePrefix": "hr/", "effect": "reject",
      "reason": "HR material is out of scope for agents." }
  ]
}

Authentication and scopes

Each API key carries a set of scopes that decide which operations it may call. listOperations is always allowed and returns every operation with a callable flag, so an integration can discover both its own permissions and the full surface. Give each key only what its integration needs, and revoke keys that are no longer used.

Available scopes

  • audit.read Read the audit log — listAuditEvents, listSystemChanges, getSystemDossier, listEcosystemChanges. Returns audit events, the configuration change feed and the dated AI system dossier, including who asked what and when. Grant only to systems allowed to see this.
  • system.write Write the system registry — updateSystemRegistry. Lets a CMDB or AI registry push governance fields — external system ID, owner, purpose, risk classification, review dates — into this workspace. Reading them needs no scope.
  • policy.decide Ask the policy engine — decide. Lets an agent ask this workspace's policy engine whether an action is allowed. Grant only to agents, never to reporting integrations.
  • policy.read Read agents, decisions and rules — listAgents, listPolicyDecisions, getDecision, listRules, verifyDecision, explainDecision, describeAgentEcosystem, describePolicyEngine. Returns agent identities, the decisions the policy engine made and the active rule set. Rationales stay in your own storage; this returns the index only.
  • policy.admin Govern the policy engine — putRuleSet, listApprovals, decideApproval, revokeDecision, pauseAgents, resumeAgents. Changes what agents are allowed to do: the rule set, approvals of waiting steps, revocation of grants and the workspace-wide pause. The strongest scope there is — grant it only to a governance system you control.

Network restrictions

If the workspace has an IP allow-list, API calls must come from an approved network. A key can also carry its own list of approved IP ranges — that is the normal way to authorise a server-to-server integration in a network-restricted workspace. A network-scoped key answers only from those networks, regardless of where it is presented from.

Error codes

Failures return a non-200 status with an error.code you can branch on:

CodeHTTPMeaning
invalid_body400The request body is not valid JSON, or does not match { operation, parameters }.
invalid_parameters400A parameter failed validation; the message names the parameter.
invalid_api_key401The x-api-key header is missing or the key is unknown or revoked.
api_disabled403API access is not enabled for this workspace.
network_not_allowed403The calling network is not on the workspace or key allow-list.
scope_denied403The key does not hold the scope this operation requires.
unknown_operation404No such operation; call listOperations to see what the key may use.
not_found404The named resource (e.g. a document id) does not exist in this workspace.
rate_limited429The key's per-minute limit is exceeded; respect Retry-After.
internal_error500Something failed on our side; quote the requestId when contacting support.

Rate limits

Each key has a per-minute request limit shown in Workspace settings. Responses carry an X-RateLimit-Limit header; when the limit is exceeded the API answers 429 with error.code rate_limited and a Retry-After: 60 header. Back off and retry rather than hammering the endpoint.

Using the API safely

  • Call the API from your server or backend service. Never embed a key in browser JavaScript or mobile apps — an exposed key can be used to query your workspace documents.
  • If you need to call Complyee from a frontend, route the request through your own backend so the key stays server-side.
  • Browser-based API tools (e.g. Postman Web) may be blocked by our edge protection. Test with desktop Postman, curl, or a server script instead.
  • Store keys somewhere safe when generated — Complyee keeps only a hash and cannot show a key again.

API access is enabled per workspace. To have it switched on, contact us at hello@complyee.ai.