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. The workspace subdomain is the only valid API address: a key presented at complyee.ai or at another workspace's subdomain is refused with 403 wrong_entry_point.

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

Twenty-nine operations cover the current API surface, in four groups: the workspace and its system record, documents and answers, document descriptions, and agents. 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.

Workspace and system

Discover the surface, read how this AI system is deployed for your workspace, keep your own governance fields in it, and pull the audit trail.

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": "listAgentPolicyDecisions",
      "parameters": "limit (number, optional)",
      "payloadKey": "decisions",
      "description": "Lists the decisions the policy engine made for this workspace.",
      "scope": "policy.read",
      "callable": false
    }
  ]
}

Common errors: invalid_api_key · wrong_entry_point · 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 · wrong_entry_point · network_not_allowed · 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 · wrong_entry_point · 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

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 · wrong_entry_point · 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

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

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

Documents and answers

The core service: ask questions of the approved library, and keep the library itself in step with your document management system.

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

Document descriptions

The short description held for each document, used to route questions to the right sources. Descriptions are written automatically after indexing and refreshed when a document changes.

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

Agents

Agents are the part of Complyee that does work on its own — today, describing the document library. Every step an agent takes is checked by the independent policy engine Agent Control Room at agent-cr.com before it happens; asking for a decision, verifying one and administering rules belong to that engine's own API, not to this one. The operations here describe the agents, drive their runs, hold them, and return this workspace's own record of the decisions Complyee acted on.

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": "remote",
      "policyEngineHost": "agent-cr.com",
      "lastRunAt": null,
      "runs": []
    }
  ]
}

Common errors: invalid_api_key · wrong_entry_point · network_not_allowed · rate_limited

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": "remote", "ruleSetVersion": "2026-09-01", "defaultEffect": "reject" },
    "enforcement": { "guard": "pep-1" },
    "library": { "described": 18, "total": 20 }
  }
}

Common errors: invalid_api_key · 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. The library curator also runs on its own — when a document finishes indexing, and on an hourly sweep for descriptions that are missing or out of date.

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

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

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

listAgentPolicyDecisions

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": "listAgentPolicyDecisions",
    "parameters": { "limit": 10 }
  }'
Example response
{
  "statusCode": 200,
  "operation": "listAgentPolicyDecisions",
  "requestId": "7d21b0aa-…",
  "decisions": [
    {
      "decisionId": "1f0e…",
      "agentKey": "library-curator",
      "action": "model.invoke",
      "resourceRef": "gemini-2.5-flash",
      "effect": "grant",
      "policyVersion": "acp-2026-09-01",
      "engineMode": "remote",
      "engineEndpoint": "agent-cr.com",
      "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

getAgentPolicyDecision

Required scope: policy.read

Returns one decision from this workspace's own decision index by its id — the fastest way to answer "why was this allowed?" when an audit quotes a decision id. The index is Complyee's record of the decisions it acted on; asking for decisions happens at the engine's own API.

Parameters
NameTypeDescription
decisionIduuidrequiredThe decision id to look up.
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": "getAgentPolicyDecision",
    "parameters": { "decisionId": "1f0e5c2b-…" }
  }'
Example response
{
  "statusCode": 200,
  "operation": "getAgentPolicyDecision",
  "requestId": "0be71c33-…",
  "decision": {
    "decisionId": "1f0e5c2b-…",
    "agentKey": "library-curator",
    "action": "document.read",
    "resourceRef": "doc_8891",
    "effect": "grant",
    "policyVersion": "acp-2026-09-01",
    "engineMode": "remote",
    "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

explainAgentPolicyDecision

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. Reads this workspace's own decision index — Complyee's record of the decisions it acted on.

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": "explainAgentPolicyDecision",
    "parameters": { "decisionId": "3d47…" }
  }'
Example response
{
  "statusCode": 200,
  "operation": "explainAgentPolicyDecision",
  "requestId": "cc31be04-…",
  "explanation": {
    "decisionId": "3d47…",
    "effect": "grant",
    "action": "document.read",
    "policyVersion": "2026-09-01",
    "engineMode": "remote",
    "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

listAgentEcosystemChanges

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": "listAgentEcosystemChanges",
    "parameters": { "from": "2026-08-01", "to": "2026-09-04" }
  }'
Example response
{
  "statusCode": 200,
  "operation": "listAgentEcosystemChanges",
  "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

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.

The operations on this page describe or drive what happens inside the workspace: agents, runs, document descriptions, the ecosystem registration and Complyee's own decision index — the record Complyee keeps of the decisions it acted on. Asking for a decision, verifying one, and the engine's self-description are the policy engine's own interface and are served by the engine at agent-cr.com, not by Complyee. Rules, approvals and revocation belong to the engine and are administered there. An engine that cannot be reached blocks the 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.

Where the rules live

Rules and approvals live in the independent policy engine Agent Control Room at agent-cr.com and are administered there — never through Complyee. Every decision Complyee records names the rule set version it was answered from, so an answer can always be traced back to the rules in force at the time. An engine that cannot be reached refuses every request rather than falling back to anything local.

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

  • prompt — Ask questions — prompt. Answers questions from this workspace's approved documents.
  • documents.read — List documents and their descriptions — listDocuments, listDocumentAbstracts, getDocumentAbstract. Returns document names, status, sizes and the short description the library curator wrote for each — no file contents.
  • documents.write — Upload and delete documents — createDocumentUpload, finalizeDocumentUpload, deleteDocument. Adds documents to the workspace and permanently deletes them from storage and the search index. Uploading with an external id replaces the document that carries that id.
  • audit.read — Read the audit log — listAuditEvents, listSystemChanges, getSystemDossier, listAgentEcosystemChanges, getAgentDossier. 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.read — Read agents, runs and decisions — listAgents, listAgentRuns, listAgentPolicyDecisions, getAgentPolicyDecision, getAgentRun, explainAgentPolicyDecision, describeAgentEcosystem. Returns agent identities, their run log and the decision index — Complyee's own record of the decisions the independent policy engine made and Complyee acted on. Rationales stay in your own storage; this returns the index only. Asking for decisions happens at the engine's own API, not here.
  • agents.run — Start and stop agent runs — runAgent, stopAgentRun, refreshLibraryAbstracts. Starts and stops agent runs. Every step of a run is still checked against the policy engine, so this grants the right to trigger work, never the right to perform it.
  • policy.admin — Govern the agents — pauseAgents, resumeAgents. Stops and restarts agent work in this workspace. Rules and approvals live in the independent policy engine and are administered there.

Network restrictions

Calls must arrive at the workspace's own subdomain, https://<tenant>.complyee.ai/api/public/v1/gateway — that address is the only entry point our edge serves, and it is where the visitor address is established. 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.
wrong_entry_point403The key was presented at an address other than its own workspace subdomain.
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.