API Reference¶
The Verity REST API provides programmatic access to principals, assets, grants, decay scores, access reviews, audit trails, and compliance reports.
Base URL¶
All API endpoints are served by the API Gateway on port 8000 and prefixed with /v1/.
In production, replace localhost:8000 with your load-balancer or ingress hostname.
Authentication¶
Every request must include a valid Bearer token in the Authorization header. Tokens are Azure AD JWTs issued for the Verity application registration.
Dev-mode bypass
When the API Gateway is started with AUTH_DISABLED=true (local development only), the Authorization header is not required. Never use this in production.
Pagination¶
All list endpoints return cursor-based paginated responses using the PaginatedResponse envelope:
| Parameter | Type | Default | Description |
|---|---|---|---|
cursor |
string | — | Opaque cursor returned by a previous response. |
limit |
int | 50 | Number of items per page (max 100). |
When next_cursor is null, there are no more pages.
Example — fetching the second page:
curl -s "http://localhost:8000/v1/principals?cursor=eyJpZCI6IjAxOTNh...&limit=25" \
-H "Authorization: Bearer $TOKEN"
Error Format¶
All error responses use a consistent JSON body:
Validation errors include additional context:
{
"detail": [
{
"loc": ["query", "limit"],
"msg": "ensure this value is less than or equal to 100",
"type": "value_error.number.not_le"
}
]
}
Common Status Codes¶
| Code | Meaning |
|---|---|
| 200 | OK — request succeeded. |
| 201 | Created — resource was created successfully. |
| 400 | Bad Request — invalid parameters or request body. |
| 401 | Unauthorized — missing or invalid Bearer token. |
| 403 | Forbidden — authenticated but lacking the required role/scope. |
| 404 | Not Found — the requested resource does not exist. |
| 409 | Conflict — duplicate resource or idempotency collision. |
| 500 | Internal Server Error — unexpected failure on the server side. |
Idempotency¶
All POST endpoints support the Idempotency-Key header to guarantee exactly-once semantics. Pass a unique UUID for each logical request:
POST /v1/reviews/019f1234-5678-9abc-def0-123456789abc/decide HTTP/1.1
Idempotency-Key: 7c9e6679-7425-40de-944b-e07fc1f90ae7
Content-Type: application/json
Authorization: Bearer <access_token>
If the same Idempotency-Key is resubmitted, the API returns the original response without re-executing the operation.
Rate Limiting¶
The API enforces per-tenant rate limits to ensure fair usage across all consumers.
Default Limits¶
| Endpoint Category | Requests / minute | Burst |
|---|---|---|
Read endpoints (GET) |
300 | 50 |
Write endpoints (POST, PUT, PATCH) |
100 | 20 |
Report generation (/v1/reports/*) |
10 | 2 |
Bulk operations (/v1/*/bulk) |
20 | 5 |
Rate Limit Headers¶
Every response includes rate limit headers:
| Header | Description |
|---|---|
X-RateLimit-Limit |
Maximum requests allowed in the current window. |
X-RateLimit-Remaining |
Requests remaining in the current window. |
X-RateLimit-Reset |
Unix epoch timestamp when the window resets. |
Rate Limit Exceeded¶
When the limit is exceeded, the API returns 429 Too Many Requests:
The response also includes a Retry-After header with the number of seconds to wait.
Tip
Use exponential backoff in your client when you receive a 429. Most HTTP client libraries support this out of the box.
Webhook Events¶
Verity can deliver webhook notifications when key events occur. Configure webhook endpoints in the Verity dashboard or via the /v1/webhooks endpoint.
Event Types¶
| Event | Trigger |
|---|---|
score.threshold_crossed |
A principal's decay score crosses a configured threshold (e.g., drops below 50). |
review.created |
A new access review campaign is generated. |
review.decided |
A reviewer approves or revokes access in a review. |
remediation.executed |
An automated remediation action completes (e.g., group removal). |
Payload Format¶
All webhook payloads follow the same envelope:
{
"id": "evt_019f1234-5678-9abc-def0-123456789abc",
"type": "score.threshold_crossed",
"created_at": "2025-07-15T10:30:00Z",
"data": { }
}
Example: score.threshold_crossed¶
{
"id": "evt_019f1234-5678-9abc-def0-123456789abc",
"type": "score.threshold_crossed",
"created_at": "2025-07-15T10:30:00Z",
"data": {
"principal_id": "019f0001-aaaa-7000-8000-000000000001",
"principal_name": "jane.doe@contoso.com",
"score": 42.7,
"previous_score": 51.3,
"threshold": 50,
"direction": "below"
}
}
Example: review.decided¶
{
"id": "evt_019f5678-bbbb-7000-8000-000000000002",
"type": "review.decided",
"created_at": "2025-07-15T14:22:00Z",
"data": {
"review_id": "019f1234-5678-9abc-def0-123456789abc",
"grant_id": "019f0002-cccc-7000-8000-000000000003",
"decision": "revoke",
"reviewer": "security-admin@contoso.com",
"reason": "No usage in 90 days, score below threshold"
}
}
Example: remediation.executed¶
{
"id": "evt_019f9abc-dddd-7000-8000-000000000004",
"type": "remediation.executed",
"created_at": "2025-07-15T14:25:00Z",
"data": {
"remediation_id": "019f0003-eeee-7000-8000-000000000005",
"action": "remove_group_membership",
"principal_id": "019f0001-aaaa-7000-8000-000000000001",
"target": "sg-finance-rw",
"status": "completed"
}
}
Webhook Security¶
Webhook payloads are signed with HMAC-SHA256. Verify the signature using the X-Verity-Signature header:
import hmac, hashlib
def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)
Common Workflows¶
Score and Review a Grant¶
This workflow demonstrates the full lifecycle: look up a principal, check their score, find their grants, and create a review decision.
Step 1 — Look up the principal:
curl -s "http://localhost:8000/v1/principals?email=jane.doe@contoso.com" \
-H "Authorization: Bearer $TOKEN"
{
"items": [
{
"id": "019f0001-aaaa-7000-8000-000000000001",
"display_name": "Jane Doe",
"email": "jane.doe@contoso.com",
"type": "user",
"source": "azure_ad",
"created_at": "2025-01-10T08:00:00Z"
}
],
"next_cursor": null
}
Step 2 — Get the principal's current decay score:
curl -s "http://localhost:8000/v1/scores/019f0001-aaaa-7000-8000-000000000001" \
-H "Authorization: Bearer $TOKEN"
{
"principal_id": "019f0001-aaaa-7000-8000-000000000001",
"overall_score": 42.7,
"factors": {
"last_login_days": 45,
"mfa_enabled": true,
"unused_grants": 3,
"anomaly_signals": 0
},
"calculated_at": "2025-07-15T06:00:00Z"
}
Step 3 — List the principal's grants:
curl -s "http://localhost:8000/v1/grants?principal_id=019f0001-aaaa-7000-8000-000000000001" \
-H "Authorization: Bearer $TOKEN"
{
"items": [
{
"id": "019f0002-cccc-7000-8000-000000000003",
"principal_id": "019f0001-aaaa-7000-8000-000000000001",
"asset_id": "019f0004-ffff-7000-8000-000000000006",
"asset_name": "sg-finance-rw",
"permission": "ReadWrite",
"last_used_at": "2025-04-01T12:00:00Z",
"status": "active"
}
],
"next_cursor": null
}
Step 4 — Submit a review decision to revoke the grant:
curl -s -X POST "http://localhost:8000/v1/reviews/019f1234-5678-9abc-def0-123456789abc/decide" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7c9e6679-7425-40de-944b-e07fc1f90ae7" \
-d '{
"grant_id": "019f0002-cccc-7000-8000-000000000003",
"decision": "revoke",
"reason": "No usage in 90 days, score below threshold"
}'
{
"id": "019f5678-bbbb-7000-8000-000000000002",
"review_id": "019f1234-5678-9abc-def0-123456789abc",
"grant_id": "019f0002-cccc-7000-8000-000000000003",
"decision": "revoke",
"decided_by": "security-admin@contoso.com",
"decided_at": "2025-07-15T14:22:00Z"
}
Investigate a High-Risk Principal¶
Use this workflow when a principal's score drops below your organization's threshold.
Step 1 — List principals with scores below threshold:
curl -s "http://localhost:8000/v1/scores?below=50&sort=score_asc&limit=10" \
-H "Authorization: Bearer $TOKEN"
{
"items": [
{
"principal_id": "019f0001-aaaa-7000-8000-000000000001",
"display_name": "Jane Doe",
"overall_score": 42.7,
"calculated_at": "2025-07-15T06:00:00Z"
}
],
"next_cursor": null
}
Step 2 — Retrieve the audit trail for the principal:
curl -s "http://localhost:8000/v1/audit?principal_id=019f0001-aaaa-7000-8000-000000000001&limit=20" \
-H "Authorization: Bearer $TOKEN"
{
"items": [
{
"id": "019faaaa-1111-7000-8000-000000000010",
"principal_id": "019f0001-aaaa-7000-8000-000000000001",
"event_type": "grant.created",
"description": "Added to sg-finance-rw",
"actor": "admin@contoso.com",
"timestamp": "2025-01-15T09:30:00Z"
},
{
"id": "019faaaa-2222-7000-8000-000000000011",
"principal_id": "019f0001-aaaa-7000-8000-000000000001",
"event_type": "score.threshold_crossed",
"description": "Score dropped below 50 (42.7)",
"actor": "system",
"timestamp": "2025-07-15T06:00:00Z"
}
],
"next_cursor": null
}
Step 3 — Fetch metrics for the principal's access patterns:
curl -s "http://localhost:8000/v1/metrics/principal/019f0001-aaaa-7000-8000-000000000001?period=90d" \
-H "Authorization: Bearer $TOKEN"
{
"principal_id": "019f0001-aaaa-7000-8000-000000000001",
"period": "90d",
"login_count": 3,
"unique_assets_accessed": 1,
"grants_total": 5,
"grants_unused": 3,
"anomaly_events": 0
}
Export a Compliance Report¶
Generate and download a compliance report for auditors.
Step 1 — Trigger report generation:
curl -s -X POST "http://localhost:8000/v1/reports" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-d '{
"type": "compliance_summary",
"period_start": "2025-01-01T00:00:00Z",
"period_end": "2025-06-30T23:59:59Z",
"format": "pdf"
}'
{
"id": "019f7777-aaaa-7000-8000-000000000020",
"type": "compliance_summary",
"status": "generating",
"created_at": "2025-07-15T15:00:00Z",
"estimated_completion": "2025-07-15T15:02:00Z"
}
Step 2 — Poll for completion and download:
curl -s "http://localhost:8000/v1/reports/019f7777-aaaa-7000-8000-000000000020" \
-H "Authorization: Bearer $TOKEN"
{
"id": "019f7777-aaaa-7000-8000-000000000020",
"type": "compliance_summary",
"status": "completed",
"download_url": "/v1/reports/019f7777-aaaa-7000-8000-000000000020/download",
"created_at": "2025-07-15T15:00:00Z",
"completed_at": "2025-07-15T15:01:30Z"
}
curl -s -o compliance-report.pdf \
"http://localhost:8000/v1/reports/019f7777-aaaa-7000-8000-000000000020/download" \
-H "Authorization: Bearer $TOKEN"
Bulk Operations¶
For large-scale operations, use the bulk endpoints to process multiple items in a single request.
Bulk score refresh:
curl -s -X POST "http://localhost:8000/v1/scores/bulk" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"principal_ids": [
"019f0001-aaaa-7000-8000-000000000001",
"019f0001-aaaa-7000-8000-000000000002",
"019f0001-aaaa-7000-8000-000000000003"
]
}'
{
"job_id": "019f8888-aaaa-7000-8000-000000000030",
"status": "accepted",
"total_items": 3,
"created_at": "2025-07-15T16:00:00Z"
}
Bulk review decisions:
curl -s -X POST "http://localhost:8000/v1/reviews/019f1234-5678-9abc-def0-123456789abc/decide/bulk" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: b2c3d4e5-f6a7-8901-bcde-f12345678901" \
-d '{
"decisions": [
{"grant_id": "019f0002-cccc-7000-8000-000000000003", "decision": "revoke", "reason": "Unused for 90+ days"},
{"grant_id": "019f0002-cccc-7000-8000-000000000004", "decision": "approve", "reason": "Active daily usage"},
{"grant_id": "019f0002-cccc-7000-8000-000000000005", "decision": "revoke", "reason": "Departed employee"}
]
}'
{
"review_id": "019f1234-5678-9abc-def0-123456789abc",
"processed": 3,
"results": [
{"grant_id": "019f0002-cccc-7000-8000-000000000003", "status": "accepted"},
{"grant_id": "019f0002-cccc-7000-8000-000000000004", "status": "accepted"},
{"grant_id": "019f0002-cccc-7000-8000-000000000005", "status": "accepted"}
]
}
Bulk limits
Bulk endpoints accept up to 500 items per request. For larger batches, split into multiple requests and use the Idempotency-Key header to ensure safe retries.
Interactive API Documentation¶
The API Gateway automatically generates interactive documentation from the OpenAPI specification.
| Tool | URL | Description |
|---|---|---|
| Swagger UI | http://localhost:8000/docs | Interactive explorer — try endpoints directly from the browser. |
| ReDoc | http://localhost:8000/redoc | Clean, readable API reference with search. |
| OpenAPI spec | http://localhost:8000/openapi.json | Raw OpenAPI 3.1 JSON for code generation and tooling. |
Authorize in Swagger UI
Click the Authorize 🔒 button in Swagger UI and paste your Bearer token to test authenticated endpoints interactively.
You can use the OpenAPI spec to generate client SDKs:
# Generate a Python client
openapi-generator-cli generate \
-i http://localhost:8000/openapi.json \
-g python \
-o ./verity-client-python
# Generate a TypeScript client
openapi-generator-cli generate \
-i http://localhost:8000/openapi.json \
-g typescript-fetch \
-o ./verity-client-ts
Conventions¶
- All IDs are UUID v7 (time-sortable).
- Timestamps are ISO 8601 with UTC timezone (
2025-07-15T10:30:00Z). - Query parameters use snake_case.
- Request and response bodies use snake_case JSON keys.