---
title: "Vendor AI audit connectors"
---

# Vendor AI audit connectors

Bring the audit and usage logs of third-party AI services into Neural Inverse Govern with pull connectors, configured by organisation administrators, with credentials in the organisation's vault.

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](https://neuralinverse.com/docs/evidence/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 only `credential.configured`, `credential.version` and `credential.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:

1. **Store the credential** (`vendoraudit.connectors:manage`, Owner and Admin):

   ```http
   PUT /api/v1/vendor-audit/connectors/anthropic/credentials
   {"credentials": {"api_key": "<Compliance API key>"}}
   ```

2. **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 is `200` with `ok: false` and a class.
3. **Turn it on** with a poll interval (60 to 86400 seconds) and settings:

   ```http
   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`.
4. **Poll now** if you do not want to wait: `POST .../poll` answers `202`; 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.General` subscription when none exists, keeps `CopilotInteraction`, `ConnectedAIAppInteraction` and `AIAppInteraction` records, and reads at most seven days back. The application needs `ActivityFeed.Read`.
- **Amazon Q Developer.** Event sources are a setting (up to five `*.amazonaws.com` hosts), because some accounts show `amazonq.amazonaws.com` or `codewhisperer.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_response` instead of being dropped, so a vendor format change shows as a failing connector, not lost events.
- **Self-hosted Tabnine and GitLab.** `server_url` must be https, without credentials, and on `NI_COMPLY_TABNINE_SERVER_HOSTS` or `NI_COMPLY_GITLAB_SERVER_HOSTS` when the operator sets them. A server on a private network also needs its network in `NI_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](https://neuralinverse.com/docs/evidence/push-receivers/).

### 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](https://neuralinverse.com/docs/evidence/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`.
