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.
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.
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.
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
Name
Type
Description
from
string
required
Start date, YYYY-MM-DD.
to
string
required
End date, YYYY-MM-DD.
limit
number
optional
Maximum number of changes to return, 1–500. Defaults to 100.
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.
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.
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.
{
"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
Name
Type
Description
externalSystemId
string
optional
The CI or asset ID this workspace carries in your registry.
ownerName / ownerEmail
string
optional
The accountable owner of the system on your side.
technicalContactName / technicalContactEmail
string
optional
Who to reach for integration and configuration questions.
businessPurpose
string
optional
What the workspace is used for, in your own words.
excludedUses
string
optional
Uses you have explicitly ruled out.
riskTier
unclassified | minimal | limited | high | prohibited
optional
Your own classification. Complyee does not assign or verify it.
riskNotes
string
optional
The reasoning behind the classification.
businessUnits
string
optional
Which parts of the organisation use the workspace.
approximateUsers
integer
optional
Roughly how many people use it.
humanReviewed
unspecified | always | sometimes | never
optional
Whether answers are reviewed by a person before they are acted on.
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.
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
Name
Type
Description
stream
admin | chat
required
Which audit stream to read.
from
string (YYYY-MM-DD)
required
Start of the date range, inclusive.
to
string (YYYY-MM-DD)
required
End of the date range, inclusive.
limit
number (1–2000, default 500)
optional
Maximum events to return; truncated is true when more exist.
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.
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.
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.
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.
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.
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
Name
Type
Description
agent
string
required
Agent key asking for permission.
action
string
required
document.read, model.invoke or library.write.
resourceRef
string
optional
What it wants to act on — a document id, a model name, a path.
payloadHash
string
required
SHA-256 hex over the exact payload that will be acted on.
{
"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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
{
"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.
{
"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."
}
}
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
Name
Type
Description
decisionId
uuid
required
The decision to check.
payloadHash
string
required
SHA-256, 64 hex characters, of the exact payload about to be acted on.
{
"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
Name
Type
Description
status
string
optional
pending (default), approved, rejected, expired or all.
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
Name
Type
Description
approvalId
uuid
required
The waiting request.
approve
boolean
required
true to approve, false to refuse.
reason
string
optional
Recorded 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." }
}'
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
Name
Type
Description
decisionId
uuid
required
The grant to withdraw.
reason
string
optional
Recorded 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." }
}'
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
Name
Type
Description
version
string
required
Your name for this rule set, quoted in every rationale.
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.
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.
Trigger: Recurrence — for example once every night.
Action: SharePoint "Get files (properties only)" on the policy library, so you get names, item ids and Modified timestamps.
Action: read your sync state — a plain SharePoint list with columns ExternalId and LastModified works well.
Apply to each file: condition — the file is new, or its Modified value is later than the stored LastModified.
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.
Action: SharePoint "Get file content" for the same item.
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.
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.
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.
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.
Dependency / relationship records (model, region, storage, interface, sub-processors)
data.retention, data.conversationLogging
Retention and logging attributes
accessControls
Access control attributes (SSO, network, sessions, API)
recordVersion, recordHash, generatedAt
Schema 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
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, listEcosystemChanges, 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.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, runs, decisions and rules — listAgents, listAgentRuns, listPolicyDecisions, getDecision, listRules, getAgentRun, verifyDecision, explainDecision, describeAgentEcosystem, describePolicyEngine. Returns agent identities, their run log, the decisions the policy engine made and the active rule set. Rationales stay in your own storage; this returns the index only.
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 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:
Code
HTTP
Meaning
invalid_body
400
The request body is not valid JSON, or does not match { operation, parameters }.
invalid_parameters
400
A parameter failed validation; the message names the parameter.
invalid_api_key
401
The x-api-key header is missing or the key is unknown or revoked.
api_disabled
403
API access is not enabled for this workspace.
network_not_allowed
403
The calling network is not on the workspace or key allow-list.
scope_denied
403
The key does not hold the scope this operation requires.
unknown_operation
404
No such operation; call listOperations to see what the key may use.
not_found
404
The named resource (e.g. a document id) does not exist in this workspace.
rate_limited
429
The key's per-minute limit is exceeded; respect Retry-After.
internal_error
500
Something 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.