Your people use AI services Govern does not sit in front of. Vendor AI audit connectors bring those services' own audit and usage logs into Govern, next to Govern's own ledger, so you can search, read and export them in one place. Pull connectors poll a vendor API; push receivers take events a tool sends.
Vendor events show what the vendor reported. They do not give Govern any control over the vendor's service.
How it runs
- Polling runs in the compliance plane (
keystone-comply), which the Kubernetes chart deploys as its own workload. It reads the enabled connectors from the server every 30 seconds (NI_COMPLY_CONFIG_REFRESH_INTERVAL) and runs one polling loop per connector and organisation. - Credentials are written to the organisation's Keystone Vault, mount
keystone-comply, one secret per connector, and never returned. Responses carry onlycredential.configured,credential.versionandcredential.updated_at. - Compliance keys are never used for inference: the model gateway reads provider keys from another vault path.
- Every vendor call goes through the outbound guard: loopback, private, link-local, carrier-grade NAT and metadata addresses are refused unless the operator lists the network in
NI_AUTH_OUTBOUND_ALLOW_PRIVATE_CIDRS. No proxy is used, and redirects are followed only within the same scheme, host and port. - A failed vendor call is reported by class, never by the vendor's text:
unauthorized,forbidden,not_found,rate_limited,vendor_rejected,vendor_unavailable,destination_blocked,destination_not_allowed,redirect_refused,timeout,unreachable,tls_error,invalid_response,credentials_missing,credentials_unavailable,poll_failed.
Set up a connector
The console page is Manage > Integrations > Vendor AI audit connectors. Through the API:
Store the credential (
vendoraudit.connectors:manage, Owner and Admin):PUT /api/v1/vendor-audit/connectors/anthropic/credentials {"credentials": {"api_key": "<Compliance API key>"}}Test it without storing anything new:
POST /api/v1/vendor-audit/connectors/anthropic/test. The answer is{ok, code, http_status, message, tested_at, latency_ms, stored}; a failed vendor call is200withok: falseand a class.Turn it on with a poll interval (60 to 86400 seconds) and settings:
PATCH /api/v1/vendor-audit/connectors/anthropic {"enabled": true, "poll_interval_seconds": 900, "settings": {"ingest_chats": "false"}}Enabling without a stored credential is
409 credentials_required.Poll now if you do not want to wait:
POST .../pollanswers202; the compliance plane polls at its next refresh.
Removing the credential (DELETE .../credentials) turns the connector off and deletes every vault version.
Settings are strings. A setting can turn off what the deployment collects; it can never turn on what an operator turned off with a NI_COMPLY_* variable.
Pull connectors
| Connector | Vendor API | Credential fields | Settings |
|---|---|---|---|
anthropic | Anthropic Compliance API (Claude Enterprise) | api_key | ingest_sessions, ingest_chats |
openai | OpenAI Compliance API (ChatGPT Enterprise) | admin_api_key, workspace_id, organization_id | ingest_usage |
azure-openai | Azure Monitor metrics, activity log, Log Analytics | tenant_id, client_id, client_secret, subscription_id, resource_group, account_name, workspace_id | ingest_metrics, ingest_activity_log |
aws-bedrock | CloudWatch Logs (Bedrock invocation logs) | access_key_id, secret_access_key, session_token, region | log_group_name |
gcp | Cloud Logging (Vertex AI, Gemini Code Assist) | project_id, client_email, private_key | |
copilot | GitHub Copilot seats, usage and metrics | api_key, github_org | |
tabnine | Tabnine audit logs and usage | token, server_url | ingest_usage, ingest_agent_usage |
gemini-workspace | Google Admin SDK Reports, Gemini in Workspace apps | client_email, private_key, admin_email, customer_id | |
m365-copilot | Office 365 Management Activity API, Audit.General | tenant_id, client_id, client_secret | |
aws-q-developer | CloudTrail LookupEvents | access_key_id, secret_access_key, session_token, region | event_sources (default q.amazonaws.com) |
openai-platform | OpenAI Admin API: audit logs, hourly usage, daily costs | admin_api_key | ingest_usage, ingest_costs |
anthropic-console | Anthropic Admin API: usage, costs, Claude Code usage, compliance activities | admin_api_key | ingest_usage, ingest_costs, ingest_claude_code, ingest_activities (off by default) |
xai | xAI Management API audit events | management_key, team_id | |
mistral | Mistral Admin API audit logs (Preview, Enterprise) | admin_api_key | |
openrouter | OpenRouter activity, one settled day per request, last 30 days | management_key | |
cursor | Cursor Admin API audit logs (Enterprise) | api_key | |
gitlab-duo | GitLab group audit events; Duo usage through GraphQL | access_token, group_path, server_url | ai_events_only (on), ingest_duo_usage (off) |
atlassian | Atlassian organisation events stream (Atlassian Guard; Rovo included) | api_key, org_id | |
slack | Slack Audit Logs API (Enterprise Grid) | token | actions |
box-ai | Box admin event stream | client_id, client_secret, enterprise_id | event_types (default Box AI events) |
Vendor notes
- Gemini for Workspace. Reports can arrive hours late, so each poll reads six-hour windows starting two hours before the last finished window; events are deduplicated by id. The service account needs domain-wide delegation of
admin.reports.audit.readonly, acting as an administrator. - Microsoft 365 Copilot. The connector starts the tenant's
Audit.Generalsubscription when none exists, keepsCopilotInteraction,ConnectedAIAppInteractionandAIAppInteractionrecords, and reads at most seven days back. The application needsActivityFeed.Read. - Amazon Q Developer. Event sources are a setting (up to five
*.amazonaws.comhosts), because some accounts showamazonq.amazonaws.comorcodewhisperer.amazonaws.com. CloudTrail holds 90 days of one account and region, and prompt text is not part of it. - Usage and cost reports are read once they settle: an hourly bucket an hour after it ends, a daily bucket a day after.
- Records without an identity or a time fail the poll as
invalid_responseinstead of being dropped, so a vendor format change shows as a failing connector, not lost events. - Self-hosted Tabnine and GitLab.
server_urlmust be https, without credentials, and onNI_COMPLY_TABNINE_SERVER_HOSTSorNI_COMPLY_GITLAB_SERVER_HOSTSwhen the operator sets them. A server on a private network also needs its network inNI_AUTH_OUTBOUND_ALLOW_PRIVATE_CIDRS. - Not covered: Cohere, Groq, Together AI, Fireworks, DeepSeek and the Perplexity API publish no audit or usage feed readable with a key. Windsurf, Devin, JetBrains AI, Sourcegraph Cody and Zoom AI Companion have no audit API whose format could be confirmed. Any service that can forward JSON can use the audit webhook.
Known defect: duplicate suppression across organisations
The ten connectors anthropic, openai, azure-openai, aws-bedrock, gcp, copilot, tabnine, gemini-workspace, m365-copilot and aws-q-developer use event ids that are not scoped to the organisation, and events are deduplicated on that id. When two organisations on one installation connect the same vendor tenant, or produce the same synthetic id (for example Tabnine's per-day usage), one organisation's event can silently suppress the other's. On a single-organisation installation this does not arise. The feed connectors (openai-platform to box-ai) and the push receivers derive ids from the organisation and are not affected.
Health
Each connector reports a state and a summary:
| State | Meaning |
|---|---|
not_set_up | Off, no credential |
off | Credential stored, turned off |
pending | Enabled, never polled |
healthy | The last poll succeeded |
failing | The last poll failed (for example "Failing (3 polls)"), with last_error_code, last_error_http_status and last_error_message |
stale | No success for three intervals and ten minutes |
The compliance plane's own health is watched by selfguard (GET /metrics/selfguard with NI_COMPLY_SERVICE_TOKEN).
Read the events
The console page is Audit > Vendor AI activity.
| Route | Permission | What |
|---|---|---|
GET /api/v1/vendor-audit/events | audit:read | Events, newest first, without payloads. Filters vendor, user (email or vendor user id), action, source_type, from, to; paging limit (1 to 200) and page_token |
GET /api/v1/vendor-audit/events/{eventID} | audit.vendor_content:view | One event with the vendor's raw_payload and its SHA-256 content_hash |
POST /api/v1/vendor-audit/events/export | audit:create and audit.vendor_content:view | A signed export; see Exports |
Payloads can hold chat content and Claude Code transcripts, so reading them needs audit.vendor_content:view, which the read-only roles do not hold (Auditor, Owner and Admin do).
Permissions
| Permission | Held by | Routes |
|---|---|---|
vendoraudit.connectors:read | Read-only roles, Owner, Admin | Connector list and detail, token list |
vendoraudit.connectors:manage | Owner and Admin | Configure, credentials, test, poll, tokens |
audit:read | Read-only roles, Auditor, Owner, Admin | Event list, export list |
audit.vendor_content:view | Auditor, Owner, Admin | Event detail with payload, export request and download |
Every change is recorded in the audit log (vendor_audit.*) in the transaction that makes it; credential changes name the fields, never the values. A connector, event or export of another organisation answers 404.