Skip to main content
Main content

Developer Portal

Build on Vouli IQ

A REST API over your organisation's governance data — scores, risks, signals, roadmap, compliance, vendors, talent, incidents and KPIs — that your other systems can also push into: risks, incidents, KPI actuals and vendor inventory accept writes, and webhook subscriptions and the audit export can be managed with a key. Plus signed outbound webhooks. One envelope, cursor pagination, per-key rate limits, and a sandbox you can point CI at.

Current version
2026-09-12
Base URL
https://www.vouliiq.com/api/v1

Interactive reference · Postman collection

Authentication

Every request carries an API key as a bearer token. Keys are minted in Dashboard → API keys by an admin, are scoped per resource family, and are stored only as a scrypt hash — the plaintext is shown once.

curl https://www.vouliiq.com/api/v1/risks?limit=10 \
  -H "Authorization: Bearer vk_live_XXXXXXXX_your_secret" \
  -H "Vouli-Version: 2026-09-12"
  • vk_live_… reads your real data. vk_test_… reads the sandbox.
  • Keys minted before the namespace rename begin bk_ and remain valid indefinitely.
  • The x-api-key header still works but is deprecated (sunset 12 Sep 2027). It runs through the identical authentication, scope and rate-limit path — move to Authorization: Bearer.
  • Rotation: mint a new key, switch your callers, revoke the old one. Revocation takes effect immediately.
  • Building an app that other organisations connect? Use OAuth 2.0 instead of a key: its access tokens (vou_at_…) are accepted everywhere a key is.

Resources and scopes

A key must hold the resource's scope (or read:all). A key without it gets 403 insufficient_scope.

Resources and scopes
EndpointReturnsScopeDescription
GET /api/v1/scoreslist of scoreread:scoresList completed assessment scores, newest first.
GET /api/v1/scorecardscorecardread:scorecardRetrieve the most recent completed SOVEREIGN scorecard.
GET /api/v1/riskslist of riskread:risksList the AI risk register, newest first.
GET /api/v1/signalslist of signalread:signalsList the current signal set.
GET /api/v1/roadmaproadmapread:roadmapRetrieve the active transformation roadmap.
GET /api/v1/roadmap/milestoneslist of milestoneread:roadmapList milestones of the active roadmap, newest first.
GET /api/v1/compliancecompliance_postureread:complianceRetrieve the active compliance posture.
GET /api/v1/compliance/obligationslist of obligationread:complianceList compliance obligations, newest first.
GET /api/v1/vendorvendor_portfolioread:vendorRetrieve the active AI vendor portfolio posture.
GET /api/v1/vendor/inventorylist of vendorread:vendorList the AI vendor inventory, newest first.
GET /api/v1/talenttalent_readinessread:talentRetrieve the active talent readiness assessment.
GET /api/v1/talent/gapslist of talent_gapread:talentList talent gaps, newest first.
GET /api/v1/incidentslist of incidentread:incidentsList AI incidents, newest first.
GET /api/v1/kpislist of kpiread:kpisList KPIs (key results) with their targets and latest actuals, newest first.
GET /api/v1/metricslist of metricread:metricsList approved metric definitions — the governed meaning of each KPI.
GET /api/v1/metrics/valueslist of metric_valueread:metricsRead a metric’s values point-in-time: as of a date, as known at a date.
GET /api/v1/documentslist of documentread:documentsList knowledge-base documents — metadata only, newest first.
GET /api/v1/board/voteslist of board_voteread:boardList board votes — metadata only.
GET /api/v1/board/documentslist of board_documentread:boardList board documents' signature status — never their text.
GET /api/v1/board/packslist of board_packread:boardList board packs' status — never their content.
GET /api/v1/decisionslist of decisionread:decisionsList the Decision Memory register (Enterprise).
GET /api/v1/insightslist of insightread:insightsList AI insights — metadata and the stored headline, flagged ai_generated.

Writes and management

External systems can push data in. Each operation needs its own scope — read:all never grants a write: or manage: scope, nor read:audit or the scopes added since (such as read:metrics), which are granted by name — and the plan that includes the module (otherwise 403 feature_not_in_plan). A write key acts for your organisation with contributor-level rights; webhook management and the audit export are admin-level. Every write runs through the same rules as the app (an unevidenced severity downgrade is refused, a KPI actual follows its latest period) and is recorded in your audit log as api.write.<resource> with the key that made it.

  • Bodies are JSON and strict: an unknown field is a 400 unknown_field; org_id or any user-id or authorship field is a 400 forbidden_field. The organisation is always the key's.
  • An id from another organisation is a 404, exactly like an id that does not exist.
  • Records cannot be deleted through the API (except webhook endpoints); deletion stays in the app.
  • POST /kpis/checkins takes up to 500 data points and is all or nothing: one bad point refuses the batch.
  • Every list accepts updated_since (an ISO date or timestamp) for incremental sync.
Write and management operations
EndpointScopePlanIdempotency-KeyDescription
POST /api/v1/riskswrite:risksMax+requiredAdd a risk to the register.
PATCH /api/v1/risks/{id}write:risksMax+optionalChange a risk.
POST /api/v1/incidentswrite:incidentsMax+requiredDeclare an AI incident.
PATCH /api/v1/incidents/{id}write:incidentsMax+optionalChange an incident.
POST /api/v1/kpis/checkinswrite:kpisMax+requiredPush KPI actuals — up to 500 data points, all or nothing.
POST /api/v1/vendor/inventorywrite:vendorMax+requiredAdd a vendor to the AI vendor inventory.
POST /api/v1/eventswrite:eventsMax+requiredPush signed metric events from your own systems (Event API).
POST /api/v1/documentswrite:documentsMax+requiredAdd a document to the AI knowledge base (descriptor, or the file inline up to 4 MB).
POST /api/v1/documents/{id}/processwrite:documentsMax+requiredCheck and process an uploaded document.
PATCH /api/v1/vendor/inventory/{id}write:vendorMax+optionalChange a vendor’s working state.
GET /api/v1/webhooks/subscriptionsmanage:webhooksMax+noneList webhook subscriptions, newest first.
POST /api/v1/webhooks/subscriptionsmanage:webhooksMax+requiredRegister a webhook endpoint.
PATCH /api/v1/webhooks/subscriptions/{id}manage:webhooksMax+optionalPause, resume or change a webhook endpoint.
DELETE /api/v1/webhooks/subscriptions/{id}manage:webhooksMax+optionalDelete a webhook endpoint.
POST /api/v1/webhooks/subscriptions/{id}/testmanage:webhooksMax+requiredSend a signed test `ping` to one endpoint, now.
GET /api/v1/documents/{id}read:documentsMax+noneRetrieve one document's metadata and status.
DELETE /api/v1/documents/{id}write:documentsMax+optionalDelete a document from the knowledge base.
GET /api/v1/auditread:auditEnterprise+noneExport the audit log, oldest first (Enterprise).

Verifying a webhook: both SDKs ship verifyWebhookSignature (verify_webhook_signature in Python), generated from the code that signs deliveries. Pass the raw body, the request headers and your endpoint's secret: the timestamped X-Vouli-Signature-V2 is checked within a 300-second window, and requireV2 (require_v2) stops it falling back to the older X-Vouli-Signature. POST /webhooks/subscriptions/{id}/test sends a signed ping so you can prove it before going live.

Push events from your systems (Event API)

Create a push source in Dashboard → Grounding → Push sources (an administrator creates it, a different administrator approves it) and send up to 100 events — 500 metric points — per request to POST /api/v1/events. Pushed values reach the signal bus near-real-time (seconds after a push); a key with an approved metric definition also lands in that governed series. Included on the Max plan and above.

  • Sign <Vouli-Timestamp>.<raw body> with HMAC-SHA256 and the source's secret; send Vouli-Source, Vouli-Timestamp (unix seconds, within ±5 minutes) and Vouli-Signature: v1=<hex>. Each signature is accepted once: sign every retry afresh.
  • Or, for a source that allows keys, send an API key holding write:events with Vouli-Source.
  • An Idempotency-Key is required. An event whose external_id and type the source sent before is counted as a duplicate, not written twice.
  • org_id and source are refused anywhere in the body. Strings are neutralised before storage; do not send personal data.
  • The source's secret can be rotated: the previous one keeps working for 24 hours.
BODY='{"events":[{"type":"finance.month_closed","occurred_at":"2026-10-01T00:00:00Z","external_id":"close-2026-09","metrics":[{"key":"finance.cash_usd","value":1250000,"unit":"USD"}]}]}'
T=$(date +%s)
SIG=$(printf '%s' "$T.$BODY" | openssl dgst -sha256 -hmac "$VOULI_PUSH_SECRET" | sed 's/^.* //')
curl -X POST https://www.vouliiq.com/api/v1/events \
  -H "Content-Type: application/json" -H "Idempotency-Key: $(uuidgen)" \
  -H "Vouli-Source: $VOULI_SOURCE_ID" -H "Vouli-Timestamp: $T" -H "Vouli-Signature: v1=$SIG" \
  --data "$BODY"
// TypeScript
import { signEventRequest } from '@vouli-iq/sdk';
const headers = await signEventRequest(body, secret);   // Vouli-Timestamp + Vouli-Signature

# Python
from vouli_iq import sign_event_request
headers = sign_event_request(body, secret)

Response envelopes

Two success shapes and one error shape, across the whole API. Read your rows from data.

// GET /api/v1/risks
{
  "object": "list",
  "api_version": "2026-09-12",
  "data": [ { "object": "risk", "id": "…", "severity": "HIGH", … } ],
  "has_more": true,
  "next_cursor": "djF8MjAyNi0wOS0wMVQwOTowMDowMC4wMDBafC4uLg"
}

// GET /api/v1/scorecard  (null until the first assessment completes)
{ "object": "scorecard", "api_version": "2026-09-12", "data": { … }, "has_more": false, "next_cursor": null }

Pagination

Collections are cursor-paginated (keyset, not offset — pages never skip or repeat a row when data changes underneath you). Pass limit (1–100, default 25) and the previous response's next_cursor. A cursor is opaque: hand it back unchanged. A cursor we did not issue returns 400 invalid_cursor.

# page 1
curl "https://www.vouliiq.com/api/v1/risks?limit=50" -H "Authorization: Bearer $VOULI_API_KEY"

# page 2 — pass next_cursor back
curl "https://www.vouliiq.com/api/v1/risks?limit=50&cursor=$NEXT_CURSOR" -H "Authorization: Bearer $VOULI_API_KEY"

Both SDKs will follow the cursors for you: listAll() in TypeScript, list_all() in Python.

Rate limits

Per-key token bucket: the capacity equals your per-minute limit (so a burst up to it is allowed) and refills at limit/60 per second, with a second rolling 24-hour cap. Limits come from your plan and can be overridden per key. Every response — success or failure — tells you where you stand.

Rate-limit headers
HeaderMeaning
X-RateLimit-LimitRequests allowed per minute for this key.
X-RateLimit-RemainingRequests still available right now.
X-RateLimit-ResetUnix time (seconds) at which the bucket is full again.
X-RateLimit-Limit-DayRequests allowed per rolling 24 hours for this key.
Retry-AfterOn a 429 only: seconds to wait before retrying.

Errors

Every 4xx and 5xx has the same body. Branch on code — it is stable. The message is for humans and may be reworded. The request_id is also returned as X-Request-Id; quote it in a support request.

{
  "error": {
    "type": "permission_error",
    "code": "insufficient_scope",
    "message": "This key does not have the \"read:risks\" scope.",
    "request_id": "req_9f2c1d4e8a7b40f1a0c3e5d7b9f10246"
  }
}

Every code we emit

missing_credential · invalid_api_key · tier_required · subscription_inactive · insufficient_scope · invalid_parameter · invalid_cursor · unsupported_version · rate_limit_exceeded · idempotency_key_reused · idempotency_in_flight · resource_not_found · feature_not_in_plan · idempotency_key_required · invalid_body · unknown_field · forbidden_field · payload_too_large · unsupported_media_type · not_found · resource_conflict · validation_failed · service_unavailable · internal_error

Versioning and deprecation

Additive changes (a new field, a new resource, a new enum member on a field documented as extensible) ship without a version bump — clients must ignore unknown fields. A BREAKING change ships as a new dated version: the operation gains an `x-deprecation` block naming the `sunset` date and its replacement, responses start carrying `Deprecation` and `Sunset` headers, and the old version keeps being served until that date. Pin a version with the `Vouli-Version` request header; omit it and you get the current one.

  • Currently served: 2026-09-12.
  • Omit Vouli-Version and you get the current version; an unknown value is a 400 unsupported_version.
  • A deprecated operation carries an x-deprecation block in the spec and Deprecation / Sunset headers on its responses.
  • Treat unknown response fields as additive and ignore them — that is what keeps an additive change non-breaking.

Idempotency

Every POST that changes data REQUIRES an Idempotency-Key header (without one it is a 400 idempotency_key_required); PATCH and DELETE accept one. The first response for a key is cached for 24 hours and replayed — with Idempotency-Replayed: true — for any retry carrying the same key, so a network blip never double-creates. The same key with a different request body is a 409 idempotency_key_reused; a retry while the first request is still running is a 409 idempotency_in_flight. Both SDKs generate a key per call and resend it on every automatic retry.

Sandbox

Mint a Test key and it returns a fixed set of synthetic rows for every endpoint — every human-readable value prefixed [SANDBOX], every id in a reserved all-zero UUID range. A test key never reads tenant data, so you can build against it, demo with it and run it in CI. Responses carry Vouli-Environment: test. A write or management call from a test key is answered with a realistic simulated record (marked Vouli-Simulated: true) and writes nothing; a test webhook ping is simulated and sends nothing.

Connect AI assistants (MCP)

Vouli IQ is also a Model Context Protocol server. Connect Claude, ChatGPT, Copilot or any other MCP client with an API key, and the assistant can answer questions from your organisation's governance data — the same resources as the REST API, with the same scopes, rate limit and sandbox. Every tool is read-only. Included on the Max plan and above.

Endpoint
https://www.vouliiq.com/api/v1/mcp
Transport
Streamable HTTP with JSON responses; protocol versions 2025-06-18, 2025-03-26, 2024-11-05. Authenticate with Authorization: Bearer vk_live_….

Claude Code

claude mcp add --transport http vouli-iq https://www.vouliiq.com/api/v1/mcp \
  --header "Authorization: Bearer $VOULI_API_KEY"

Claude Desktop

Claude Desktop starts local servers from claude_desktop_config.json. The open-source mcp-remote package (needs Node.js) bridges it to this endpoint and sends your key as a header:

{
  "mcpServers": {
"vouli-iq": {
  "command": "npx",
  "args": ["-y", "mcp-remote", "https://www.vouliiq.com/api/v1/mcp",
           "--header", "Authorization:${VOULI_AUTH}"],
  "env": { "VOULI_AUTH": "Bearer vk_live_XXXXXXXX_your_secret" }
}
  }
}

Any other MCP client

Give the client the endpoint URL and the key as a request header. Most clients that speak Streamable HTTP take a configuration of this shape:

{
  "mcpServers": {
"vouli-iq": {
  "type": "http",
  "url": "https://www.vouliiq.com/api/v1/mcp",
  "headers": { "Authorization": "Bearer vk_live_XXXXXXXX_your_secret" }
}
  }
}

# Or check the handshake by hand:
curl https://www.vouliiq.com/api/v1/mcp \
  -H "Authorization: Bearer $VOULI_API_KEY" -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Tools

MCP tools
ToolReturnsScope
list_scoresList completed assessment scores, newest first.read:scores
get_scorecardRetrieve the most recent completed SOVEREIGN scorecard.read:scorecard
list_risksList the AI risk register, newest first.read:risks
list_signalsList the current signal set.read:signals
get_roadmapRetrieve the active transformation roadmap.read:roadmap
list_roadmap_milestonesList milestones of the active roadmap, newest first.read:roadmap
get_complianceRetrieve the active compliance posture.read:compliance
list_compliance_obligationsList compliance obligations, newest first.read:compliance
get_vendorRetrieve the active AI vendor portfolio posture.read:vendor
list_vendor_inventoryList the AI vendor inventory, newest first.read:vendor
get_talentRetrieve the active talent readiness assessment.read:talent
list_talent_gapsList talent gaps, newest first.read:talent
list_incidentsList AI incidents, newest first.read:incidents
list_kpisList KPIs (key results) with their targets and latest actuals, newest first.read:kpis
list_metricsList approved metric definitions — the governed meaning of each KPI.read:metrics
list_metrics_valuesRead a metric’s values point-in-time: as of a date, as known at a date.read:metrics
list_documentsList knowledge-base documents — metadata only, newest first.read:documents
list_board_votesList board votes — metadata only.read:board
list_board_documentsList board documents' signature status — never their text.read:board
list_board_packsList board packs' status — never their content.read:board
list_decisionsList the Decision Memory register (Enterprise).read:decisions
list_insightsList AI insights — metadata and the stored headline, flagged ai_generated.read:insights
  • Read-only by design: each tool reads one resource and nothing else. None creates, changes or deletes anything in Vouli IQ.
  • Your organisation only: results always belong to the organisation that owns the key, and no tool argument can select another one.
  • Scopes: a tool needs its resource's scope (or read:all). Without it the tool answers This API key does not have the read:risks scope. Mint a dedicated key for each assistant with only the scopes it needs, and revoke it when you disconnect.
  • Sandbox: a vk_test_… key returns the same [SANDBOX] sample rows as the REST sandbox and never your data.
  • Rate limit: every tool call spends one request from the key's API rate limit. Connection set-up messages are limited separately.
  • The server tells the assistant to treat field values as your records, not as instructions to follow.

Connecting your own MCP server to Vouli IQ

The other direction: Live Data Grounding reads figures from an MCP server you run. What your server must support today:

  • A public HTTPS address (private and reserved addresses are refused, including after a redirect) speaking MCP Streamable HTTP. Vouli IQ sends initialize with protocol version 2025-06-18 and accepts 2025-06-18, 2025-03-26, 2024-11-05; it keeps the Mcp-Session-Id you assign and reads JSON or SSE replies (up to 5 MB each). A server that answers only bare JSON-RPC still works.
  • Authentication: an API token, sent as Authorization: Bearer …. OAuth 2.1, client credentials and mutual TLS are coming soon and are refused at registration until then — never quietly replaced by a token.
  • Read-only tools (readOnlyHint: true, or a read-style name). Write tools are never called. tools/list pagination is followed up to 20 pages or 1,000 tools; a tool result with isError: true is treated as a failure, never as data.
  • Nothing reaches an AI model unless an admin turns on “Use in AI” for the connection; values then enter briefs as quoted data with their source and age. Synced near-real-time: every 30–90 minutes, or on demand.

Build apps for other organisations (OAuth 2.0)

Partner apps connect with OAuth 2.0 using the authorization code flow with PKCE: an organisation admin approves exactly the scopes shown on the consent screen, and can disconnect the app at any time in Settings → Connected apps. An API key is for your own organisation's integrations; an app that other organisations connect uses OAuth, so nobody ever pastes a key into it.

  1. Register the app in the developer console (sign in; an organisation admin registers). Give its redirect URIs — https, or http://localhost while you develop — and the scopes it needs. The client secret is shown once and can be rotated.
  2. Send the admin to the authorization endpoint with response_type=code, your client_id, an exact registered redirect_uri, the scope, a state and a PKCE code_challenge with code_challenge_method=S256. The consent screen names your app, your organisation as its publisher, each scope in plain language and the organisation it would act on. Only an admin can approve.
  3. Exchange the code within 60 seconds at the token endpoint, authenticating with your client secret (HTTP Basic or in the body) and sending the code_verifier. A code works once; presenting it again revokes the tokens it bought.
  4. Call /api/v1 or the MCP server with Authorization: Bearer vou_at_…. Access tokens last 1 hour; refresh tokens last up to 30 days, work once and are replaced on every refresh. A refresh token used twice revokes the whole chain.
  5. Live data needs the connecting organisation's plan to include the public API (Max and above).
OAuth endpoints
EndpointMethodURL
AuthorizationGEThttps://www.vouliiq.com/oauth/authorize
TokenPOSThttps://www.vouliiq.com/api/oauth/token
Revocation (RFC 7009)POSThttps://www.vouliiq.com/api/oauth/revoke
Server metadata (RFC 8414)GEThttps://www.vouliiq.com/.well-known/oauth-authorization-server
curl https://www.vouliiq.com/api/oauth/token \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d grant_type=authorization_code \
  -d code="$CODE" \
  -d redirect_uri="https://your-app.example.com/callback" \
  -d code_verifier="$CODE_VERIFIER"

An admin sees every connected app, and disconnects it, in Settings → Connected apps; uninstalling the app from the marketplace revokes its tokens too. Revoke a token yourself with the revocation endpoint (revoking a refresh token revokes its access tokens).

Scopes for apps

The same scopes as API keys, checked by the same code. read:all never grants a write: or manage: scope, nor read:audit. A token can use only what the admin approved; if the admin later approves fewer scopes, tokens already issued lose the rest at once. This is the wording the consent screen shows.

OAuth scopes and what the consent screen says
ScopeThe consent screen says the app can
read:allSee your scores, scorecard, roadmap, risks, signals, compliance, vendors, talent, incidents and KPIs (not the audit log)
read:scoresSee your assessment score history
read:scorecardSee your latest SOVEREIGN scorecard
read:roadmapSee your roadmap and milestones
read:risksSee your risk register
read:signalsSee your cockpit signals
read:complianceSee your compliance posture and obligations
read:vendorSee your vendor portfolio and inventory
read:talentSee your talent readiness and skill gaps
read:incidentsSee your AI incidents
read:kpisSee your KPIs and key results
read:auditExport your audit log (Enterprise plan)
read:metricsSee your governed metric definitions and their values, including past values as they were known at the time
read:documentsSee the titles and status of the documents in your knowledge base (never their content)
read:boardRead board metadata (votes, signature status, pack status — no content)
read:decisionsSee your Decision Memory register, including the decision and rationale text
read:insightsSee your AI insights' module, time, quality score and headline (marked as AI output)
write:risksCreate and change risks in your risk register (changes data)
write:incidentsDeclare and update AI incidents (changes data)
write:kpisRecord KPI actuals (changes data)
write:vendorAdd and change vendors in your inventory (changes data)
manage:webhooksCreate, change and delete the webhook endpoints that receive your events (changes data)
write:eventsPush metric events from your systems to the push sources an admin allowed for this app (changes data)
write:documentsAdd documents to your knowledge base, and delete them (changes data)

Developer sandbox workspace

The developer console gives your organisation a free developer sandbox workspace: apps connected to it are served sample data, writes are simulated, and no paid plan is needed. Choose “Developer sandbox” on the consent screen and the token is served the same 42 sample rows test keys see — every human-readable value marked [SANDBOX], responses marked Vouli-Environment: test — and never any organisation's records, yours included. An app that has not been approved yet can already be connected by its own organisation, to the sandbox or to that organisation's own data.

Rate limits for apps

  • Each connection (one app in one organisation) has its own limit at the organisation's plan rate, with the same X-RateLimit-* headers and 429 + Retry-After as a key. For OAuth tokens the limit is currently counted by each server instance, so treat the headers as the authority.
  • Sandbox tokens: 60 requests a minute and 10,000 a day, whatever the plan.
  • The token and revocation endpoints are limited per IP address; a 429 carries Retry-After.

App review

A new app is a Draft. When it is ready, submit it from the console: it becomes Submitted, then In review when a Vouli IQ reviewer takes it, and then Approved or Rejected — a rejection always carries the reviewer's reason, which you can read in the console. Only an approved app is listed in the marketplace catalogue and can be connected by other organisations. Approval records that a reviewer looked at what you submitted; it is not a certification or an endorsement, and nothing may describe it as one.

Partner programme

We are setting up a partner programme for technology partners, who build apps and integrations on the API, and services partners, who implement Vouli IQ or advise the boards that use it. If you are interested, tell us about yourself here; the details go to the Vouli IQ team.

I am interested as a

We use these details only to reply to you about the partner programme. See the privacy notice.

Quickstart — TypeScript

# Not on npm yet: registry publication is pending the first release.
# Until then, build and install it from the repository's sdk/ directory.
(cd sdk/vouli-iq && npm install && npm run build)
npm install ./sdk/vouli-iq

import { VouliIQ } from '@vouli-iq/sdk';

const vouli = new VouliIQ({ apiKey: process.env.VOULI_API_KEY! });

// One page, with the pagination state.
const page = await vouli.risks({ severity: 'HIGH', limit: 50 });
console.log(page.data.length, page.has_more);

// Or let the SDK follow the cursors.
for await (const risk of vouli.listAll('risks', { severity: 'HIGH' })) {
  console.log(risk.risk_id);
}

Quickstart — Python

# Not on PyPI yet: registry publication is pending the first release.
# Until then, install it from the repository's sdk/ directory.
pip install ./sdk/vouli-iq-python

import os
from vouli_iq import VouliIQ

vouli = VouliIQ(api_key=os.environ["VOULI_API_KEY"])

page = vouli.risks(severity="HIGH", limit=50)
print(len(page["data"]), page["has_more"])

for risk in vouli.list_all("risks", severity="HIGH"):
    print(risk["risk_id"])

Stdlib-only (no third-party dependencies). Both SDKs' typed surfaces are generated from the OpenAPI document and a CI check fails the build if regeneration produces any diff — so an SDK method, its parameters and its return type cannot drift from the API. The SDKs ship from the repository today; registry publication is pending the first tagged release. Until that release is announced here, do not install a package called vouli-iq or @vouli-iq/sdk from a public registry: it is not ours.

Webhooks

Subscribe an endpoint to receive signed events (HMAC-SHA256 with your endpoint's secret). Deliveries retry with backoff and dead-letter; an admin can replay a dead-lettered delivery from Settings → Webhooks in the dashboard, or through the deliveries endpoint. Every envelope carries api_version. Board, decision, insight and KPI events are opt-in by name: a subscription with no event types never receives them, and the subscribing key needs the scope shown (ids and status only, never content).

Verifying a delivery

Every delivery is signed at the moment it is sent, and each retry is signed again. Verify X-Vouli-Signature-V2: it is t=<unix seconds>,v2=<hex>, where the hex is the HMAC-SHA256 of <t>.<raw body>. Compare in constant time, and refuse a delivery whose t is more than 300 seconds from your clock, so a captured request cannot be replayed later (X-Vouli-Timestamp carries the same time). The older X-Vouli-Signature (sha256=<hex> over the body alone, no timestamp) is still sent for existing receivers; accept it alone only until you have moved, because it does not protect against replay. During a secret rotation each header carries one signature per valid secret; a match on any of them is valid. Use the event id to ignore a delivery you have already processed.

// TypeScript SDK — prefers v2, refuses anything outside the 300 s window
import { verifyWebhookSignature } from '@vouli-iq/sdk';
const ok = await verifyWebhookSignature(rawBody, req.headers, secret, { requireV2: true });

# Python SDK
from vouli_iq import verify_webhook_signature
ok = verify_webhook_signature(raw_body, request.headers, secret, require_v2=True)
Webhook events
EventWhenStability
assessment.completedA maturity assessment finished and a new SOVEREIGN scorecard is available.stable
verdict.changedThe org’s headline verdict band changed versus the previous assessment.stable
roadmap.updatedThe active roadmap or one of its milestones changed.stable
risk.createdA new risk was added to the register.stable
risk.severity_changedAn existing risk’s severity was re-rated.stable
compliance.posture_changedA new compliance assessment went live and the overall posture, or a regime’s posture, moved versus the previous version (one event per movement; never on the first assessment or when nothing moved).stable
evidence.pack_filedAn Evidence Vault pack was assembled and filed (stored ready with every item). manifest_sha256 is the sha256 of the manifest’s canonical JSON (keys sorted).stable
board.vote_openedA board or circular resolution was put to a vote. Ids and timing only — never the motion. Committee votes only when the board allows them. Opt-in by name; needs read:board.beta
board.vote_closedA vote closed or was cancelled. The outcome is included only when the board allows it (and after any embargo); never tallies, names or the motion. Opt-in by name; needs read:board.beta
board.signature_requestedA board document (minutes, a written resolution, a declaration) was approved and sent for signature. The signer count, never who. Opt-in by name; needs read:board.beta
board.pack_publishedA board pack was finalised. Ids and scope only — never its title or content. Opt-in by name; needs read:board.beta
decision.recordedA decision was recorded in Decision Memory. Ids, status and date only. Opt-in by name; needs read:decisions.beta
insight.generatedAn AI insight was generated and stored. Ids and the module only — flagged ai_generated. Opt-in by name; needs read:insights.beta
kpi.threshold_breachedA governed metric's current value crossed into RED. The key, version and threshold — the measured value only when the board allows KPI values (include_kpi_values, default off). No name or description. Opt-in by name; needs read:metrics.beta

Automation templates

Ready-made receivers for Vouli IQ webhooks. Each one checks X-Vouli-Signature (HMAC-SHA256 of the raw body with your subscription’s secret) before anything else, answers 401 when it does not match, and routes the 7 core event types to their own branches.

  • n8n Download the workflow (JSON) — import it from Workflows, then set the signing secret.
  • Make Download the scenario blueprint (JSON) — import it, turn on JSON pass-through and request headers on the webhook, paste the secret into both filters.
  • Zapier Today: add a “Webhooks by Zapier” Catch Hook URL under Settings → Integrations. A Vouli IQ Zapier app definition exists but has not been published, so there is no public Vouli IQ Zapier app yet.

Actions: create and update records

The same packages can also write to Vouli IQ through the API v1 write endpoints: create or update a risk, create or update an incident, add a vendor, and record a KPI actual. Every call sends Authorization: Bearer, Vouli-Version and an Idempotency-Key derived from its input, so a retry is answered with the first response instead of writing twice. The key needs the matching scope: write:risks, write:incidents, write:vendor or write:kpis (read:all never grants one), and the plan must include that module.

  • n8n Download the Jira to incident workflow (JSON) — a Jira issue becomes a Vouli IQ incident; import it into your own n8n.
  • Make Download the actions blueprint (JSON) — one HTTP route per action; import it, then paste your API key and a relay token.
  • Zapier The Vouli IQ Zapier app definition now also carries these six actions and three polling triggers built on updated_since, but it has not been published. Today, a “Webhooks by Zapier” Custom Request step can call the same endpoints with the headers above.

SDKs and integrations: where each one is

One table, from one declared source: an install command appears only once a package or listing is published.

SDKs and directory apps, their publication status, and how to install or build each one
SDK or appStatusHow to get it
TypeScript SDK (npm)Not yet publishedFrom the repository: sdk/vouli-iq
Python SDK (PyPI)Not yet publishedFrom the repository: sdk/vouli-iq-python
Zapier appNot yet publishedFrom the repository: integrations/zapier
MakeNot yet publishedFrom the repository: integrations/make (scenario blueprints)
n8nNot yet publishedFrom the repository: integrations/n8n (workflow templates)
Power AutomateNot yet publishedFrom the repository: not built yet
MCP RegistryNot yet publishedFrom the repository: docs/store-submissions/mcp-registry/server.json
Slack App DirectoryNot yet publishedFrom the repository: docs/slack-app
Microsoft Teams storeNot yet publishedFrom the repository: docs/teams-app

Identity provisioning (SCIM 2.0)

On the Enterprise plan, your identity provider — Okta, Microsoft Entra ID or any SCIM 2.0 client — can add, update and remove people in Vouli IQ automatically, and set each person's role from the directory groups they belong to.

Base URL
https://www.vouliiq.com/api/scim/v2
Authentication
Authorization: Bearer scim_… — a token a workspace administrator issues in Settings → Enterprise Access. It is shown once and is valid for one year.
  • /Users and /Groups: create, read, update and replace; filtering and sorting; pages of up to 200 (default 100); version tags with If-Match; /Bulk of up to 100 operations. /ServiceProviderConfig lists exactly what is supported.
  • Removing a person (DELETE, or setting active to false) takes them out of your organisation at once and cancels the API keys they created in it. If yours is the only organisation they belong to, their Vouli IQ sign-in is switched off too. Nothing is deleted: their record and the audit trail are kept, and setting active back to true restores their access and role.
  • Details Vouli IQ does not keep — job title, phone numbers, addresses, and the enterprise fields such as department, employee number and manager — are accepted and set aside, so a directory's standard attribute mapping never causes an update to fail.

Roles from directory groups

A group's name decides the role it gives. When a person is in several matching groups, the most senior role wins. A group whose name contains none of the words below gives no role and changes no one's role. Administrator access is only ever given by a group whose name says so. Someone provisioned with no matching group starts as Viewer.

Group name to role mapping
RoleGroup name contains any of
Administratoradmin · administrator · owner · superuser
Contributorcontributor · editor · member · analyst
Viewerviewer · reader · readonly · guest · auditor

API changelog

2026-09-12

Breaking

  • One response envelope everywhere. Lists return { object: "list", api_version, data, has_more, next_cursor }; single objects return { object: "<type>", api_version, data }. The old per-resource shapes ({ risks, count }, { scorecard }, { roadmap, milestones }, …) are gone — read `data`.
  • One error envelope everywhere: { error: { type, code, message, request_id } }. Branch on `code`, not on `message`.
  • Scopes are enforced on every route. /scores and /signals previously accepted any valid key; they now require read:scores and read:signals (read:all still satisfies everything). Re-mint narrow keys with the scopes they need.
  • Child collections moved out of their parent object and became paginated resources: /roadmap/milestones, /compliance/obligations, /vendor/inventory, /talent/gaps. The parent objects no longer embed those arrays (they were unbounded and could be truncated).
  • The OpenAPI document moved from GET /api/v1 to GET /api/v1/openapi.json. GET /api/v1 is now the resource index.
  • Risk `status` filter values are the real column values (Open, InProgress, Mitigated, Accepted, Closed) and `rag` is RED|AMBER|GREEN — the previous spec documented lowercase values the database never held.

Additive

  • Cursor pagination on every collection: `limit` (1–100, default 25) and an opaque `cursor`. /scores is no longer capped at 24 rows and /signals no longer at 200.
  • X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset and X-RateLimit-Limit-Day on every response; Retry-After on every 429.
  • X-Request-Id on every response, and the same id inside every error body.
  • Vouli-Version request header to pin the dated version; Vouli-Version and Vouli-Environment on every response.
  • Sandbox keys: a vk_test_ key returns a fixed set of clearly-labelled synthetic rows and never reads tenant data.
  • New key namespace vk_ for newly minted keys. Keys beginning bk_ remain valid indefinitely.
  • Idempotency-Key support for future mutating endpoints, with a 24h replay window.
  • Fixed: /scorecard selected a column that does not exist, so it returned null for every organisation.
  • Both SDKs are now generated from this document and checked for drift in CI.
  • Added 2026-10-03: a read-only MCP server at POST /api/v1/mcp, so an AI assistant can query the same resources with the same key, scopes, plan gate, rate limit and sandbox. Its tools are generated from these resources.
  • Added 2026-10-03: writes. POST /risks, PATCH /risks/{id}, POST /incidents, PATCH /incidents/{id}, POST /kpis/checkins (up to 500 data points, all or nothing), POST /vendor/inventory and PATCH /vendor/inventory/{id}, each behind its own write: scope (read:all never grants one), the module's plan gate, a required Idempotency-Key on POST, strict bodies (unknown fields and org_id/owner-id fields refused) and the same service rules as the app. Sandbox keys get a simulated answer and nothing is written.
  • Added 2026-10-03: webhook subscriptions machine-to-machine — GET/POST /webhooks/subscriptions, PATCH/DELETE /webhooks/subscriptions/{id} and POST /webhooks/subscriptions/{id}/test (a signed ping) — behind manage:webhooks.
  • Added 2026-10-03: GET /audit, the audit-log export (cursor, since, until, json or ndjson), behind read:audit — which read:all does not grant — on the Enterprise plan only.
  • Added 2026-10-03: GET /incidents (read:incidents) and GET /kpis (read:kpis) read resources, and an `updated_since` filter on every list resource for incremental sync.
  • Added 2026-10-21 (wave 4a): GET /metrics and GET /metrics/values (read:metrics) — governed metric definitions and point-in-time values (as_of, known_at, start/end, updated_since).
  • Added 2026-10-21 (wave 4a): POST /events (write:events, or an HMAC-signed request from a push source) — the inbound Event API.
  • Added 2026-10-21 (wave 4a): POST /documents, POST /documents/{id}/process, GET /documents (read:documents), GET and DELETE /documents/{id} — the Documents API (write:documents).
  • Added 2026-10-21 (wave 4a): GET /board/votes, /board/documents and /board/packs (read:board), GET /decisions (read:decisions) and GET /insights (read:insights) — metadata only.
  • Added 2026-10-21 (wave 4a): seven restricted webhook events (board.*, decision.recorded, insight.generated, kpi.threshold_breached), delivered only to a subscription that lists them by name and whose key holds the scope in x-requires-scope. A catch-all subscription (no event types) still receives every core event and never a restricted one.
  • Scopes added in wave 4a (read:metrics, read:documents, read:board, read:decisions, read:insights) are granted by name: read:all does not cover them, so no existing key is widened.

Reference