Skip to content

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/.

http://localhost:8000/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.

Authorization: Bearer <access_token>

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:

{
  "items": [ ... ],
  "next_cursor": "eyJpZCI6IjAxOTNhYjEyLTM0NTYtNzg5MC..."
}
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:

{
  "detail": "Principal not found"
}

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:

{
  "detail": "Rate limit exceeded. Retry after 32 seconds.",
  "retry_after": 32
}

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.