Skip to main content

DriftWise API (2)

Download OpenAPI specification:Download

DriftWise multi-cloud infrastructure drift detection API. All endpoints live under /api/v2.

admin

List audit log entries

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.

Authorizations:
OIDCBearer
query Parameters
limit
integer

page size (default 50, max 200)

offset
integer

page offset

Responses

Request samples

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'

Response samples

Content type
application/json
[
  • {
    }
]

Verify the platform audit chain

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.

Authorizations:
OIDCBearer
query Parameters
expected_min_seq
integer

Operator-tracked watermark; broken if current head_seq is lower

Responses

Request samples

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'

Response samples

Content type
application/json
{
  • "checked": 0,
  • "head_hash": "string",
  • "head_seq": 0,
  • "status": "string"
}

List allowed email domains

Email domains whose users may authenticate. Users with domains not on this list are rejected at login with 403.

Authorizations:
OIDCBearer

Responses

Request samples

curl --request GET \
  --url https://app.driftwise.ai/api/v2/admin/domains \
  --header 'Authorization: REPLACE_KEY_VALUE'

Response samples

Content type
application/json
[
  • {
    }
]

Add an allowed email domain

Validation: must contain a dot, must not contain @, spaces, or a protocol prefix. Automatically lowercased.

Authorizations:
OIDCBearer
Request Body schema: application/json
required

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.

Responses

Request samples

Content type
application/json
{
  • "domain": "acme.com"
}

Response samples

Content type
application/json
{
  • "created_at": "string",
  • "created_by": "string",
  • "domain": "string",
  • "id": "string"
}

Remove an allowed email domain

Users with this domain will be rejected at next login. Existing sessions survive up to 30s (cache TTL) before invalidation.

Authorizations:
OIDCBearer
path Parameters
id
required
string

Domain allowlist entry ID (UUID)

Responses

Request samples

curl --request DELETE \
  --url https://app.driftwise.ai/api/v2/admin/domains/%7Bid%7D \
  --header 'Authorization: REPLACE_KEY_VALUE'

Report admin status (and bootstrap first admin)

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.

Authorizations:
OIDCBearerAPIKey

Responses

Request samples

curl --request GET \
  --url https://app.driftwise.ai/api/v2/admin/me \
  --header 'X-API-Key: REPLACE_KEY_VALUE'

Response samples

Content type
application/json
{
  • "is_platform_admin": true
}

Add a user to an org

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.

Authorizations:
OIDCBearer
Request Body schema: application/json
required

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

Responses

Request samples

Content type
application/json
{
  • "org_id": "b2c3d4e5-6789-01bc-defa-2345678901bc",
  • "role": "member",
  • "user_id": "a1b2c3d4-5678-90ab-cdef-1234567890ab"
}

Response samples

Content type
application/json
{
  • "acceptedAt": "string",
  • "createdAt": "string",
  • "expiresAt": "string",
  • "id": "string",
  • "invitedBy": "string",
  • "lastActiveAt": "string",
  • "orgID": "string",
  • "role": "string",
  • "updatedAt": "string",
  • "userID": "string"
}

Remove a membership

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.

Authorizations:
OIDCBearer
path Parameters
id
required
string

Membership ID (UUID)

Responses

Request samples

curl --request DELETE \
  --url https://app.driftwise.ai/api/v2/admin/memberships/%7Bid%7D \
  --header 'Authorization: REPLACE_KEY_VALUE'

Change a membership's role (admin)

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.

Authorizations:
OIDCBearer
path Parameters
id
required
string

Membership ID (UUID)

Request Body schema: application/json
required

New role

role
required
string
Enum: "owner" "admin" "member" "viewer"

Responses

Request samples

Content type
application/json
{
  • "role": "admin"
}

Response samples

Content type
application/json
{
  • "membership_id": "string",
  • "noop": true,
  • "org_id": "string",
  • "role": "string",
  • "user_id": "string"
}

List all organizations

Platform-admin-only paginated list of every org. Includes free, team, and enterprise plans. Ordered by created_at descending.

Authorizations:
OIDCBearer
query Parameters
limit
integer

page size (default 100, max 500)

offset
integer

page offset

Responses

Request samples

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'

Response samples

Content type
application/json
[
  • {
    }
]

Create an organization (admin)

Provision a new org on the Free plan. Does NOT make the caller an owner — use POST /admin/memberships afterward to assign ownership.

Authorizations:
OIDCBearer
Request Body schema: application/json
required

Org slug + display name

display_name
required
string
slug
required
string

Responses

Request samples

Content type
application/json
{
  • "display_name": "string",
  • "slug": "string"
}

Response samples

Content type
application/json
{
  • "created_at": "string",
  • "display_name": "string",
  • "features": [
    ],
  • "id": "string",
  • "plan": "string",
  • "slug": "string"
}

List users

Paginated list of every user known to the platform. Includes both admins and regular users; the is_platform_admin flag distinguishes.

Authorizations:
OIDCBearer
query Parameters
limit
integer

page size (default 100, max 500)

offset
integer

page offset

Responses

Request samples

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'

Response samples

Content type
application/json
[
  • {
    }
]

Delete a user (soft)

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.

Authorizations:
OIDCBearer
path Parameters
id
required
string

User ID (UUID)

Responses

Request samples

curl --request DELETE \
  --url https://app.driftwise.ai/api/v2/admin/users/%7Bid%7D \
  --header 'Authorization: REPLACE_KEY_VALUE'

List a user's org memberships

Returns the user's role in every org they belong to. Order unspecified.

Authorizations:
OIDCBearer
path Parameters
id
required
string

User ID (UUID)

Responses

Request samples

curl --request GET \
  --url https://app.driftwise.ai/api/v2/admin/users/%7Bid%7D/memberships \
  --header 'Authorization: REPLACE_KEY_VALUE'

Response samples

Content type
application/json
[
  • {
    }
]

Get WIF configuration for CI integration

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.

Authorizations:
OIDCBearer

Responses

Request samples

curl --request GET \
  --url https://app.driftwise.ai/api/v2/federation-info \
  --header 'Authorization: REPLACE_KEY_VALUE'

Response samples

Content type
application/json
{}

Create a new org (self-service)

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.

Authorizations:
OIDCBearer
Request Body schema: application/json
required

Org slug + display name

display_name
required
string
slug
required
string

Responses

Request samples

Content type
application/json
{
  • "display_name": "string",
  • "slug": "string"
}

Response samples

Content type
application/json
{
  • "created_at": "string",
  • "display_name": "string",
  • "features": [
    ],
  • "id": "string",
  • "plan": "string",
  • "slug": "string"
}

Get org metadata

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

Responses

Request samples

curl --request GET \
  --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D \
  --header 'X-API-Key: REPLACE_KEY_VALUE'

Response samples

Content type
application/json
{
  • "created_at": "string",
  • "display_name": "string",
  • "features": [
    ],
  • "id": "string",
  • "plan": "string",
  • "slug": "string"
}

Change a team member's role

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.

Authorizations:
OIDCBearer
path Parameters
id
required
string

Org ID (UUID)

membership_id
required
string

Membership ID (UUID) — must belong to the org in the URL

Request Body schema: application/json
required

New role

role
required
string
Enum: "owner" "admin" "member" "viewer"

Responses

Request samples

Content type
application/json
{
  • "role": "admin"
}

Response samples

Content type
application/json
{
  • "membership_id": "string",
  • "noop": true,
  • "org_id": "string",
  • "role": "string",
  • "user_id": "string"
}

audit

List audit log entries

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.

Authorizations:
OIDCBearer
query Parameters
limit
integer

page size (default 50, max 200)

offset
integer

page offset

Responses

Request samples

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'

Response samples

Content type
application/json
[
  • {
    }
]

Verify the platform audit chain

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.

Authorizations:
OIDCBearer
query Parameters
expected_min_seq
integer

Operator-tracked watermark; broken if current head_seq is lower

Responses

Request samples

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'

Response samples

Content type
application/json
{
  • "checked": 0,
  • "head_hash": "string",
  • "head_seq": 0,
  • "status": "string"
}

List Compliance Packs

Paginated list of the org's Compliance Pack rows in every status (pending, running, done, error). Any org member can list.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

query Parameters
limit
integer

page size (default 50, max 200)

offset
integer

page offset

Responses

Request samples

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'

Response samples

Content type
application/json
{
  • "items": [
    ],
  • "total": 0
}

Enqueue a Compliance Pack

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.

Authorizations:
OIDCBearer
path Parameters
id
required
string

Org ID (UUID)

Request Body schema: application/json
required

Period start + end (UTC)

period_end
string
period_start
string

Responses

Request samples

Content type
application/json
{
  • "period_end": "string",
  • "period_start": "string"
}

Response samples

Content type
application/json
{
  • "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"
}

Delete a Compliance Pack

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

Authorizations:
OIDCBearer
path Parameters
id
required
string

Org ID (UUID)

eid
required
string

Export ID (UUID)

Responses

Request samples

curl --request DELETE \
  --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/audit-exports/%7Beid%7D \
  --header 'Authorization: REPLACE_KEY_VALUE'

Get Compliance Pack status

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

eid
required
string

Export ID (UUID)

Responses

Request samples

curl --request GET \
  --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/audit-exports/%7Beid%7D \
  --header 'X-API-Key: REPLACE_KEY_VALUE'

Response samples

Content type
application/json
{
  • "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"
}

Download Compliance Pack ZIP

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

Authorizations:
OIDCBearer
path Parameters
id
required
string

Org ID (UUID)

eid
required
string

Export ID (UUID)

Responses

Request samples

curl --request GET \
  --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/audit-exports/%7Beid%7D/download \
  --header 'Authorization: REPLACE_KEY_VALUE'

Verify the org audit chain

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

query Parameters
expected_min_seq
integer

Caller-tracked watermark; broken if current head_seq is lower

Responses

Request samples

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'

Response samples

Content type
application/json
{
  • "checked": 0,
  • "head_hash": "string",
  • "head_seq": 0,
  • "status": "string"
}

policy

List built-in rules

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.

Authorizations:
OIDCBearerAPIKey
query Parameters
provider
string
Enum: "aws" "gcp" "azure"

Filter types by provider

Responses

Request samples

curl --request GET \
  --url 'https://app.driftwise.ai/api/v2/builtin-rules?provider=SOME_STRING_VALUE' \
  --header 'X-API-Key: REPLACE_KEY_VALUE'

Response samples

Content type
application/json
{
  • "noise_rules": [
    ],
  • "risk_encryption_types": [
    ],
  • "risk_iam_types": [
    ],
  • "risk_network_types": [
    ],
  • "risk_security_types": [
    ],
  • "risk_stateful_types": [
    ]
}

List custom rules

Returns both enabled and disabled rules. Optional rule_type narrows to "noise" or "risk". Not paginated — rule counts are bounded in practice.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

query Parameters
rule_type
string
Enum: "noise" "risk"

Filter by rule type

Responses

Request samples

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'

Response samples

Content type
application/json
{
  • "rules": [
    ]
}

Create a custom rule

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

Request Body schema: application/json
required

Rule definition

config
required
Array of integers
description
required
string
name
required
string
rule_type
required
string
Enum: "noise" "risk"

Responses

Request samples

Content type
application/json
{
  • "config": [
    ],
  • "description": "string",
  • "name": "string",
  • "rule_type": "noise"
}

Response samples

Content type
application/json
{
  • "config": [
    ],
  • "created_at": "string",
  • "created_by": "string",
  • "description": "string",
  • "enabled": true,
  • "id": "string",
  • "name": "string",
  • "rule_type": "string",
  • "updated_at": "string"
}

Delete a custom rule

Hard delete. To temporarily disable a rule without losing config, PATCH with enabled=false instead.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

rule_id
required
string

Custom rule ID (UUID)

Responses

Request samples

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'

Get a custom rule

Returns the rule including its config blob and enabled flag.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

rule_id
required
string

Custom rule ID (UUID)

Responses

Request samples

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'

Response samples

Content type
application/json
{
  • "config": [
    ],
  • "created_at": "string",
  • "created_by": "string",
  • "description": "string",
  • "enabled": true,
  • "id": "string",
  • "name": "string",
  • "rule_type": "string",
  • "updated_at": "string"
}

Toggle a custom rule

Flips the enabled flag. Preserves config so re-enabling later restores the rule exactly as configured.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

rule_id
required
string

Custom rule ID (UUID)

Request Body schema: application/json
required

Desired enabled state

enabled
boolean

Responses

Request samples

Content type
application/json
{
  • "enabled": true
}

Update a custom rule

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

rule_id
required
string

Custom rule ID (UUID)

Request Body schema: application/json
required

Updated fields

config
required
Array of integers
description
required
string
name
required
string

Responses

Request samples

Content type
application/json
{
  • "config": [
    ],
  • "description": "string",
  • "name": "string"
}

Response samples

Content type
application/json
{
  • "config": [
    ],
  • "created_at": "string",
  • "created_by": "string",
  • "description": "string",
  • "enabled": true,
  • "id": "string",
  • "name": "string",
  • "rule_type": "string",
  • "updated_at": "string"
}

List disabled built-in rules

Returns the org's exception list against the built-in rule catalog. Optional rule_type narrows to "noise" or "risk".

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

query Parameters
rule_type
string
Enum: "noise" "risk"

Filter by rule type

Responses

Request samples

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'

Response samples

Content type
application/json
{
  • "rules": [
    ]
}

Disable a built-in rule

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

Request Body schema: application/json
required

Which rule + why

builtin_rule_id
required
string
reason
string
rule_type
required
string
Enum: "noise" "risk"

Responses

Request samples

Content type
application/json
{
  • "builtin_rule_id": "string",
  • "reason": "string",
  • "rule_type": "noise"
}

Response samples

Content type
application/json
{
  • "builtin_rule_id": "string",
  • "created_at": "string",
  • "disabled_by": "string",
  • "id": "string",
  • "reason": "string",
  • "rule_type": "string"
}

Re-enable a built-in rule

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

disabled_rule_id
required
string

Disabled rule record ID (UUID)

Responses

Request samples

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'

List plan-noise patterns

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

query Parameters
repo_owner
string

Filter by repo owner

repo_name
string

Filter by repo name

Responses

Request samples

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'

Response samples

Content type
application/json
{
  • "patterns": [
    ],
  • "settings": {
    }
}

Generate fix recommendations

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

fingerprint
required
string

Pattern fingerprint (hash of resource+attribute+action)

Responses

Request samples

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'

Response samples

Content type
application/json
{
  • "confidence": "high",
  • "fixes": [
    ],
  • "trace_id": "trc-abc123"
}

Get plan-noise settings

Returns recurrence_threshold (minimum occurrences to flag) and window_days (lookback period). Defaults applied server-side when the org has never customized.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

Responses

Request samples

curl --request GET \
  --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/plan-noise/settings \
  --header 'X-API-Key: REPLACE_KEY_VALUE'

Response samples

Content type
application/json
{
  • "recurrence_threshold": 0,
  • "window_days": 0
}

Update plan-noise settings

Replaces recurrence_threshold and window_days. Takes effect on the next ListPlanNoise call — no re-aggregation job triggered.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

Request Body schema: application/json
required

New thresholds

recurrence_threshold
required
integer >= 1
window_days
required
integer >= 1

Responses

Request samples

Content type
application/json
{
  • "recurrence_threshold": 1,
  • "window_days": 1
}

Response samples

Content type
application/json
{
  • "recurrence_threshold": 0,
  • "window_days": 0
}

Suppress plan-noise fingerprints

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

Request Body schema: application/json
required

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

Responses

Request samples

Content type
application/json
{
  • "duration": "7d",
  • "fingerprints": [
    ],
  • "is_false_positive": true,
  • "reason": "string",
  • "resource_addresses": [
    ]
}

Response samples

Content type
application/json
{
  • "suppressions": [
    ]
}

List plan-noise suppressions

Returns active suppressions. Pass include_expired=true to include suppressions past their expiry (useful for audit). Not paginated.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

query Parameters
include_expired
boolean

Include already-expired suppressions

Responses

Request samples

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'

Response samples

Content type
application/json
{
  • "suppressions": [
    ]
}

Remove a plan-noise suppression

Restores visibility of the suppressed drift. Already-deleted suppressions return 404.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

suppression_id
required
string

Suppression ID (UUID)

Responses

Request samples

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'

Get risk policy

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

Responses

Request samples

curl --request GET \
  --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/policy \
  --header 'X-API-Key: REPLACE_KEY_VALUE'

Response samples

Content type
application/json
{
  • "rules": [
    ],
  • "updated_at": "string",
  • "version": 0
}

Replace risk policy

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

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

Request Body schema: application/json
required

Policy rules + version

Array of objects (github_com_driftwise_backend_internal_policy.PolicyRule)
updated_at
string
version
integer

Responses

Request samples

Content type
application/json
{
  • "rules": [
    ],
  • "updated_at": "string",
  • "version": 0
}

Response samples

Content type
application/json
{
  • "rules": [
    ],
  • "updated_at": "string",
  • "version": 0
}

Get Terraform Registry resource metadata

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.

Authorizations:
OIDCBearerAPIKey
query Parameters
provider
required
string
Enum: "aws" "gcp" "azure"

Cloud provider

resource_type
required
string

Terraform resource type (e.g. aws_s3_bucket)

Responses

Request samples

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'

Response samples

Content type
application/json
{
  • "docs_url": "string",
  • "fetched_at": "string",
  • "latest_version": "string",
  • "min_version": "string",
  • "provider": "string",
  • "resource_type": "string"
}

llm

List supported LLM providers

Returns the compiled-in LLM provider enums (anthropic, openai, bedrock, gemini, azure_openai). Response is static per server build; frontend caches indefinitely.

Authorizations:
OIDCBearerAPIKey

Responses

Request samples

curl --request GET \
  --url https://app.driftwise.ai/api/v2/llm-providers \
  --header 'X-API-Key: REPLACE_KEY_VALUE'

Response samples

Content type
application/json
{
  • "providers": [
    ]
}

Delete BYOK LLM configuration

Hard delete. Subsequent LLM calls fall back to the platform default + its quota gate (weekly/hourly). 404 if no row exists.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

Responses

Request samples

curl --request DELETE \
  --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/llm-config \
  --header 'X-API-Key: REPLACE_KEY_VALUE'

Get BYOK LLM configuration

Returns only metadata — provider name + timestamps. Credentials never exposed: the raw api_key / aws_secret_access_key / azure_api_key are write-only.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

Responses

Request samples

curl --request GET \
  --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/llm-config \
  --header 'X-API-Key: REPLACE_KEY_VALUE'

Response samples

Content type
application/json
{
  • "configured_at": "string",
  • "last_validated_at": "string",
  • "provider": "string"
}

Replace BYOK LLM configuration

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

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

Request Body schema: application/json
required

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

Responses

Request samples

Content type
application/json
{
  • "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"
}

Response samples

Content type
application/json
{
  • "configured_at": "string",
  • "last_validated_at": "string",
  • "provider": "string"
}

Get LLM usage + caps

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

Responses

Request samples

curl --request GET \
  --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/llm-usage \
  --header 'X-API-Key: REPLACE_KEY_VALUE'

Response samples

Content type
application/json
{
  • "byok_configured": true,
  • "hour_resets_at": "string",
  • "hourly_cap": 0,
  • "hourly_used": 0,
  • "week_resets_at": "string",
  • "weekly_cap": 0,
  • "weekly_used": 0
}

accounts

Ingest K8s operator resource report

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.

Authorizations:
APIKey
Request Body schema: application/json
required

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)

Responses

Request samples

Content type
application/json
{
  • "cluster_id": "string",
  • "custom_prompt": "string",
  • "generate_iac": true,
  • "iac_format": "string",
  • "namespace": "string",
  • "resources": [
    ]
}

Response samples

Content type
application/json
{
  • "report_id": "string",
  • "resource_count": 0
}

List cloud accounts

Paginated list of registered cloud accounts. Credentials are redacted — callers see only metadata (display name, provider, external account ID, credential type).

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

query Parameters
limit
integer

page size (default 100, max 500)

offset
integer

page offset

Responses

Request samples

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'

Response samples

Content type
application/json
{
  • "accounts": [
    ],
  • "limit": 100,
  • "offset": 0,
  • "total": 3
}

Register a cloud account

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

Request Body schema: application/json
required

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

Responses

Request samples

Content type
application/json
{
  • "credential_ref": "string",
  • "credential_type": "string",
  • "default_region": "string",
  • "display_name": "string",
  • "external_account_id": "string",
  • "provider": "string"
}

Response samples

Content type
application/json
{
  • "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"
}

List live resources

Paginated list of cloud resources discovered by recent scans. Optional account_id narrows to one cloud account.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

query Parameters
account_id
string

Filter by cloud account ID

limit
integer

page size (default 100, max 500)

offset
integer

page offset

Responses

Request samples

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'

Response samples

Content type
application/json
{
  • "limit": 0,
  • "offset": 0,
  • "resources": [
    ],
  • "total": 0
}

Enrich one resource

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

resource_id
required
string

Live resource ID (UUID)

Responses

Request samples

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'

Response samples

Content type
application/json
{
  • "enrichment_failure_reason": "string",
  • "enrichment_status": "string",
  • "iac_resource_id": "string",
  • "id": "string",
  • "last_seen_at": "string",
  • "name": "string",
  • "normalized_type": "string",
  • "properties": [
    ],
  • "provider": "string",
  • "provider_resource_id": "string",
  • "provider_type": "string",
  • "region": "string",
  • "status": "string"
}

List supported cloud providers

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.

Authorizations:
OIDCBearerAPIKey

Responses

Request samples

curl --request GET \
  --url https://app.driftwise.ai/api/v2/providers \
  --header 'X-API-Key: REPLACE_KEY_VALUE'

Response samples

Content type
application/json
{
  • "providers": [
    ]
}

drift

Analyze a Terraform plan

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

Request Body schema: application/json
required

Terraform plan JSON + optional CI metadata

object (internal_api.CIMetadataRequest)
plan_json
required
string

Responses

Request samples

Content type
application/json
{
  • "ci": {
    },
  • "plan_json": "string"
}

Response samples

Content type
application/json
{
  • "changes": [
    ],
  • "narrative": "string",
  • "plan_noise": {
    },
  • "risk_level": "string",
  • "scan_run": {
    },
  • "summary": [
    ]
}

Get posture summary

Per-account coverage percentage, risk level, and undeclared-resource count aggregated over the latest scan/drift data. Drives the dashboard's posture card.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

Responses

Request samples

curl --request GET \
  --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/posture \
  --header 'X-API-Key: REPLACE_KEY_VALUE'

Response samples

Content type
application/json
{
  • "accounts": [
    ],
  • "org_id": "string"
}

Get drift results for a scan

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

scan_id
required
string

Scan run ID (UUID)

Responses

Request samples

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'

Response samples

Content type
application/json
{
  • "changed_count": 0,
  • "coverage_pct": 0,
  • "created_at": "string",
  • "extra_count": 0,
  • "id": "string",
  • "items": [
    ],
  • "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
}

Compute drift for a scan (sync)

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

scan_id
required
string

Scan run ID (UUID)

Responses

Request samples

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'

Response samples

Content type
application/json
{
  • "changed_count": 0,
  • "coverage_pct": 0,
  • "created_at": "string",
  • "extra_count": 0,
  • "id": "string",
  • "items": [
    ],
  • "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
}

api-keys

List API keys

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

query Parameters
limit
integer

page size (default 100, max 500)

offset
integer

page offset

Responses

Request samples

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'

Response samples

Content type
application/json
{
  • "api_keys": [
    ],
  • "limit": 100,
  • "offset": 0,
  • "total": 2
}

Create an API key

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

Request Body schema: application/json
required

Key name + scopes

name
required
string
scopes
Array of strings

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "scopes": [
    ]
}

Response samples

Content type
application/json
{
  • "created_at": "string",
  • "id": "string",
  • "key_prefix": "string",
  • "name": "string",
  • "raw_key": "string",
  • "scopes": [
    ]
}

Revoke an API key

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.

Authorizations:
OIDCBearer
path Parameters
id
required
string

Org ID (UUID)

keyID
required
string

API key ID (UUID)

Responses

Request samples

curl --request DELETE \
  --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/api-keys/%7BkeyID%7D \
  --header 'Authorization: REPLACE_KEY_VALUE'

billing

Create Stripe checkout session

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.

Authorizations:
OIDCBearer
path Parameters
id
required
string

Org ID (UUID)

Request Body schema: application/json
required

Billing interval

interval
required
string
Enum: "month" "year"

Interval selects the Stripe price: "month" for monthly billing, "year" for annual. Anything else returns 400.

Responses

Request samples

Content type
application/json
{
  • "interval": "month"
}

Response samples

Content type
application/json

Create Stripe customer portal session

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

Authorizations:
OIDCBearer
path Parameters
id
required
string

Org ID (UUID)

Responses

Request samples

curl --request POST \
  --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/billing/portal \
  --header 'Authorization: REPLACE_KEY_VALUE'

Response samples

Content type
application/json

Get current billing usage

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

Responses

Request samples

curl --request GET \
  --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/billing/usage \
  --header 'X-API-Key: REPLACE_KEY_VALUE'

Response samples

Content type
application/json
{
  • "plan": "team",
  • "role": "owner",
  • "seats": {
    },
  • "subscription_status": "active"
}

webhooks

List GitHub installations

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

query Parameters
limit
integer

max installations to return (default 100, max 500)

Responses

Request samples

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'

Response samples

Content type
application/json
{
  • "github_installations": [
    ]
}

Link a GitHub App installation

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

Request Body schema: application/json
required

GitHub installation ID + account login

account_login
required
string
installation_id
required
integer

Responses

Request samples

Content type
application/json
{
  • "account_login": "string",
  • "installation_id": 0
}

Response samples

Content type
application/json
{
  • "account_login": "string",
  • "created_at": "string",
  • "id": "string",
  • "installation_id": 0,
  • "updated_at": "string"
}

Unlink a GitHub installation

Removes the DriftWise-side record. Does not uninstall the App from GitHub — the user must do that separately via GitHub's UI.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

install_id
required
string

Installation link ID (UUID)

Responses

Request samples

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'

List webhook configs

Paginated list. raw_secret is always empty — shown only once at creation. Optional provider filter narrows by GitLab/Atlantis.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

query Parameters
provider
string
Enum: "gitlab" "atlantis"

Filter by provider

limit
integer

page size (default 100, max 500)

offset
integer

page offset

Responses

Request samples

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'

Response samples

Content type
application/json
{
  • "limit": 100,
  • "offset": 0,
  • "total": 3,
  • "webhook_configs": [
    ]
}

Register a webhook config

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

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

Request Body schema: application/json
required

Webhook config

api_token
string
label
required
string
provider
required
string
provider_base_url
string
repo_path
string

Responses

Request samples

Content type
application/json
{
  • "api_token": "string",
  • "label": "string",
  • "provider": "string",
  • "provider_base_url": "string",
  • "repo_path": "string"
}

Response samples

Content type
application/json
{
  • "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"
}

Delete a webhook config

Removes the config and stops accepting webhooks signed with its secret. Already-deleted configs return 404.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

config_id
required
string

Webhook config ID (UUID)

Responses

Request samples

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'

Update a webhook config

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

config_id
required
string

Webhook config ID (UUID)

Request Body schema: application/json
required

Fields to update

api_token
string

non-empty to replace; empty string clears the token

enabled
boolean

Responses

Request samples

Content type
application/json
{
  • "api_token": "string",
  • "enabled": true
}

Response samples

Content type
application/json
{
  • "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"
}

scans

List scan runs

Paginated scan history, filtered by retention window (free: 24h, team+: unlimited). Optional account_id and scan_type narrow results.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

query Parameters
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

Responses

Request samples

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'

Response samples

Content type
application/json
{
  • "limit": 50,
  • "offset": 0,
  • "scans": [
    ],
  • "total": 42
}

Queue a new scan run

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

Request Body schema: application/json
required

Scan config: cloud account, region filters, CI metadata

object (internal_api.CIMetadataRequest)
cloud_account_id
required
string
filter_regions
Array of strings

Responses

Request samples

Content type
application/json
{
  • "ci": {
    },
  • "cloud_account_id": "string",
  • "filter_regions": [
    ]
}

Response samples

Content type
application/json
{
  • "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": [
    ],
  • "scan_errors": [
    ],
  • "scan_type": "string",
  • "started_at": "string",
  • "status": "string",
  • "status_kind": "string"
}

Get a scan run

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

scan_id
required
string

Scan run ID (UUID)

Responses

Request samples

curl --request GET \
  --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/scans/%7Bscan_id%7D \
  --header 'X-API-Key: REPLACE_KEY_VALUE'

Response samples

Content type
application/json
{
  • "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": [
    ],
  • "scan_errors": [
    ],
  • "scan_type": "string",
  • "started_at": "string",
  • "status": "string",
  • "status_kind": "string"
}

Get drift results for a scan

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

scan_id
required
string

Scan run ID (UUID)

Responses

Request samples

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'

Response samples

Content type
application/json
{
  • "changed_count": 0,
  • "coverage_pct": 0,
  • "created_at": "string",
  • "extra_count": 0,
  • "id": "string",
  • "items": [
    ],
  • "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
}

Compute drift for a scan (sync)

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

scan_id
required
string

Scan run ID (UUID)

Responses

Request samples

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'

Response samples

Content type
application/json
{
  • "changed_count": 0,
  • "coverage_pct": 0,
  • "created_at": "string",
  • "extra_count": 0,
  • "id": "string",
  • "items": [
    ],
  • "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
}

List LLM traces for a scan

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

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

scan_id
required
string

Scan run ID (UUID)

Responses

Request samples

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'

Response samples

Content type
application/json
[
  • {
    }
]

Queue bulk scans

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

Request Body schema: application/json
required

Account selection + region filters

account_ids
Array of strings

nil/empty = all accounts

filter_regions
Array of strings

Responses

Request samples

Content type
application/json
{
  • "account_ids": [
    ],
  • "filter_regions": [
    ]
}

Response samples

Content type
application/json
{
  • "count": 0,
  • "scan_ids": [
    ]
}

List scheduled scans

Paginated list. Disabled schedules (by user or auto-disable after too many consecutive failures) are included — check the enabled and disabled_reason fields.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

query Parameters
limit
integer

page size (default 50, max 200)

offset
integer

page offset

Responses

Request samples

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'

Response samples

Content type
application/json
{
  • "limit": 50,
  • "offset": 0,
  • "schedules": [
    ],
  • "total": 3
}

Create a scheduled scan

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

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

Request Body schema: application/json
required

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

Responses

Request samples

Content type
application/json
{
  • "cloud_account_id": "string",
  • "enabled": true,
  • "filter_regions": [
    ],
  • "name": "string",
  • "notify_slack_channel": "string",
  • "notify_webhook_config_ids": [
    ],
  • "schedule": "string"
}

Response samples

Content type
application/json
{
  • "cloud_account_id": "string",
  • "consecutive_failures": 0,
  • "created_at": "string",
  • "created_by": "string",
  • "disabled_reason": "string",
  • "enabled": true,
  • "filter_regions": [
    ],
  • "id": "string",
  • "last_error": "string",
  • "last_run_at": "string",
  • "name": "string",
  • "next_run_at": "string",
  • "notify_slack_channel": "string",
  • "notify_webhook_config_ids": [
    ],
  • "org_id": "string",
  • "schedule": "string",
  • "updated_at": "string"
}

Delete a scheduled scan

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

sid
required
string

Schedule ID (UUID)

Responses

Request samples

curl --request DELETE \
  --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/schedules/%7Bsid%7D \
  --header 'X-API-Key: REPLACE_KEY_VALUE'

Get a scheduled scan

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

sid
required
string

Schedule ID (UUID)

Responses

Request samples

curl --request GET \
  --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/schedules/%7Bsid%7D \
  --header 'X-API-Key: REPLACE_KEY_VALUE'

Response samples

Content type
application/json
{
  • "cloud_account_id": "string",
  • "consecutive_failures": 0,
  • "created_at": "string",
  • "created_by": "string",
  • "disabled_reason": "string",
  • "enabled": true,
  • "filter_regions": [
    ],
  • "id": "string",
  • "last_error": "string",
  • "last_run_at": "string",
  • "name": "string",
  • "next_run_at": "string",
  • "notify_slack_channel": "string",
  • "notify_webhook_config_ids": [
    ],
  • "org_id": "string",
  • "schedule": "string",
  • "updated_at": "string"
}

Update a scheduled scan

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

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

sid
required
string

Schedule ID (UUID)

Request Body schema: application/json
required

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

Responses

Request samples

Content type
application/json
{
  • "cloud_account_id": "string",
  • "enabled": true,
  • "filter_regions": [
    ],
  • "name": "string",
  • "notify_slack_channel": "string",
  • "notify_webhook_config_ids": [
    ],
  • "schedule": "string"
}

Response samples

Content type
application/json
{
  • "cloud_account_id": "string",
  • "consecutive_failures": 0,
  • "created_at": "string",
  • "created_by": "string",
  • "disabled_reason": "string",
  • "enabled": true,
  • "filter_regions": [
    ],
  • "id": "string",
  • "last_error": "string",
  • "last_run_at": "string",
  • "name": "string",
  • "next_run_at": "string",
  • "notify_slack_channel": "string",
  • "notify_webhook_config_ids": [
    ],
  • "org_id": "string",
  • "schedule": "string",
  • "updated_at": "string"
}

Run a scheduled scan now

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

sid
required
string

Schedule ID (UUID)

Responses

Request samples

curl --request POST \
  --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/schedules/%7Bsid%7D/run \
  --header 'X-API-Key: REPLACE_KEY_VALUE'

Response samples

Content type
application/json
{
  • "count": 0,
  • "scan_ids": [
    ]
}

Get an LLM trace

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

trace_id
required
string

Trace ID (starts with 'trc-')

Responses

Request samples

curl --request GET \
  --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/traces/%7Btrace_id%7D \
  --header 'X-API-Key: REPLACE_KEY_VALUE'

Response samples

Content type
application/json
{
  • "cached": true,
  • "created_at": "string",
  • "error": "string",
  • "fallback": true,
  • "id": "string",
  • "input_tokens": 0,
  • "inputs": [
    ],
  • "kind": "string",
  • "latency_ms": 0,
  • "model": "string",
  • "org_id": "string",
  • "output_tokens": 0,
  • "parsed": [
    ],
  • "raw_response": "string",
  • "scan_run_id": "string",
  • "system_prompt": "string",
  • "user_prompt": "string"
}

slack

Start Slack OAuth flow

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

Responses

Request samples

curl --request POST \
  --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/slack/install \
  --header 'X-API-Key: REPLACE_KEY_VALUE'

Response samples

Content type
application/json

Get Slack installation status

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

Responses

Request samples

curl --request GET \
  --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/slack/status \
  --header 'X-API-Key: REPLACE_KEY_VALUE'

Response samples

Content type
application/json
{
  • "available": true,
  • "created_at": "string",
  • "installed": true,
  • "scopes": [
    ],
  • "team_id": "string",
  • "team_name": "string"
}

Uninstall Slack integration

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

Responses

Request samples

curl --request DELETE \
  --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/slack/uninstall \
  --header 'X-API-Key: REPLACE_KEY_VALUE'

Response samples

Content type
application/json
{
  • "uninstalled": true
}

Slack OAuth callback (redirect)

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.

query Parameters
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)

Responses

Request samples

curl --request GET \
  --url 'https://app.driftwise.ai/api/v2/slack/callback?state=SOME_STRING_VALUE&code=SOME_STRING_VALUE&error=SOME_STRING_VALUE'

Response samples

Content type
application/json
{
  • "error": "resource not found",
  • "request_id": "req_01HXABC..."
}

sso

Get SSO (SAML) configuration

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

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

Responses

Request samples

curl --request GET \
  --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/sso-config \
  --header 'X-API-Key: REPLACE_KEY_VALUE'

Response samples

Content type
application/json
{
  • "enabled": true,
  • "idp_entity_id": "string",
  • "idp_metadata_url": "string",
  • "scim_endpoint": "string"
}

Update SSO (SAML) configuration

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

Authorizations:
OIDCBearer
path Parameters
id
required
string

Org ID (UUID)

Request Body schema: application/json
required

IdP metadata URL + entity ID

idp_entity_id
string
idp_metadata_url
string

Responses

Request samples

Content type
application/json
{
  • "idp_entity_id": "string",
  • "idp_metadata_url": "string"
}

Response samples

Content type
application/json
{
  • "ok": true
}

state-sources

List Terraform state sources

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

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

query Parameters
limit
integer

page size (default 100, max 500)

offset
integer

page offset

Responses

Request samples

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'

Response samples

Content type
application/json
{
  • "limit": 100,
  • "offset": 0,
  • "state_sources": [
    ],
  • "total": 2
}

Register a Terraform state source

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.

Authorizations:
OIDCBearerAPIKey
path Parameters
id
required
string

Org ID (UUID)

Request Body schema: application/json
required

State source config

cloud_account_id
string
config
required
Array of integers
display_name
required
string
kind
required
string

Responses

Request samples

Content type
application/json
{
  • "cloud_account_id": "string",
  • "config": [
    ],
  • "display_name": "string",
  • "kind": "string"
}

Response samples

Content type
application/json
{
  • "cloud_account_id": "string",
  • "config": [
    ],
  • "created_at": "string",
  • "display_name": "string",
  • "id": "string",
  • "kind": "string",
  • "last_fetched_at": "string",
  • "latest_snapshot_id": "string",
  • "org_id": "string",
  • "workspaces": [
    ]
}