Download OpenAPI specification:Download
DriftWise multi-cloud infrastructure drift detection API. All endpoints live under /api/v2.
Paginated list of every audit event on the platform. Covers platform events (org.created, domain.added, user.deleted) and org-scoped events. Use GET /orgs/:id/audit-log for per-org filtering.
| limit | integer page size (default 50, max 200) |
| offset | integer page offset |
curl --request GET \ --url 'https://app.driftwise.ai/api/v2/admin/audit-log?limit=SOME_INTEGER_VALUE&offset=SOME_INTEGER_VALUE' \ --header 'Authorization: REPLACE_KEY_VALUE'
[- {
- "action": "string",
- "actor_email": "string",
- "created_at": "string",
- "detail": [
- 0
], - "id": "string",
- "org_id": "string"
}
]Walks the hash chain over platform-scoped audit events (domain.added, user.deleted, org.created, etc.). Platform-admin only. Use expected_min_seq to detect tail truncation via an out-of-band watermark.
| expected_min_seq | integer Operator-tracked watermark; broken if current head_seq is lower |
curl --request GET \ --url 'https://app.driftwise.ai/api/v2/admin/audit-log/verify?expected_min_seq=SOME_INTEGER_VALUE' \ --header 'Authorization: REPLACE_KEY_VALUE'
{- "checked": 0,
- "head_hash": "string",
- "head_seq": 0,
- "status": "string"
}Email domains whose users may authenticate. Users with domains not on this list are rejected at login with 403.
curl --request GET \ --url https://app.driftwise.ai/api/v2/admin/domains \ --header 'Authorization: REPLACE_KEY_VALUE'
[- {
- "created_at": "string",
- "created_by": "string",
- "domain": "string",
- "id": "string"
}
]Validation: must contain a dot, must not contain @, spaces, or a protocol prefix. Automatically lowercased.
Domain to allow
| domain required | string Domain is the bare email-domain string (e.g. "acme.com"). Must contain a dot, must not contain @, spaces, or a protocol prefix (http:// / https://). Automatically lowercased server-side. |
{- "domain": "acme.com"
}{- "created_at": "string",
- "created_by": "string",
- "domain": "string",
- "id": "string"
}Users with this domain will be rejected at next login. Existing sessions survive up to 30s (cache TTL) before invalidation.
| id required | string Domain allowlist entry ID (UUID) |
curl --request DELETE \ --url https://app.driftwise.ai/api/v2/admin/domains/%7Bid%7D \ --header 'Authorization: REPLACE_KEY_VALUE'
Any authenticated user can call this — unlike every other /admin/* route. Returns is_platform_admin: true once the caller is (or has been just auto-promoted to) a platform admin. Auto-promotion fires once, for the first OIDC caller with a domain-allowed email, when zero admins exist in the platform.
curl --request GET \ --url https://app.driftwise.ai/api/v2/admin/me \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "is_platform_admin": true
}Creates an org membership with a role. Enforces the org's plan seat limit; over the limit returns 402 with the canonical PaymentRequired body. On Team plans, best-effort Stripe seat-quantity sync fires after creation.
user + org + role
| org_id required | string |
| role required | string Enum: "owner" "admin" "member" "viewer" Role must be one of owner, admin, member, viewer. Case-sensitive. |
| user_id required | string |
{- "org_id": "b2c3d4e5-6789-01bc-defa-2345678901bc",
- "role": "member",
- "user_id": "a1b2c3d4-5678-90ab-cdef-1234567890ab"
}{- "acceptedAt": "string",
- "createdAt": "string",
- "expiresAt": "string",
- "id": "string",
- "invitedBy": "string",
- "lastActiveAt": "string",
- "orgID": "string",
- "role": "string",
- "updatedAt": "string",
- "userID": "string"
}Revokes the membership. Access to the org stops immediately on the handling pod; other pods invalidate within 30s (cache TTL). Best-effort Stripe seat-quantity sync runs in the background.
| id required | string Membership ID (UUID) |
curl --request DELETE \ --url https://app.driftwise.ai/api/v2/admin/memberships/%7Bid%7D \ --header 'Authorization: REPLACE_KEY_VALUE'
OIDC-only — API keys are rejected with 403 for role mutations, regardless of scope. Same-role requests are idempotent no-ops (200, noop:true, no audit row). Demoting the sole org owner returns 409.
| id required | string Membership ID (UUID) |
New role
| role required | string Enum: "owner" "admin" "member" "viewer" |
{- "role": "admin"
}{- "membership_id": "string",
- "noop": true,
- "org_id": "string",
- "role": "string",
- "user_id": "string"
}Platform-admin-only paginated list of every org. Includes free, team, and enterprise plans. Ordered by created_at descending.
| limit | integer page size (default 100, max 500) |
| offset | integer page offset |
curl --request GET \ --url 'https://app.driftwise.ai/api/v2/admin/orgs?limit=SOME_INTEGER_VALUE&offset=SOME_INTEGER_VALUE' \ --header 'Authorization: REPLACE_KEY_VALUE'
[- {
- "created_at": "string",
- "display_name": "string",
- "id": "string",
- "member_count": 0,
- "plan": "string",
- "slug": "string"
}
]Provision a new org on the Free plan. Does NOT make the caller an owner — use POST /admin/memberships afterward to assign ownership.
Org slug + display name
| display_name required | string |
| slug required | string |
{- "display_name": "string",
- "slug": "string"
}{- "created_at": "string",
- "display_name": "string",
- "features": [
- "string"
], - "id": "string",
- "plan": "string",
- "slug": "string"
}Paginated list of every user known to the platform. Includes both admins and regular users; the is_platform_admin flag distinguishes.
| limit | integer page size (default 100, max 500) |
| offset | integer page offset |
curl --request GET \ --url 'https://app.driftwise.ai/api/v2/admin/users?limit=SOME_INTEGER_VALUE&offset=SOME_INTEGER_VALUE' \ --header 'Authorization: REPLACE_KEY_VALUE'
[- {
- "created_at": "string",
- "display_name": "string",
- "email": "string",
- "id": "string",
- "is_platform_admin": true
}
]Soft-delete: sets deleted_at and clears the platform admin flag. Org memberships remain. Auth is denied immediately on the handling pod; other pods invalidate within 30s via the revocation store. Cannot delete yourself.
| id required | string User ID (UUID) |
curl --request DELETE \ --url https://app.driftwise.ai/api/v2/admin/users/%7Bid%7D \ --header 'Authorization: REPLACE_KEY_VALUE'
Returns the user's role in every org they belong to. Order unspecified.
| id required | string User ID (UUID) |
curl --request GET \ --url https://app.driftwise.ai/api/v2/admin/users/%7Bid%7D/memberships \ --header 'Authorization: REPLACE_KEY_VALUE'
[- {
- "accepted_at": "string",
- "id": "string",
- "org_id": "string",
- "org_slug": "string",
- "role": "string"
}
]Platform-admin-only (OIDC JWT required). Returns the GCP Workload Identity Federation config external CI systems use to obtain short-lived credentials. Path is under /api/v2/ but outside /admin/ — requires platform admin via the RequirePlatformAdmin middleware pinned on this route specifically.
curl --request GET \ --url https://app.driftwise.ai/api/v2/federation-info \ --header 'Authorization: REPLACE_KEY_VALUE'
{- "subject": "repo:myorg/myrepo:ref:refs/heads/main"
}OIDC-only — API keys cannot create orgs. Caller becomes the sole owner. X-Org-ID header is rejected with 400 to catch client bugs that assume the header targets an existing org. New orgs start on the Free plan.
Org slug + display name
| display_name required | string |
| slug required | string |
{- "display_name": "string",
- "slug": "string"
}{- "created_at": "string",
- "display_name": "string",
- "features": [
- "string"
], - "id": "string",
- "plan": "string",
- "slug": "string"
}Returns the org record including plan, slug, display name, and the list of active plan feature flags. Features drive frontend UI gating — features.includes('audit_compliance') is load-bearing across the app.
| id required | string Org ID (UUID) |
curl --request GET \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "created_at": "string",
- "display_name": "string",
- "features": [
- "string"
], - "id": "string",
- "plan": "string",
- "slug": "string"
}Owner-only, OIDC-only. The platform-admin mirror (PATCH /admin/memberships/{id}) permits cross-org role changes; this route is scoped to the caller's org. Self-demotion returns 409 — ask another owner. Same-role requests are idempotent no-ops (200, noop:true, no audit row). Demoting the sole owner returns 409. Cross-org attempts return 404 to avoid an enumeration oracle on membership IDs.
| id required | string Org ID (UUID) |
| membership_id required | string Membership ID (UUID) — must belong to the org in the URL |
New role
| role required | string Enum: "owner" "admin" "member" "viewer" |
{- "role": "admin"
}{- "membership_id": "string",
- "noop": true,
- "org_id": "string",
- "role": "string",
- "user_id": "string"
}Paginated list of every audit event on the platform. Covers platform events (org.created, domain.added, user.deleted) and org-scoped events. Use GET /orgs/:id/audit-log for per-org filtering.
| limit | integer page size (default 50, max 200) |
| offset | integer page offset |
curl --request GET \ --url 'https://app.driftwise.ai/api/v2/admin/audit-log?limit=SOME_INTEGER_VALUE&offset=SOME_INTEGER_VALUE' \ --header 'Authorization: REPLACE_KEY_VALUE'
[- {
- "action": "string",
- "actor_email": "string",
- "created_at": "string",
- "detail": [
- 0
], - "id": "string",
- "org_id": "string"
}
]Walks the hash chain over platform-scoped audit events (domain.added, user.deleted, org.created, etc.). Platform-admin only. Use expected_min_seq to detect tail truncation via an out-of-band watermark.
| expected_min_seq | integer Operator-tracked watermark; broken if current head_seq is lower |
curl --request GET \ --url 'https://app.driftwise.ai/api/v2/admin/audit-log/verify?expected_min_seq=SOME_INTEGER_VALUE' \ --header 'Authorization: REPLACE_KEY_VALUE'
{- "checked": 0,
- "head_hash": "string",
- "head_seq": 0,
- "status": "string"
}Paginated list of the org's Compliance Pack rows in every status (pending, running, done, error). Any org member can list.
| id required | string Org ID (UUID) |
| limit | integer page size (default 50, max 200) |
| offset | integer page offset |
curl --request GET \ --url 'https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/audit-exports?limit=SOME_INTEGER_VALUE&offset=SOME_INTEGER_VALUE' \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "items": [
- {
- "artifact_bytes": 0,
- "artifact_sha256": "string",
- "created_at": "string",
- "error_message": "string",
- "expires_at": "string",
- "finished_at": "string",
- "id": "string",
- "period_end": "string",
- "period_start": "string",
- "requested_by": "string",
- "retry_count": 0,
- "started_at": "string",
- "status": "string"
}
], - "total": 0
}OIDC-only, owner/admin. Period validated (start<end, end<=now) and clamped to plan retention. Rate-limited at 5/24h per org. Returns 202 with the queued row — poll GET /audit-exports/{eid} for status transition to 'done', then GET /audit-exports/{eid}/download for the ZIP.
| id required | string Org ID (UUID) |
Period start + end (UTC)
| period_end | string |
| period_start | string |
{- "period_end": "string",
- "period_start": "string"
}{- "artifact_bytes": 0,
- "artifact_sha256": "string",
- "created_at": "string",
- "error_message": "string",
- "expires_at": "string",
- "finished_at": "string",
- "id": "string",
- "period_end": "string",
- "period_start": "string",
- "requested_by": "string",
- "retry_count": 0,
- "started_at": "string",
- "status": "string"
}OIDC-only, owner/admin. Removes both the DB row and the stored ZIP. Artifact delete is best-effort — bucket lifecycle catches any residue. Idempotent on concurrent deletes (returns 204 even if the row vanished between the read and the delete).
| id required | string Org ID (UUID) |
| eid required | string Export ID (UUID) |
curl --request DELETE \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/audit-exports/%7Beid%7D \ --header 'Authorization: REPLACE_KEY_VALUE'
Returns the row status + metadata. Poll this to wait for async build completion (pending → running → done/error). artifact_key, artifact_bytes, and artifact_sha256 populate only when status=done.
| id required | string Org ID (UUID) |
| eid required | string Export ID (UUID) |
curl --request GET \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/audit-exports/%7Beid%7D \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "artifact_bytes": 0,
- "artifact_sha256": "string",
- "created_at": "string",
- "error_message": "string",
- "expires_at": "string",
- "finished_at": "string",
- "id": "string",
- "period_end": "string",
- "period_start": "string",
- "requested_by": "string",
- "retry_count": 0,
- "started_at": "string",
- "status": "string"
}OIDC-only, owner/admin. Streams the ZIP with Content-Disposition: attachment and a stable filename derived from the org slug + period. X-Checksum-SHA256 header lets auditors verify integrity without unzipping. 409 when the row exists but is not yet 'done'; 410 when the row is 'done' but the artifact has been GC'd (UI should re-enqueue).
| id required | string Org ID (UUID) |
| eid required | string Export ID (UUID) |
curl --request GET \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/audit-exports/%7Beid%7D/download \ --header 'Authorization: REPLACE_KEY_VALUE'
Walks the hash chain over the org's audit events and reports intact or broken. Optional expected_min_seq anchor detects tail truncation that in-chain hashes can't (privileged deletes of the latest rows verify clean because the remaining rows still hash correctly). Rate-limited at 10/hour per org.
| id required | string Org ID (UUID) |
| expected_min_seq | integer Caller-tracked watermark; broken if current head_seq is lower |
curl --request GET \ --url 'https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/audit-log/verify?expected_min_seq=SOME_INTEGER_VALUE' \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "checked": 0,
- "head_hash": "string",
- "head_seq": 0,
- "status": "string"
}Returns the shipping rule catalog. Optional provider narrows Terraform resource-type lists to one cloud (aws, gcp, azure). Plan-noise rules are matched and filtered similarly.
| provider | string Enum: "aws" "gcp" "azure" Filter types by provider |
curl --request GET \ --url 'https://app.driftwise.ai/api/v2/builtin-rules?provider=SOME_STRING_VALUE' \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "noise_rules": [
- {
- "category": "string",
- "description": "string",
- "fixed_in_version": "string",
- "fixes": [
- {
- "cons": [
- "string"
], - "detail": "string",
- "label": "string",
- "pros": [
- "string"
], - "summary": "string"
}
], - "id": "string",
- "provider": "string",
- "severity": "string",
- "title": "string"
}
], - "risk_encryption_types": [
- "string"
], - "risk_iam_types": [
- "string"
], - "risk_network_types": [
- "string"
], - "risk_security_types": [
- "string"
], - "risk_stateful_types": [
- "string"
]
}Returns both enabled and disabled rules. Optional rule_type narrows to "noise" or "risk". Not paginated — rule counts are bounded in practice.
| id required | string Org ID (UUID) |
| rule_type | string Enum: "noise" "risk" Filter by rule type |
curl --request GET \ --url 'https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/custom-rules?rule_type=SOME_STRING_VALUE' \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "rules": [
- {
- "config": [
- 0
], - "created_at": "string",
- "created_by": "string",
- "description": "string",
- "enabled": true,
- "id": "string",
- "name": "string",
- "rule_type": "string",
- "updated_at": "string"
}
]
}Org-defined noise or risk rule. Config is validated against the rule_type schema before save — invalid configs return 400. Requires an authenticated user identity for the audit trail.
| id required | string Org ID (UUID) |
Rule definition
| config required | Array of integers |
| description required | string |
| name required | string |
| rule_type required | string Enum: "noise" "risk" |
{- "config": [
- 0
], - "description": "string",
- "name": "string",
- "rule_type": "noise"
}{- "config": [
- 0
], - "created_at": "string",
- "created_by": "string",
- "description": "string",
- "enabled": true,
- "id": "string",
- "name": "string",
- "rule_type": "string",
- "updated_at": "string"
}Hard delete. To temporarily disable a rule without losing config, PATCH with enabled=false instead.
| id required | string Org ID (UUID) |
| rule_id required | string Custom rule ID (UUID) |
curl --request DELETE \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/custom-rules/%7Brule_id%7D \ --header 'X-API-Key: REPLACE_KEY_VALUE'
Returns the rule including its config blob and enabled flag.
| id required | string Org ID (UUID) |
| rule_id required | string Custom rule ID (UUID) |
curl --request GET \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/custom-rules/%7Brule_id%7D \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "config": [
- 0
], - "created_at": "string",
- "created_by": "string",
- "description": "string",
- "enabled": true,
- "id": "string",
- "name": "string",
- "rule_type": "string",
- "updated_at": "string"
}Flips the enabled flag. Preserves config so re-enabling later restores the rule exactly as configured.
| id required | string Org ID (UUID) |
| rule_id required | string Custom rule ID (UUID) |
Desired enabled state
| enabled | boolean |
{- "enabled": true
}Replaces name, description, and config. rule_type is immutable — to change a rule's type, delete it and create a new one. Config is revalidated against the existing rule_type.
| id required | string Org ID (UUID) |
| rule_id required | string Custom rule ID (UUID) |
Updated fields
| config required | Array of integers |
| description required | string |
| name required | string |
{- "config": [
- 0
], - "description": "string",
- "name": "string"
}{- "config": [
- 0
], - "created_at": "string",
- "created_by": "string",
- "description": "string",
- "enabled": true,
- "id": "string",
- "name": "string",
- "rule_type": "string",
- "updated_at": "string"
}Returns the org's exception list against the built-in rule catalog. Optional rule_type narrows to "noise" or "risk".
| id required | string Org ID (UUID) |
| rule_type | string Enum: "noise" "risk" Filter by rule type |
curl --request GET \ --url 'https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/disabled-rules?rule_type=SOME_STRING_VALUE' \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "rules": [
- {
- "builtin_rule_id": "string",
- "created_at": "string",
- "disabled_by": "string",
- "id": "string",
- "reason": "string",
- "rule_type": "string"
}
]
}Suppresses a shipping rule for this org only. Built-in rules themselves are not modified — this creates an org-scoped exception row. Optional reason is free-form; surfaces in audit UI.
| id required | string Org ID (UUID) |
Which rule + why
| builtin_rule_id required | string |
| reason | string |
| rule_type required | string Enum: "noise" "risk" |
{- "builtin_rule_id": "string",
- "reason": "string",
- "rule_type": "noise"
}{- "builtin_rule_id": "string",
- "created_at": "string",
- "disabled_by": "string",
- "id": "string",
- "reason": "string",
- "rule_type": "string"
}Removes the org's exception for this rule, restoring it to active. The disabled-rule row is deleted, not flipped — re-disabling later creates a fresh row.
| id required | string Org ID (UUID) |
| disabled_rule_id required | string Disabled rule record ID (UUID) |
curl --request DELETE \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/disabled-rules/%7Bdisabled_rule_id%7D \ --header 'X-API-Key: REPLACE_KEY_VALUE'
Aggregated recurring attributes (noise candidates) across recent scans, bundled with the effective settings that drove the aggregation (window_days, recurrence_threshold). Frontend dashboards render both together. Optional repo_owner/repo_name narrow by originating repo.
| id required | string Org ID (UUID) |
| repo_owner | string Filter by repo owner |
| repo_name | string Filter by repo name |
curl --request GET \ --url 'https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/plan-noise?repo_owner=SOME_STRING_VALUE&repo_name=SOME_STRING_VALUE' \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "patterns": [
- {
- "action": "string",
- "attribute": "string",
- "classification": "string",
- "fingerprint": "string",
- "first_seen": "string",
- "last_seen": "string",
- "matched_rule": {
- "category": "string",
- "description": "string",
- "fixed_in_version": "string",
- "fixes": [
- {
- "cons": [
- "string"
], - "detail": "string",
- "label": "string",
- "pros": [
- "string"
], - "summary": "string"
}
], - "id": "string",
- "provider": "string",
- "severity": "string",
- "title": "string"
}, - "occurrence_count": 0,
- "repo_name": "string",
- "repo_owner": "string",
- "resource_address": "string",
- "resource_type": "string"
}
], - "settings": {
- "recurrence_threshold": 0,
- "window_days": 0
}
}LLM-powered remediation suggestions for a recurring plan-noise pattern. Returns tiered fixes (quick patch, structural refactor, long-term rework) with pros/cons. Same BYOK/platform gate as AnalyzePlan — BYOK bypasses platform quota, platform path reserves and releases on failure.
| id required | string Org ID (UUID) |
| fingerprint required | string Pattern fingerprint (hash of resource+attribute+action) |
curl --request POST \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/plan-noise/%7Bfingerprint%7D/fix \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "confidence": "high",
- "fixes": [
- {
- "cons": [
- "string"
], - "detail": "string",
- "label": "string",
- "pros": [
- "string"
], - "summary": "string"
}
], - "trace_id": "trc-abc123"
}Returns recurrence_threshold (minimum occurrences to flag) and window_days (lookback period). Defaults applied server-side when the org has never customized.
| id required | string Org ID (UUID) |
curl --request GET \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/plan-noise/settings \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "recurrence_threshold": 0,
- "window_days": 0
}Replaces recurrence_threshold and window_days. Takes effect on the next ListPlanNoise call — no re-aggregation job triggered.
| id required | string Org ID (UUID) |
New thresholds
| recurrence_threshold required | integer >= 1 |
| window_days required | integer >= 1 |
{- "recurrence_threshold": 1,
- "window_days": 1
}{- "recurrence_threshold": 0,
- "window_days": 0
}Bulk-creates suppressions. The fingerprints and resource_addresses arrays must be the same length — each index pair produces one suppression. Duration is one of "7d", "30d", "90d", or "forever". Requires an authenticated user identity for the audit trail; API-key calls without user context return 403.
| id required | string Org ID (UUID) |
Fingerprints + addresses + duration
| duration required | string Enum: "7d" "30d" "90d" "forever" |
| fingerprints required | Array of strings [ 1 .. 100 ] items |
| is_false_positive | boolean |
| reason required | string |
| resource_addresses required | Array of strings [ 1 .. 100 ] items |
{- "duration": "7d",
- "fingerprints": [
- "string"
], - "is_false_positive": true,
- "reason": "string",
- "resource_addresses": [
- "string"
]
}{- "suppressions": [
- {
- "created_at": "string",
- "duration": "string",
- "expires_at": "string",
- "fingerprint": "string",
- "id": "string",
- "is_false_positive": true,
- "reason": "string",
- "resource_address": "string",
- "suppressed_by": "string"
}
]
}Returns active suppressions. Pass include_expired=true to include suppressions past their expiry (useful for audit). Not paginated.
| id required | string Org ID (UUID) |
| include_expired | boolean Include already-expired suppressions |
curl --request GET \ --url 'https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/plan-noise/suppressions?include_expired=SOME_BOOLEAN_VALUE' \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "suppressions": [
- {
- "created_at": "string",
- "duration": "string",
- "expires_at": "string",
- "fingerprint": "string",
- "id": "string",
- "is_false_positive": true,
- "reason": "string",
- "resource_address": "string",
- "suppressed_by": "string"
}
]
}Restores visibility of the suppressed drift. Already-deleted suppressions return 404.
| id required | string Org ID (UUID) |
| suppression_id required | string Suppression ID (UUID) |
curl --request DELETE \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/plan-noise/suppressions/%7Bsuppression_id%7D \ --header 'X-API-Key: REPLACE_KEY_VALUE'
Returns the attribute-risk policy used by drift classification. An empty policy (version 0, rules: []) is returned when no policy has been saved — callers don't need to handle a 404.
| id required | string Org ID (UUID) |
curl --request GET \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/policy \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "rules": [
- {
- "flag_pattern": "string",
- "id": "string",
- "reason": "string",
- "resource_pattern": "string",
- "severity": "string"
}
], - "updated_at": "string",
- "version": 0
}Owner/admin only. Policy is sanitized (lowercased enum values, trimmed whitespace) and validated server-side before save. 422 on validation failure (e.g. unknown severity, malformed glob pattern).
| id required | string Org ID (UUID) |
Policy rules + version
Array of objects (github_com_driftwise_backend_internal_policy.PolicyRule) | |
| updated_at | string |
| version | integer |
{- "rules": [
- {
- "flag_pattern": "string",
- "id": "string",
- "reason": "string",
- "resource_pattern": "string",
- "severity": "string"
}
], - "updated_at": "string",
- "version": 0
}{- "rules": [
- {
- "flag_pattern": "string",
- "id": "string",
- "reason": "string",
- "resource_pattern": "string",
- "severity": "string"
}
], - "updated_at": "string",
- "version": 0
}Fetches documentation + argument/attribute schemas from registry.terraform.io for one (provider, resource_type) pair. Redis-backed cache; rate-limited at 120/min per user. Response shape mirrors registry's native schema.
| provider required | string Enum: "aws" "gcp" "azure" Cloud provider |
| resource_type required | string Terraform resource type (e.g. aws_s3_bucket) |
curl --request GET \ --url 'https://app.driftwise.ai/api/v2/tf-registry/meta?provider=SOME_STRING_VALUE&resource_type=SOME_STRING_VALUE' \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "docs_url": "string",
- "fetched_at": "string",
- "latest_version": "string",
- "min_version": "string",
- "provider": "string",
- "resource_type": "string"
}Returns the compiled-in LLM provider enums (anthropic, openai, bedrock, gemini, azure_openai). Response is static per server build; frontend caches indefinitely.
curl --request GET \ --url https://app.driftwise.ai/api/v2/llm-providers \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "providers": [
- {
- "categories": [
- "string"
], - "credential_fields": [
- {
- "key": "string",
- "label": "string",
- "required": true,
- "secret": true,
- "type": "string"
}
], - "label": "string",
- "name": "string"
}
]
}Hard delete. Subsequent LLM calls fall back to the platform default + its quota gate (weekly/hourly). 404 if no row exists.
| id required | string Org ID (UUID) |
curl --request DELETE \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/llm-config \ --header 'X-API-Key: REPLACE_KEY_VALUE'
Returns only metadata — provider name + timestamps. Credentials never exposed: the raw api_key / aws_secret_access_key / azure_api_key are write-only.
| id required | string Org ID (UUID) |
curl --request GET \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/llm-config \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "configured_at": "string",
- "last_validated_at": "string",
- "provider": "string"
}Strict decoder rejects unknown fields with 400. Credentials are envelope-encrypted before DB write and wiped from memory after. Validation rejects "hosted mode" variants: Anthropic requires api_key, Bedrock requires explicit access key + region (not instance profile — the profile would attach to DriftWise's SA, not the customer's).
| id required | string Org ID (UUID) |
BYOK credentials
| api_key | string Anthropic / OpenAI / Gemini |
| aws_access_key_id | string |
| aws_region | string AWS Bedrock |
| aws_role_arn | string |
| aws_secret_access_key | string |
| aws_session_token | string |
| azure_api_key | string |
| azure_api_version | string |
| azure_deployment | string |
| azure_endpoint | string Azure OpenAI |
| model | string |
| provider | string |
{- "api_key": "string",
- "aws_access_key_id": "string",
- "aws_region": "string",
- "aws_role_arn": "string",
- "aws_secret_access_key": "string",
- "aws_session_token": "string",
- "azure_api_key": "string",
- "azure_api_version": "string",
- "azure_deployment": "string",
- "azure_endpoint": "string",
- "model": "string",
- "provider": "string"
}{- "configured_at": "string",
- "last_validated_at": "string",
- "provider": "string"
}Returns current-bucket used/cap for both the weekly and hourly gates, plus the next reset timestamp for each. byok_configured=true means the caps are advisory: BYOK calls bypass both gates. Caps of -1 mean unlimited.
| id required | string Org ID (UUID) |
curl --request GET \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/llm-usage \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "byok_configured": true,
- "hour_resets_at": "string",
- "hourly_cap": 0,
- "hourly_used": 0,
- "week_resets_at": "string",
- "weekly_cap": 0,
- "weekly_used": 0
}API-key-only endpoint for the in-cluster DriftWise operator. Creates a scan_run with scan_type=operator and bulk-upserts live_resources. K8s resources skip cloud enrichment (operator sends full properties inline). OIDC callers are rejected with 403 — this is a machine-to-machine path.
Cluster + namespace + resources
| cluster_id required | string |
| custom_prompt | string |
| generate_iac | boolean |
| iac_format | string |
| namespace required | string |
required | Array of objects (github_com_driftwise_backend_internal_cloud.Resource) |
{- "cluster_id": "string",
- "custom_prompt": "string",
- "generate_iac": true,
- "iac_format": "string",
- "namespace": "string",
- "resources": [
- {
- "enrichmentFailure": "unsupported_identifier",
- "id": "string",
- "name": "string",
- "normalizedType": "k8s/deployment",
- "properties": {
- "property1": [
- 0
], - "property2": [
- 0
]
}, - "provider": "string",
- "providerType": "string",
- "region": "string",
- "relationships": [
- {
- "targetID": "string",
- "type": "string"
}
], - "tags": {
- "property1": "string",
- "property2": "string"
}
}
]
}{- "report_id": "string",
- "resource_count": 0
}Paginated list of registered cloud accounts. Credentials are redacted — callers see only metadata (display name, provider, external account ID, credential type).
| id required | string Org ID (UUID) |
| limit | integer page size (default 100, max 500) |
| offset | integer page offset |
curl --request GET \ --url 'https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/accounts?limit=SOME_INTEGER_VALUE&offset=SOME_INTEGER_VALUE' \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "accounts": [
- {
- "created_at": "string",
- "credential_type": "string",
- "default_region": "string",
- "display_name": "string",
- "external_account_id": "string",
- "id": "string",
- "last_validated_at": "string",
- "org_id": "string",
- "provider": "string"
}
], - "limit": 100,
- "offset": 0,
- "total": 3
}Validates credentials against the cloud provider before saving. Credentials are envelope-encrypted at rest (AES-256-GCM); the DB only sees ciphertext. Account-ID spoofing defense: the provider-reported identity must match the caller's external_account_id. Returns 402 when the org's plan cloud-account limit is reached.
| id required | string Org ID (UUID) |
Cloud account config
| credential_ref required | string |
| credential_type required | string |
| default_region | string |
| display_name required | string |
| external_account_id required | string |
| provider required | string |
{- "credential_ref": "string",
- "credential_type": "string",
- "default_region": "string",
- "display_name": "string",
- "external_account_id": "string",
- "provider": "string"
}{- "created_at": "string",
- "credential_type": "string",
- "default_region": "string",
- "display_name": "string",
- "external_account_id": "string",
- "id": "string",
- "last_validated_at": "string",
- "org_id": "string",
- "provider": "string"
}Paginated list of cloud resources discovered by recent scans. Optional account_id narrows to one cloud account.
| id required | string Org ID (UUID) |
| account_id | string Filter by cloud account ID |
| limit | integer page size (default 100, max 500) |
| offset | integer page offset |
curl --request GET \ --url 'https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/resources?account_id=SOME_STRING_VALUE&limit=SOME_INTEGER_VALUE&offset=SOME_INTEGER_VALUE' \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "limit": 0,
- "offset": 0,
- "resources": [
- {
- "enrichment_status": "string",
- "iac_resource_id": "string",
- "id": "string",
- "last_seen_at": "string",
- "name": "string",
- "normalized_type": "string",
- "provider": "string",
- "provider_resource_id": "string",
- "provider_type": "string",
- "region": "string",
- "status": "string"
}
], - "total": 0
}Fetches full resource properties from the cloud API on demand. Skips cleanly for resources already at enrichment_status=enriched or n/a. Returns 422 when the enricher reports a per-resource failure (e.g. permission denied, resource gone) — the resource row is marked failed and the reason is returned.
| id required | string Org ID (UUID) |
| resource_id required | string Live resource ID (UUID) |
curl --request POST \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/resources/%7Bresource_id%7D/enrich \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "enrichment_failure_reason": "string",
- "enrichment_status": "string",
- "iac_resource_id": "string",
- "id": "string",
- "last_seen_at": "string",
- "name": "string",
- "normalized_type": "string",
- "properties": [
- 0
], - "provider": "string",
- "provider_resource_id": "string",
- "provider_type": "string",
- "region": "string",
- "status": "string"
}Returns descriptors for every registered cloud provider (AWS, Azure, GCP + any future additions). Also serves as the frontend's auth-probe target after OIDC login — a 401/403 triggers the "access denied" sign-out flow.
curl --request GET \ --url https://app.driftwise.ai/api/v2/providers \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "providers": [
- {
- "categories": [
- "compute"
], - "credential_fields": [
- {
- "credential_types": [
- "string"
], - "key": "string",
- "label": "string",
- "options": [
- "string"
], - "required": true,
- "secret": true,
- "type": "string"
}
], - "credential_types": [
- {
- "label": "string",
- "value": "string"
}
], - "label": "string",
- "name": "string"
}
]
}LLM-powered plan analysis. Returns a risk level, structured change summary, and human-readable narrative. BYOK LLM config (if present for the org) takes precedence over the platform default; transient BYOK failures fail loud rather than silently falling back (the "your data, your perimeter" contract). Non-BYOK callers consume platform quota via PlatformGate — 402 on plan exhaustion, 429 on hourly throttle.
| id required | string Org ID (UUID) |
Terraform plan JSON + optional CI metadata
object (internal_api.CIMetadataRequest) | |
| plan_json required | string |
{- "ci": {
- "branch": "string",
- "commit_sha": "string",
- "pr_number": 0,
- "provider": "string",
- "repo_name": "string",
- "repo_owner": "string",
- "repo_url": "string"
}, - "plan_json": "string"
}{- "changes": [
- 0
], - "narrative": "string",
- "plan_noise": {
- "known_patterns": 0,
- "novel_count": 0,
- "recurring_count": 0,
- "signals": [
- {
- "action": "string",
- "attribute": "string",
- "classification": "string",
- "matched_rule": {
- "category": "string",
- "description": "string",
- "fixed_in_version": "string",
- "fixes": [
- {
- "cons": [
- "string"
], - "detail": "string",
- "label": "string",
- "pros": [
- "string"
], - "summary": "string"
}
], - "id": "string",
- "provider": "string",
- "severity": "string",
- "title": "string"
}, - "occurrence_count": 0,
- "resource_address": "string"
}
]
}, - "risk_level": "string",
- "scan_run": {
- "branch": "string",
- "changed_count": 0,
- "cloud_account_id": "string",
- "commit_sha": "string",
- "created_at": "string",
- "drift_status": "string",
- "finished_at": "string",
- "id": "string",
- "missing_count": 0,
- "new_count": 0,
- "org_id": "string",
- "pr_number": 0,
- "provider": "string",
- "repo_name": "string",
- "repo_owner": "string",
- "repo_url": "string",
- "resource_count": 0,
- "result_json": [
- 0
], - "scan_errors": [
- {
- "code": "string",
- "error": "string",
- "region": "string",
- "resource_type": "string"
}
], - "scan_type": "string",
- "started_at": "string",
- "status": "string",
- "status_kind": "string"
}, - "summary": [
- 0
]
}Per-account coverage percentage, risk level, and undeclared-resource count aggregated over the latest scan/drift data. Drives the dashboard's posture card.
| id required | string Org ID (UUID) |
curl --request GET \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/posture \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "accounts": [
- {
- "account_name": "string",
- "cloud_account_id": "string",
- "coverage_pct": 0,
- "provider": "string",
- "risk_level": "string",
- "total_live": 0,
- "undeclared_count": 0
}
], - "org_id": "string"
}Returns the drift snapshot (new, changed, missing items) produced by the drift worker. Returns 404 if drift hasn't been computed yet — call POST /scans/{scan_id}/drift/compute first to produce it.
| id required | string Org ID (UUID) |
| scan_id required | string Scan run ID (UUID) |
curl --request GET \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/scans/%7Bscan_id%7D/drift \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "changed_count": 0,
- "coverage_pct": 0,
- "created_at": "string",
- "extra_count": 0,
- "id": "string",
- "items": [
- {
- "attribute_diffs": [
- 0
], - "drift_type": "string",
- "iac_resource_id": "string",
- "id": "string",
- "live_resource_id": "string",
- "provider": "string",
- "region": "string",
- "resolution": "string",
- "resource_name": "string",
- "resource_type": "string"
}
], - "matched_count": 0,
- "missing_count": 0,
- "narrative": "string",
- "narrative_status": "string",
- "parse_failure_count": 0,
- "risk_level": "string",
- "scan_run_id": "string",
- "total_iac": 0,
- "total_live": 0
}Runs drift computation inline against the scan's live+IaC data and returns the produced snapshot. Requires scan to be status=done. Takes seconds for small scans, tens of seconds for large ones — the companion async drift-worker process handles the same computation for scheduled workflows.
| id required | string Org ID (UUID) |
| scan_id required | string Scan run ID (UUID) |
curl --request POST \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/scans/%7Bscan_id%7D/drift/compute \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "changed_count": 0,
- "coverage_pct": 0,
- "created_at": "string",
- "extra_count": 0,
- "id": "string",
- "items": [
- {
- "attribute_diffs": [
- 0
], - "drift_type": "string",
- "iac_resource_id": "string",
- "id": "string",
- "live_resource_id": "string",
- "provider": "string",
- "region": "string",
- "resolution": "string",
- "resource_name": "string",
- "resource_type": "string"
}
], - "matched_count": 0,
- "missing_count": 0,
- "narrative": "string",
- "narrative_status": "string",
- "parse_failure_count": 0,
- "risk_level": "string",
- "scan_run_id": "string",
- "total_iac": 0,
- "total_live": 0
}Paginated list of API keys. The raw_key field is always empty — the raw key is shown only once at creation time (POST /api-keys). Callers see key IDs, names, prefixes, scopes, and creation timestamps.
| id required | string Org ID (UUID) |
| limit | integer page size (default 100, max 500) |
| offset | integer page offset |
curl --request GET \ --url 'https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/api-keys?limit=SOME_INTEGER_VALUE&offset=SOME_INTEGER_VALUE' \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "api_keys": [
- {
- "created_at": "string",
- "id": "string",
- "key_prefix": "string",
- "name": "string",
- "raw_key": "string",
- "scopes": [
- "string"
]
}
], - "limit": 100,
- "offset": 0,
- "total": 2
}Returns the raw key once in the response raw_key field — never retrievable afterward. Scopes are validated at create time (typos like "admin" instead of "write" fail fast). Returns 402 when the org's plan API key limit is reached.
| id required | string Org ID (UUID) |
Key name + scopes
| name required | string |
| scopes | Array of strings |
{- "name": "string",
- "scopes": [
- "string"
]
}{- "created_at": "string",
- "id": "string",
- "key_prefix": "string",
- "name": "string",
- "raw_key": "string",
- "scopes": [
- "string"
]
}OIDC-only — API keys cannot revoke other API keys (lateral-movement defense). Revocation propagates across backend pods within one Redis round-trip via the revocation store; on Redis failure falls back to the 30s cache TTL floor. Already-revoked keys return 404 — idempotent from the caller's perspective.
| id required | string Org ID (UUID) |
| keyID required | string API key ID (UUID) |
curl --request DELETE \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/api-keys/%7BkeyID%7D \ --header 'Authorization: REPLACE_KEY_VALUE'
OIDC-only. Provisions a Stripe customer on first call (stored in org.stripe_customer_id). Returns the hosted Stripe Checkout URL. Frontend redirects into this URL; Stripe's return URL points back to the app, and webhook events finalize the plan change.
| id required | string Org ID (UUID) |
Billing interval
| interval required | string Enum: "month" "year" Interval selects the Stripe price: "month" for monthly billing, "year" for annual. Anything else returns 400. |
{- "interval": "month"
}OIDC-only. Returns the hosted Stripe Customer Portal URL where the caller can manage their subscription, view invoices, update payment methods, and cancel. Requires an existing Stripe customer — a 400 is returned if the org has no Stripe account yet (upgrade via checkout first).
| id required | string Org ID (UUID) |
curl --request POST \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/billing/portal \ --header 'Authorization: REPLACE_KEY_VALUE'
Reads from the database only — no Stripe round-trip. Available even when STRIPE_SECRET_KEY is unset. Seat count is limit-aware; -1 in seats.included means unlimited. API-key callers always see role "admin" regardless of the key's scope. Platform-LLM analysis counters live on the dedicated /llm-usage endpoint.
| id required | string Org ID (UUID) |
curl --request GET \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/billing/usage \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "plan": "team",
- "role": "owner",
- "seats": {
- "included": 10,
- "used": 7
}, - "subscription_status": "active"
}Returns up to limit linked installations (default 100, max 500). Unlike most list endpoints, this is limit-capped rather than paginated — orgs typically have <10 installations.
| id required | string Org ID (UUID) |
| limit | integer max installations to return (default 100, max 500) |
curl --request GET \ --url 'https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/github-installations?limit=SOME_INTEGER_VALUE' \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "github_installations": [
- {
- "account_login": "string",
- "created_at": "string",
- "id": "string",
- "installation_id": 0,
- "updated_at": "string"
}
]
}Called from the GitHub App post-install redirect to associate the installation with this DriftWise org. installation_id comes from GitHub; account_login is the GitHub org/user that installed the App.
| id required | string Org ID (UUID) |
GitHub installation ID + account login
| account_login required | string |
| installation_id required | integer |
{- "account_login": "string",
- "installation_id": 0
}{- "account_login": "string",
- "created_at": "string",
- "id": "string",
- "installation_id": 0,
- "updated_at": "string"
}Removes the DriftWise-side record. Does not uninstall the App from GitHub — the user must do that separately via GitHub's UI.
| id required | string Org ID (UUID) |
| install_id required | string Installation link ID (UUID) |
curl --request DELETE \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/github-installations/%7Binstall_id%7D \ --header 'X-API-Key: REPLACE_KEY_VALUE'
Paginated list. raw_secret is always empty — shown only once at creation. Optional provider filter narrows by GitLab/Atlantis.
| id required | string Org ID (UUID) |
| provider | string Enum: "gitlab" "atlantis" Filter by provider |
| limit | integer page size (default 100, max 500) |
| offset | integer page offset |
curl --request GET \ --url 'https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/webhook-configs?provider=SOME_STRING_VALUE&limit=SOME_INTEGER_VALUE&offset=SOME_INTEGER_VALUE' \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "limit": 100,
- "offset": 0,
- "total": 3,
- "webhook_configs": [
- {
- "api_token_expires_at": "string",
- "api_token_prefix": "string",
- "api_token_status": "active",
- "created_at": "string",
- "enabled": true,
- "id": "string",
- "label": "string",
- "provider": "string",
- "provider_base_url": "string",
- "raw_secret": "string",
- "repo_path": "string",
- "secret_prefix": "string",
- "updated_at": "string"
}
]
}Generates a whsec_-prefixed secret (32 random bytes, hex) returned once in the response. GitLab tokens are live-validated against the target instance — expired tokens, wrong scope, or unreachable endpoints fail at save time with a specific 400 message. provider_base_url is SSRF-validated (must be public HTTPS).
| id required | string Org ID (UUID) |
Webhook config
| api_token | string |
| label required | string |
| provider required | string |
| provider_base_url | string |
| repo_path | string |
{- "api_token": "string",
- "label": "string",
- "provider": "string",
- "provider_base_url": "string",
- "repo_path": "string"
}{- "api_token_expires_at": "string",
- "api_token_prefix": "string",
- "api_token_status": "active",
- "created_at": "string",
- "enabled": true,
- "id": "string",
- "label": "string",
- "provider": "string",
- "provider_base_url": "string",
- "raw_secret": "string",
- "repo_path": "string",
- "secret_prefix": "string",
- "updated_at": "string"
}Removes the config and stops accepting webhooks signed with its secret. Already-deleted configs return 404.
| id required | string Org ID (UUID) |
| config_id required | string Webhook config ID (UUID) |
curl --request DELETE \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/webhook-configs/%7Bconfig_id%7D \ --header 'X-API-Key: REPLACE_KEY_VALUE'
Toggle enabled state and/or rotate the api_token. At least one field required — empty body is a caller bug. Token rotations re-validate against GitLab before save; failures return 400 with an actionable message and never contain the raw token.
| id required | string Org ID (UUID) |
| config_id required | string Webhook config ID (UUID) |
Fields to update
| api_token | string non-empty to replace; empty string clears the token |
| enabled | boolean |
{- "api_token": "string",
- "enabled": true
}{- "api_token_expires_at": "string",
- "api_token_prefix": "string",
- "api_token_status": "active",
- "created_at": "string",
- "enabled": true,
- "id": "string",
- "label": "string",
- "provider": "string",
- "provider_base_url": "string",
- "raw_secret": "string",
- "repo_path": "string",
- "secret_prefix": "string",
- "updated_at": "string"
}Paginated scan history, filtered by retention window (free: 24h, team+: unlimited). Optional account_id and scan_type narrow results.
| id required | string Org ID (UUID) |
| account_id | string Filter by cloud account ID |
| scan_type | string Enum: "manual" "scheduled" "webhook" Filter by scan type |
| limit | integer page size (default 50, max 200) |
| offset | integer page offset |
curl --request GET \ --url 'https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/scans?account_id=SOME_STRING_VALUE&scan_type=SOME_STRING_VALUE&limit=SOME_INTEGER_VALUE&offset=SOME_INTEGER_VALUE' \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "limit": 50,
- "offset": 0,
- "scans": [
- {
- "branch": "string",
- "changed_count": 0,
- "cloud_account_id": "string",
- "commit_sha": "string",
- "created_at": "string",
- "drift_status": "string",
- "finished_at": "string",
- "id": "string",
- "missing_count": 0,
- "new_count": 0,
- "org_id": "string",
- "pr_number": 0,
- "provider": "string",
- "repo_name": "string",
- "repo_owner": "string",
- "repo_url": "string",
- "resource_count": 0,
- "result_json": [
- 0
], - "scan_errors": [
- {
- "code": "string",
- "error": "string",
- "region": "string",
- "resource_type": "string"
}
], - "scan_type": "string",
- "started_at": "string",
- "status": "string",
- "status_kind": "string"
}
], - "total": 42
}Creates a scan_run row with status=pending and returns immediately with 202. A background worker picks it up and transitions it through running → done (or error). Poll GET /scans/{scan_id} to track progress.
| id required | string Org ID (UUID) |
Scan config: cloud account, region filters, CI metadata
object (internal_api.CIMetadataRequest) | |
| cloud_account_id required | string |
| filter_regions | Array of strings |
{- "ci": {
- "branch": "string",
- "commit_sha": "string",
- "pr_number": 0,
- "provider": "string",
- "repo_name": "string",
- "repo_owner": "string",
- "repo_url": "string"
}, - "cloud_account_id": "string",
- "filter_regions": [
- "string"
]
}{- "branch": "string",
- "changed_count": 0,
- "cloud_account_id": "string",
- "commit_sha": "string",
- "created_at": "string",
- "drift_status": "string",
- "finished_at": "string",
- "id": "string",
- "missing_count": 0,
- "new_count": 0,
- "org_id": "string",
- "pr_number": 0,
- "provider": "string",
- "repo_name": "string",
- "repo_owner": "string",
- "repo_url": "string",
- "resource_count": 0,
- "result_json": [
- 0
], - "scan_errors": [
- {
- "code": "string",
- "error": "string",
- "region": "string",
- "resource_type": "string"
}
], - "scan_type": "string",
- "started_at": "string",
- "status": "string",
- "status_kind": "string"
}Returns current state including status, resource counts, scan_errors, and derived status_kind (ok/partial/failed). drift_status is non-null only after drift computation has run.
| id required | string Org ID (UUID) |
| scan_id required | string Scan run ID (UUID) |
curl --request GET \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/scans/%7Bscan_id%7D \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "branch": "string",
- "changed_count": 0,
- "cloud_account_id": "string",
- "commit_sha": "string",
- "created_at": "string",
- "drift_status": "string",
- "finished_at": "string",
- "id": "string",
- "missing_count": 0,
- "new_count": 0,
- "org_id": "string",
- "pr_number": 0,
- "provider": "string",
- "repo_name": "string",
- "repo_owner": "string",
- "repo_url": "string",
- "resource_count": 0,
- "result_json": [
- 0
], - "scan_errors": [
- {
- "code": "string",
- "error": "string",
- "region": "string",
- "resource_type": "string"
}
], - "scan_type": "string",
- "started_at": "string",
- "status": "string",
- "status_kind": "string"
}Returns the drift snapshot (new, changed, missing items) produced by the drift worker. Returns 404 if drift hasn't been computed yet — call POST /scans/{scan_id}/drift/compute first to produce it.
| id required | string Org ID (UUID) |
| scan_id required | string Scan run ID (UUID) |
curl --request GET \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/scans/%7Bscan_id%7D/drift \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "changed_count": 0,
- "coverage_pct": 0,
- "created_at": "string",
- "extra_count": 0,
- "id": "string",
- "items": [
- {
- "attribute_diffs": [
- 0
], - "drift_type": "string",
- "iac_resource_id": "string",
- "id": "string",
- "live_resource_id": "string",
- "provider": "string",
- "region": "string",
- "resolution": "string",
- "resource_name": "string",
- "resource_type": "string"
}
], - "matched_count": 0,
- "missing_count": 0,
- "narrative": "string",
- "narrative_status": "string",
- "parse_failure_count": 0,
- "risk_level": "string",
- "scan_run_id": "string",
- "total_iac": 0,
- "total_live": 0
}Runs drift computation inline against the scan's live+IaC data and returns the produced snapshot. Requires scan to be status=done. Takes seconds for small scans, tens of seconds for large ones — the companion async drift-worker process handles the same computation for scheduled workflows.
| id required | string Org ID (UUID) |
| scan_id required | string Scan run ID (UUID) |
curl --request POST \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/scans/%7Bscan_id%7D/drift/compute \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "changed_count": 0,
- "coverage_pct": 0,
- "created_at": "string",
- "extra_count": 0,
- "id": "string",
- "items": [
- {
- "attribute_diffs": [
- 0
], - "drift_type": "string",
- "iac_resource_id": "string",
- "id": "string",
- "live_resource_id": "string",
- "provider": "string",
- "region": "string",
- "resolution": "string",
- "resource_name": "string",
- "resource_type": "string"
}
], - "matched_count": 0,
- "missing_count": 0,
- "narrative": "string",
- "narrative_status": "string",
- "parse_failure_count": 0,
- "risk_level": "string",
- "scan_run_id": "string",
- "total_iac": 0,
- "total_live": 0
}Returns summary metadata for every LLM trace produced while processing the scan — narrative generation, drift classification, plan-noise fix, etc. Full trace bodies (prompt, response, tokens) available via GET /traces/{trace_id}.
| id required | string Org ID (UUID) |
| scan_id required | string Scan run ID (UUID) |
curl --request GET \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/scans/%7Bscan_id%7D/traces \ --header 'X-API-Key: REPLACE_KEY_VALUE'
[- {
- "cached": true,
- "created_at": "string",
- "fallback": true,
- "id": "string",
- "input_tokens": 0,
- "kind": "string",
- "latency_ms": 0,
- "model": "string",
- "output_tokens": 0
}
]Creates scan runs for every cloud account in the request (empty account_ids = all accounts). Returns 402 if creating these scans would push the org over its concurrent-scan plan limit. Rate-limited at 5/min per org.
| id required | string Org ID (UUID) |
Account selection + region filters
| account_ids | Array of strings nil/empty = all accounts |
| filter_regions | Array of strings |
{- "account_ids": [
- "string"
], - "filter_regions": [
- "string"
]
}{- "count": 0,
- "scan_ids": [
- "string"
]
}Paginated list. Disabled schedules (by user or auto-disable after too many consecutive failures) are included — check the enabled and disabled_reason fields.
| id required | string Org ID (UUID) |
| limit | integer page size (default 50, max 200) |
| offset | integer page offset |
curl --request GET \ --url 'https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/schedules?limit=SOME_INTEGER_VALUE&offset=SOME_INTEGER_VALUE' \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "limit": 50,
- "offset": 0,
- "schedules": [
- {
- "cloud_account_id": "string",
- "consecutive_failures": 0,
- "created_at": "string",
- "created_by": "string",
- "disabled_reason": "string",
- "enabled": true,
- "filter_regions": [
- "string"
], - "id": "string",
- "last_error": "string",
- "last_run_at": "string",
- "name": "string",
- "next_run_at": "string",
- "notify_slack_channel": "string",
- "notify_webhook_config_ids": [
- "string"
], - "org_id": "string",
- "schedule": "string",
- "updated_at": "string"
}
], - "total": 3
}Cron in 5-field format (minute hour dom month dow). cloud_account_id null = scan all accounts on each run. Plan limits enforce both count and minimum interval — 402 responses differ between "too many schedules" (plan_limit_exceeded) and "too frequent" (plan_schedule_frequency).
| id required | string Org ID (UUID) |
Schedule config
| cloud_account_id | string nil = all accounts |
| enabled | boolean default true |
| filter_regions | Array of strings |
| name required | string |
| notify_slack_channel | string |
| notify_webhook_config_ids | Array of strings |
| schedule required | string |
{- "cloud_account_id": "string",
- "enabled": true,
- "filter_regions": [
- "string"
], - "name": "string",
- "notify_slack_channel": "string",
- "notify_webhook_config_ids": [
- "string"
], - "schedule": "string"
}{- "cloud_account_id": "string",
- "consecutive_failures": 0,
- "created_at": "string",
- "created_by": "string",
- "disabled_reason": "string",
- "enabled": true,
- "filter_regions": [
- "string"
], - "id": "string",
- "last_error": "string",
- "last_run_at": "string",
- "name": "string",
- "next_run_at": "string",
- "notify_slack_channel": "string",
- "notify_webhook_config_ids": [
- "string"
], - "org_id": "string",
- "schedule": "string",
- "updated_at": "string"
}Soft delete — the schedule no longer fires and disappears from list/get, but the row persists for audit history. Not recoverable via API; contact support if a delete was unintentional.
| id required | string Org ID (UUID) |
| sid required | string Schedule ID (UUID) |
curl --request DELETE \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/schedules/%7Bsid%7D \ --header 'X-API-Key: REPLACE_KEY_VALUE'
Returns the full record including last_run_at, last_error, consecutive_failures, and next_run_at. disabled_reason indicates why the scheduler turned it off if enabled=false.
| id required | string Org ID (UUID) |
| sid required | string Schedule ID (UUID) |
curl --request GET \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/schedules/%7Bsid%7D \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "cloud_account_id": "string",
- "consecutive_failures": 0,
- "created_at": "string",
- "created_by": "string",
- "disabled_reason": "string",
- "enabled": true,
- "filter_regions": [
- "string"
], - "id": "string",
- "last_error": "string",
- "last_run_at": "string",
- "name": "string",
- "next_run_at": "string",
- "notify_slack_channel": "string",
- "notify_webhook_config_ids": [
- "string"
], - "org_id": "string",
- "schedule": "string",
- "updated_at": "string"
}PUT with partial semantics: only fields set in the body are updated. Schedule changes revalidate cron expression + minimum-interval plan limit and recompute next_run_at inside the same transaction (TOCTOU-free).
| id required | string Org ID (UUID) |
| sid required | string Schedule ID (UUID) |
Fields to update
| cloud_account_id | string |
| enabled | boolean |
| filter_regions | Array of strings |
| name | string |
| notify_slack_channel | string |
| notify_webhook_config_ids | Array of strings |
| schedule | string |
{- "cloud_account_id": "string",
- "enabled": true,
- "filter_regions": [
- "string"
], - "name": "string",
- "notify_slack_channel": "string",
- "notify_webhook_config_ids": [
- "string"
], - "schedule": "string"
}{- "cloud_account_id": "string",
- "consecutive_failures": 0,
- "created_at": "string",
- "created_by": "string",
- "disabled_reason": "string",
- "enabled": true,
- "filter_regions": [
- "string"
], - "id": "string",
- "last_error": "string",
- "last_run_at": "string",
- "name": "string",
- "next_run_at": "string",
- "notify_slack_channel": "string",
- "notify_webhook_config_ids": [
- "string"
], - "org_id": "string",
- "schedule": "string",
- "updated_at": "string"
}Fires the schedule immediately, independent of its cron. Uses the schedule's current cloud_account_id + filter_regions. Does not advance next_run_at; the next cron firing happens on schedule. Concurrent-scan plan limit applies.
| id required | string Org ID (UUID) |
| sid required | string Schedule ID (UUID) |
curl --request POST \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/schedules/%7Bsid%7D/run \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "count": 0,
- "scan_ids": [
- "string"
]
}Returns the full trace body. Non-admin callers get a projected view with prompts and raw response redacted; platform admins see the full trace. Status reflects storage state — pending (202) while the drift-worker is still uploading, ready/failed (200) for terminal states, expired (410) past the retention window.
| id required | string Org ID (UUID) |
| trace_id required | string Trace ID (starts with 'trc-') |
curl --request GET \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/traces/%7Btrace_id%7D \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "cached": true,
- "created_at": "string",
- "error": "string",
- "fallback": true,
- "id": "string",
- "input_tokens": 0,
- "inputs": [
- 0
], - "kind": "string",
- "latency_ms": 0,
- "model": "string",
- "org_id": "string",
- "output_tokens": 0,
- "parsed": [
- 0
], - "raw_response": "string",
- "scan_run_id": "string",
- "system_prompt": "string",
- "user_prompt": "string"
}Returns a Slack authorization URL the frontend redirects the browser to. The state parameter is HMAC-signed with the server's encryption key and bound to (org_id, user_id); the callback validates both. Requires the Slack feature flag on the org's plan.
| id required | string Org ID (UUID) |
curl --request POST \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/slack/install \ --header 'X-API-Key: REPLACE_KEY_VALUE'
installed=true means the org has a valid Slack token. available=false indicates the DriftWise deployment itself has no Slack app configured — the integration can't be used at all. Team metadata is only populated when installed=true.
| id required | string Org ID (UUID) |
curl --request GET \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/slack/status \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "available": true,
- "created_at": "string",
- "installed": true,
- "scopes": [
- "string"
], - "team_id": "string",
- "team_name": "string"
}Best-effort revokes the bot token at Slack, then deletes the local installation row. Revoke failures are logged but don't fail the endpoint — the local delete is the authoritative state.
| id required | string Org ID (UUID) |
curl --request DELETE \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/slack/uninstall \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "uninstalled": true
}Browser-facing callback. NOT called by API clients — Slack's OAuth redirect lands here. Happy path returns 302 to /#/settings?slack=connected or ?slack=denied. Rate-limited at 10/min per IP.
| state required | string HMAC-signed state produced by POST /slack/install |
| code | string OAuth authorization code (absent on user-denial redirects) |
| error | string OAuth error param (present on user denial) |
curl --request GET \ --url 'https://app.driftwise.ai/api/v2/slack/callback?state=SOME_STRING_VALUE&code=SOME_STRING_VALUE&error=SOME_STRING_VALUE'
{- "error": "resource not found",
- "request_id": "req_01HXABC..."
}Reads from Casdoor. Returns enabled=false (not 404) when no provider exists. scim_endpoint is populated only for plans with the SCIM feature (enterprise tier).
| id required | string Org ID (UUID) |
curl --request GET \ --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/sso-config \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "enabled": true,
- "idp_entity_id": "string",
- "idp_metadata_url": "string",
- "scim_endpoint": "string"
}OIDC-only. Writes the SAML provider metadata URL + entity ID to Casdoor. Audited with entity_id (safe public identifier); metadata_url is excluded from audit detail (may contain credentials/signed query strings).
| id required | string Org ID (UUID) |
IdP metadata URL + entity ID
| idp_entity_id | string |
| idp_metadata_url | string |
{- "idp_entity_id": "string",
- "idp_metadata_url": "string"
}{- "ok": true
}Paginated list of state sources. Config blobs are returned verbatim — sensitive fields like TFC tokens are not redacted (stored in DB as plaintext under the org isolation boundary).
| id required | string Org ID (UUID) |
| limit | integer page size (default 100, max 500) |
| offset | integer page offset |
curl --request GET \ --url 'https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/state-sources?limit=SOME_INTEGER_VALUE&offset=SOME_INTEGER_VALUE' \ --header 'X-API-Key: REPLACE_KEY_VALUE'
{- "limit": 100,
- "offset": 0,
- "state_sources": [
- {
- "cloud_account_id": "string",
- "config": [
- 0
], - "created_at": "string",
- "display_name": "string",
- "id": "string",
- "kind": "string",
- "last_fetched_at": "string",
- "latest_snapshot_id": "string",
- "org_id": "string",
- "workspaces": [
- "string"
]
}
], - "total": 2
}Config is kind-specific: gcs (bucket, object), s3 (bucket, key, region), azure_blob (account, container, blob), upload (manual uploads, no backing store), tfc (endpoint, workspace, token). URL-shaped keys in config are SSRF-validated before save. Returns 402 when the org's plan state-source limit is reached.
| id required | string Org ID (UUID) |
State source config
| cloud_account_id | string |
| config required | Array of integers |
| display_name required | string |
| kind required | string |
{- "cloud_account_id": "string",
- "config": [
- 0
], - "display_name": "string",
- "kind": "string"
}{- "cloud_account_id": "string",
- "config": [
- 0
], - "created_at": "string",
- "display_name": "string",
- "id": "string",
- "kind": "string",
- "last_fetched_at": "string",
- "latest_snapshot_id": "string",
- "org_id": "string",
- "workspaces": [
- "string"
]
}