Shadow AI Discovery API
The Shadow AI Discovery API provides endpoints for discovering unauthorized AI service usage across your organization. Use these endpoints to query discovered services, manage the service catalog, ingest network and identity signals, view user activity, and configure alert rules.
For the admin guide covering data sources, the discovery dashboard, and deployment patterns, see Shadow AI Discovery Admin Guide.
Authentication
Section titled “Authentication”All endpoints require authentication via Bearer token with admin role:
Authorization: Bearer <admin-api-key>Base URL: https://gateway.arbitex.ai
Services
Section titled “Services”List discovered services
Section titled “List discovered services”| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/api/shadow-ai/services/ |
Bearer (admin) | List discovered AI services with filtering and pagination |
Query parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
status |
string | No | Filter by classification: "sanctioned", "unsanctioned", "unknown" |
category |
string | No | Filter by service category (e.g., "llm", "image_generation", "code_assistant") |
page |
integer | No | Page number (default: 1) |
per_page |
integer | No | Results per page (default: 20, max: 100) |
sort_by |
string | No | Sort field: "last_seen" (default), "first_seen", "user_count", "name" |
date_from |
string | No | ISO 8601 date; only return services seen on or after this date |
date_to |
string | No | ISO 8601 date; only return services seen on or before this date |
Example:
curl -s "https://gateway.arbitex.ai/api/shadow-ai/services/?status=unsanctioned&sort_by=user_count&per_page=10" \ -H "Authorization: Bearer $ADMIN_TOKEN"Response (200 OK):
{ "data": [ { "id": "svc_01j4ab5cd6e7f8gh9i0j1k2l", "name": "ChatGPT", "domains": ["chat.openai.com", "chatgpt.com"], "category": "llm", "status": "unsanctioned", "user_count": 47, "first_seen": "2026-02-14T09:12:33Z", "last_seen": "2026-04-01T11:45:02Z", "data_volume_bytes": 12483200, "source_channels": ["proxy_log", "dns_log", "idp_events"] } ], "pagination": { "page": 1, "per_page": 10, "total": 8, "total_pages": 1 }}Response fields per service:
| Field | Type | Description |
|---|---|---|
id |
string | Unique service identifier |
name |
string | Human-readable service name |
domains |
string[] | Known domains and API endpoints for this service |
category |
string | Service category ("llm", "image_generation", "code_assistant", "transcription", "other") |
status |
string | Classification: "sanctioned", "unsanctioned", "unknown" |
user_count |
integer | Distinct users who accessed this service |
first_seen |
string | ISO 8601 timestamp of first detected access |
last_seen |
string | ISO 8601 timestamp of most recent detected access |
data_volume_bytes |
integer | Estimated data volume where proxy data is available; null if DNS-only |
source_channels |
string[] | Detection channels: "proxy_log", "dns_log", "idp_events" |
Get service detail
Section titled “Get service detail”| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/api/shadow-ai/services/:id |
Bearer (admin) | Get detailed information for a single discovered service |
Path parameters:
| Parameter | Type | Description |
|---|---|---|
id |
string | Service ID (e.g., svc_01j4ab5cd6e7f8gh9i0j1k2l) |
Example:
curl -s https://gateway.arbitex.ai/api/shadow-ai/services/svc_01j4ab5cd6e7f8gh9i0j1k2l \ -H "Authorization: Bearer $ADMIN_TOKEN"Response (200 OK):
{ "id": "svc_01j4ab5cd6e7f8gh9i0j1k2l", "name": "ChatGPT", "domains": ["chat.openai.com", "chatgpt.com"], "category": "llm", "status": "unsanctioned", "user_count": 47, "first_seen": "2026-02-14T09:12:33Z", "last_seen": "2026-04-01T11:45:02Z", "data_volume_bytes": 12483200, "source_channels": ["proxy_log", "dns_log", "idp_events"], "access_frequency": { "daily_avg_7d": 132, "daily_avg_30d": 98 }, "top_users": [ { "user_id": "user_01j2mn3op4q5r6st7u8v9w0x", "access_count": 214, "last_seen": "2026-04-01T11:45:02Z" } ], "classification_history": [ { "from": "unknown", "to": "unsanctioned", "changed_at": "2026-02-15T14:30:00Z", "note": "Not approved per security policy SP-2026-003" } ]}Get service users
Section titled “Get service users”| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/api/shadow-ai/services/:id/users |
Bearer (admin) | List users who accessed a specific service |
Path parameters:
| Parameter | Type | Description |
|---|---|---|
id |
string | Service ID |
Query parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
page |
integer | No | Page number (default: 1) |
per_page |
integer | No | Results per page (default: 20, max: 100) |
Example:
curl -s "https://gateway.arbitex.ai/api/shadow-ai/services/svc_01j4ab5cd6e7f8gh9i0j1k2l/users?per_page=5" \ -H "Authorization: Bearer $ADMIN_TOKEN"Response (200 OK):
{ "data": [ { "user_id": "user_01j2mn3op4q5r6st7u8v9w0x", "display_name": "Jane Smith", "access_count": 214, "first_seen": "2026-02-14T09:12:33Z", "last_seen": "2026-04-01T11:45:02Z", "source_channels": ["proxy_log", "idp_events"] } ], "pagination": { "page": 1, "per_page": 5, "total": 47, "total_pages": 10 }}Update service status
Section titled “Update service status”| Method | Path | Auth | Description |
|---|---|---|---|
PATCH |
/api/shadow-ai/services/:id |
Bearer (admin) | Update service classification |
Path parameters:
| Parameter | Type | Description |
|---|---|---|
id |
string | Service ID |
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
status |
string | Yes | New classification: "sanctioned", "unsanctioned", "unknown" |
note |
string | No | Reason for the classification change (recorded in the audit log) |
Example:
curl -s -X PATCH https://gateway.arbitex.ai/api/shadow-ai/services/svc_01j4ab5cd6e7f8gh9i0j1k2l \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "status": "sanctioned", "note": "Approved via security review SR-2026-041" }'Response (200 OK):
{ "id": "svc_01j4ab5cd6e7f8gh9i0j1k2l", "name": "ChatGPT", "status": "sanctioned", "previous_status": "unsanctioned", "changed_at": "2026-04-01T12:00:00Z", "note": "Approved via security review SR-2026-041"}Classification changes are recorded in the audit log. See the Audit Log Management Guide for export and verification procedures.
List users with shadow AI activity
Section titled “List users with shadow AI activity”| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/api/shadow-ai/users/ |
Bearer (admin) | List all users with detected shadow AI activity |
Query parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
page |
integer | No | Page number (default: 1) |
per_page |
integer | No | Results per page (default: 20, max: 100) |
sort_by |
string | No | Sort field: "last_seen" (default), "service_count", "unsanctioned_count" |
status |
string | No | Filter to users who accessed services with this classification |
Example:
curl -s "https://gateway.arbitex.ai/api/shadow-ai/users/?sort_by=unsanctioned_count&per_page=10" \ -H "Authorization: Bearer $ADMIN_TOKEN"Response (200 OK):
{ "data": [ { "user_id": "user_01j2mn3op4q5r6st7u8v9w0x", "display_name": "Jane Smith", "service_count": 5, "unsanctioned_count": 3, "first_seen": "2026-02-14T09:12:33Z", "last_seen": "2026-04-01T11:45:02Z", "services": [ { "service_id": "svc_01j4ab5cd6e7f8gh9i0j1k2l", "name": "ChatGPT", "status": "unsanctioned", "access_count": 214, "last_seen": "2026-04-01T11:45:02Z" } ] } ], "pagination": { "page": 1, "per_page": 10, "total": 132, "total_pages": 14 }}Response fields per user:
| Field | Type | Description |
|---|---|---|
user_id |
string | User identifier (from IdP or proxy authentication) |
email |
string | User email address; null if identity resolved from IP only |
display_name |
string | Display name from IdP; null if unavailable |
service_count |
integer | Total AI services this user has accessed |
unsanctioned_count |
integer | Number of unsanctioned services accessed |
first_seen |
string | First detected shadow AI access |
last_seen |
string | Most recent detected access |
services |
object[] | Summary of services accessed (service_id, name, status, access_count, last_seen) |
Overview
Section titled “Overview”Get summary statistics
Section titled “Get summary statistics”| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/api/shadow-ai/overview |
Bearer (admin) | Organization-wide shadow AI summary |
Example:
curl -s https://gateway.arbitex.ai/api/shadow-ai/overview \ -H "Authorization: Bearer $ADMIN_TOKEN"Response (200 OK):
{ "total_services": 34, "sanctioned": 8, "unsanctioned": 12, "unknown": 14, "total_users": 132, "users_unsanctioned_7d": 47, "new_services_7d": 3, "new_services_30d": 11, "data_sources_active": 3, "data_sources_total": 3}Response fields:
| Field | Type | Description |
|---|---|---|
total_services |
integer | Total AI services discovered |
sanctioned |
integer | Services classified as sanctioned |
unsanctioned |
integer | Services classified as unsanctioned |
unknown |
integer | Services awaiting classification review |
total_users |
integer | Distinct users with any shadow AI activity |
users_unsanctioned_7d |
integer | Users who accessed unsanctioned services in the last 7 days |
new_services_7d |
integer | New services discovered in the last 7 days |
new_services_30d |
integer | New services discovered in the last 30 days |
data_sources_active |
integer | Number of configured data sources currently receiving data |
data_sources_total |
integer | Total configured data sources |
Data Ingestion
Section titled “Data Ingestion”These endpoints accept network and identity signals for shadow AI analysis. Use them when integrating custom log pipelines or forwarding events from infrastructure that does not use the built-in source connectors.
Ingest proxy logs
Section titled “Ingest proxy logs”| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/api/shadow-ai/ingest/proxy-logs |
Bearer (admin) | Ingest proxy log entries for AI service detection |
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
entries |
object[] | Yes | Array of proxy log entries |
entries[].timestamp |
string | Yes | ISO 8601 timestamp of the request |
entries[].source_ip |
string | Yes | Source IP address |
entries[].destination_host |
string | Yes | Destination hostname (e.g., "api.openai.com") |
entries[].destination_port |
integer | No | Destination port (default: 443) |
entries[].user_identity |
string | No | Authenticated user identity from the proxy |
entries[].bytes_sent |
integer | No | Request payload size in bytes |
entries[].bytes_received |
integer | No | Response payload size in bytes |
entries[].http_method |
string | No | HTTP method (e.g., "POST") |
entries[].url_path |
string | No | URL path (e.g., "/v1/chat/completions") |
Example:
curl -s -X POST https://gateway.arbitex.ai/api/shadow-ai/ingest/proxy-logs \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "entries": [ { "timestamp": "2026-04-01T10:30:00Z", "source_ip": "10.0.1.42", "destination_host": "api.openai.com", "user_identity": "[email protected]", "bytes_sent": 1024, "bytes_received": 4096, "http_method": "POST", "url_path": "/v1/chat/completions" } ] }'Response (202 Accepted):
{ "accepted": 1, "rejected": 0, "errors": []}Ingest DNS logs
Section titled “Ingest DNS logs”| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/api/shadow-ai/ingest/dns-logs |
Bearer (admin) | Ingest DNS query logs for AI service domain detection |
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
entries |
object[] | Yes | Array of DNS query log entries |
entries[].timestamp |
string | Yes | ISO 8601 timestamp of the query |
entries[].source_ip |
string | Yes | IP address of the DNS client |
entries[].query_name |
string | Yes | Queried domain name (e.g., "api.anthropic.com") |
entries[].query_type |
string | No | DNS record type (default: "A") |
entries[].resolver |
string | No | DNS resolver identifier |
Example:
curl -s -X POST https://gateway.arbitex.ai/api/shadow-ai/ingest/dns-logs \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "entries": [ { "timestamp": "2026-04-01T10:29:58Z", "source_ip": "10.0.1.42", "query_name": "api.anthropic.com", "query_type": "A", "resolver": "infoblox-primary" } ] }'Response (202 Accepted):
{ "accepted": 1, "rejected": 0, "errors": []}Ingest IdP events
Section titled “Ingest IdP events”| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/api/shadow-ai/ingest/idp-events |
Bearer (admin) | Ingest IdP audit events for AI service SSO detection |
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
entries |
object[] | Yes | Array of IdP audit events |
entries[].timestamp |
string | Yes | ISO 8601 timestamp of the event |
entries[].user_id |
string | Yes | User identifier from the IdP |
entries[].user_email |
string | No | User email address |
entries[].event_type |
string | Yes | IdP event type (e.g., "sso_login", "oauth_grant", "saml_assertion") |
entries[].target_app |
string | Yes | Target application name as registered in the IdP |
entries[].target_domain |
string | No | Target application domain |
entries[].idp_vendor |
string | No | IdP vendor: "okta", "entra_id" |
Example:
curl -s -X POST https://gateway.arbitex.ai/api/shadow-ai/ingest/idp-events \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "entries": [ { "timestamp": "2026-04-01T10:28:00Z", "user_id": "00u1a2b3c4d5e6f7g8h9", "user_email": "[email protected]", "event_type": "sso_login", "target_app": "ChatGPT Enterprise", "target_domain": "chat.openai.com", "idp_vendor": "okta" } ] }'Response (202 Accepted):
{ "accepted": 1, "rejected": 0, "errors": []}Alerts
Section titled “Alerts”Get alert configuration
Section titled “Get alert configuration”| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/api/shadow-ai/alerts/config |
Bearer (admin) | Retrieve current alert configuration |
Example:
curl -s https://gateway.arbitex.ai/api/shadow-ai/alerts/config \ -H "Authorization: Bearer $ADMIN_TOKEN"Response (200 OK):
{ "triggers": { "new_unsanctioned_service": true, "new_unknown_service": true, "unsanctioned_user_threshold": { "enabled": true, "threshold": 5 } }, "webhook": { "enabled": true, "url": "https://hooks.example.com/shadow-ai-alerts", "secret_configured": true }}Update alert configuration
Section titled “Update alert configuration”| Method | Path | Auth | Description |
|---|---|---|---|
PUT |
/api/shadow-ai/alerts/config |
Bearer (admin) | Update alert triggers and webhook configuration |
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
triggers |
object | No | Alert trigger configuration |
triggers.new_unsanctioned_service |
boolean | No | Alert on first sighting of an unsanctioned service |
triggers.new_unknown_service |
boolean | No | Alert on first sighting of an uncataloged service |
triggers.unsanctioned_user_threshold |
object | No | Alert when user count for an unsanctioned service exceeds threshold |
triggers.unsanctioned_user_threshold.enabled |
boolean | Yes (if parent provided) | Enable or disable the threshold trigger |
triggers.unsanctioned_user_threshold.threshold |
integer | Yes (if parent provided) | User count threshold (minimum: 1) |
webhook |
object | No | Webhook delivery configuration |
webhook.enabled |
boolean | No | Enable or disable webhook delivery |
webhook.url |
string | No | Webhook URL (must be HTTPS) |
webhook.secret |
string | No | HMAC-SHA256 signing secret for webhook payloads |
Example:
curl -s -X PUT https://gateway.arbitex.ai/api/shadow-ai/alerts/config \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "triggers": { "new_unsanctioned_service": true, "new_unknown_service": true, "unsanctioned_user_threshold": { "enabled": true, "threshold": 10 } }, "webhook": { "enabled": true, "url": "https://hooks.example.com/shadow-ai-alerts", "secret": "whsec_a1b2c3d4e5f6..." } }'Response (200 OK):
{ "triggers": { "new_unsanctioned_service": true, "new_unknown_service": true, "unsanctioned_user_threshold": { "enabled": true, "threshold": 10 } }, "webhook": { "enabled": true, "url": "https://hooks.example.com/shadow-ai-alerts", "secret_configured": true }}Webhook payload:
Webhook deliveries include the following headers:
| Header | Description |
|---|---|
X-Arbitex-Signature |
HMAC-SHA256 signature of the request body using the configured secret |
Content-Type |
application/json |
Webhook payload body example:
{ "trigger": "new_unsanctioned_service", "service_id": "svc_01j4ab5cd6e7f8gh9i0j1k2l", "service_name": "ChatGPT", "classification": "unsanctioned", "user_count": 1, "timestamp": "2026-04-01T10:30:05Z"}Data Sources
Section titled “Data Sources”Get data source status
Section titled “Get data source status”| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/api/shadow-ai/sources |
Bearer (admin) | List configured data sources and their connection status |
Example:
curl -s https://gateway.arbitex.ai/api/shadow-ai/sources \ -H "Authorization: Bearer $ADMIN_TOKEN"Response (200 OK):
{ "sources": [ { "id": "src_01j5cd6ef7g8h9ij0k1l2m3n", "type": "proxy_log", "vendor": "zscaler", "label": "Zscaler NSS Feed", "delivery": "syslog", "status": "connected", "last_event_at": "2026-04-01T12:29:55Z", "events_24h": 14832 }, { "id": "src_01j5ef7gh8i9j0kl1m2n3o4p", "type": "dns_log", "vendor": "infoblox", "label": "Infoblox WAPI", "delivery": "api_pull", "status": "connected", "last_event_at": "2026-04-01T12:25:00Z", "events_24h": 52410 }, { "id": "src_01j5gh8ij9k0l1mn2o3p4q5r", "type": "idp_events", "vendor": "okta", "label": "Okta System Log", "delivery": "api_pull", "status": "connected", "last_event_at": "2026-04-01T12:29:00Z", "events_24h": 3241 } ]}Response fields per source:
| Field | Type | Description |
|---|---|---|
id |
string | Unique data source identifier |
type |
string | Source type: "proxy_log", "dns_log", "idp_events" |
vendor |
string | Vendor name (e.g., "zscaler", "infoblox", "okta") |
label |
string | Admin-provided label for this source |
delivery |
string | Delivery method: "syslog", "api_pull" |
status |
string | Connection status: "connected", "disconnected", "error" |
last_event_at |
string | ISO 8601 timestamp of the most recent event received |
events_24h |
integer | Number of events received in the last 24 hours |
Pagination Format
Section titled “Pagination Format”All paginated endpoints return results in the following format:
{ "data": [...], "pagination": { "page": 1, "per_page": 20, "total": 132, "total_pages": 7 }}| Field | Type | Description |
|---|---|---|
page |
integer | Current page number |
per_page |
integer | Results per page |
total |
integer | Total matching records |
total_pages |
integer | Total number of pages |
Error Codes
Section titled “Error Codes”| Status | Code | Description |
|---|---|---|
400 |
invalid_request |
Malformed request body or invalid query parameter value |
400 |
invalid_status |
Status must be "sanctioned", "unsanctioned", or "unknown" |
400 |
batch_too_large |
Ingestion batch exceeds 1000 entries |
403 |
forbidden |
Authenticated user does not have admin role |
404 |
service_not_found |
Service ID does not exist in the org |
404 |
org_not_found |
Organization not found for the authenticated token |
422 |
validation_error |
Required fields missing or field type mismatch in ingestion entries |
Example error response:
{ "error": { "code": "invalid_status", "message": "status must be 'sanctioned', 'unsanctioned', or 'unknown'", "field": "status" }}