basePath: /api/v2
definitions:
  github_com_driftwise_backend_internal_cloud.Category:
    enum:
      - compute
      - storage
      - database
      - network
      - iam
      - serverless
      - messaging
      - other
    type: string
    x-enum-varnames:
      - CategoryCompute
      - CategoryStorage
      - CategoryDatabase
      - CategoryNetwork
      - CategoryIAM
      - CategoryServerless
      - CategoryMessaging
      - CategoryOther
  github_com_driftwise_backend_internal_cloud.CredentialField:
    properties:
      credential_types:
        description: |-
          CredentialTypes limits this field to specific credential types.
          When empty, the field applies to all credential types.
        items:
          type: string
        type: array
      key:
        type: string
      label:
        type: string
      options:
        description: Options is populated when Type == "select".
        items:
          type: string
        type: array
      required:
        type: boolean
      secret:
        type: boolean
      type:
        description: Type is one of "text", "password", "select".
        type: string
    type: object
  github_com_driftwise_backend_internal_cloud.CredentialType:
    properties:
      label:
        type: string
      value:
        type: string
    type: object
  github_com_driftwise_backend_internal_cloud.EnrichmentFailureReason:
    enum:
      - unsupported_identifier
      - get_resource_failed
      - scrub_failed
      - credential_decrypt_failed
      - credential_parse_failed
      - enricher_call_failed
      - marshal_failed
    type: string
    x-enum-comments:
      ReasonCredentialDecryptFailed: '#nosec G101 -- enum value, not a credential'
      ReasonCredentialParseFailed: '#nosec G101 -- enum value, not a credential'
    x-enum-descriptions:
      - ''
      - ''
      - ''
      - '#nosec G101 -- enum value, not a credential'
      - '#nosec G101 -- enum value, not a credential'
      - ''
      - ''
    x-enum-varnames:
      - ReasonUnsupportedIdentifier
      - ReasonGetResourceFailed
      - ReasonScrubFailed
      - ReasonCredentialDecryptFailed
      - ReasonCredentialParseFailed
      - ReasonEnricherCallFailed
      - ReasonMarshalFailed
  github_com_driftwise_backend_internal_cloud.NormalizedType:
    enum:
      - k8s/deployment
      - k8s/statefulset
      - k8s/daemonset
      - k8s/service
      - k8s/ingress
      - k8s/configmap
      - k8s/secret
      - k8s/network_policy
      - k8s/service_account
      - k8s/pvc
      - k8s/hpa
      - k8s/namespace
    type: string
    x-enum-varnames:
      - TypeK8sDeployment
      - TypeK8sStatefulSet
      - TypeK8sDaemonSet
      - TypeK8sService
      - TypeK8sIngress
      - TypeK8sConfigMap
      - TypeK8sSecret
      - TypeK8sNetworkPolicy
      - TypeK8sServiceAccount
      - TypeK8sPVC
      - TypeK8sHPA
      - TypeK8sNamespace
  github_com_driftwise_backend_internal_cloud.ProviderDescriptor:
    properties:
      categories:
        items:
          $ref: '#/definitions/github_com_driftwise_backend_internal_cloud.Category'
        type: array
      credential_fields:
        items:
          $ref: >-
            #/definitions/github_com_driftwise_backend_internal_cloud.CredentialField
        type: array
      credential_types:
        items:
          $ref: >-
            #/definitions/github_com_driftwise_backend_internal_cloud.CredentialType
        type: array
      label:
        type: string
      name:
        type: string
    type: object
  github_com_driftwise_backend_internal_cloud.Relationship:
    properties:
      targetID:
        type: string
      type:
        description: '"depends_on", "contained_in"'
        type: string
    type: object
  github_com_driftwise_backend_internal_cloud.Resource:
    properties:
      enrichmentFailure:
        allOf:
          - $ref: >-
              #/definitions/github_com_driftwise_backend_internal_cloud.EnrichmentFailureReason
        description: |-
          EnrichmentFailure, when non-empty, signals that the discoverer knows
          this row cannot be usefully compared to IaC state. The scan worker
          persists the resource with enrichment_status='failed' and the given
          reason, so the UI surfaces "cannot compare" instead of declaring a
          drift-free match against empty Properties. Used today by GCP/Azure
          discoverers when redact.Scrub fails — fail-closed for secret leakage
          plus fail-loud for drift observability.
      id:
        type: string
      name:
        type: string
      normalizedType:
        $ref: >-
          #/definitions/github_com_driftwise_backend_internal_cloud.NormalizedType
      properties:
        additionalProperties:
          items:
            type: integer
          type: array
        type: object
      provider:
        type: string
      providerType:
        description: e.g. "AWS::EC2::Instance"
        type: string
      region:
        type: string
      relationships:
        items:
          $ref: >-
            #/definitions/github_com_driftwise_backend_internal_cloud.Relationship
        type: array
      tags:
        additionalProperties:
          type: string
        type: object
    type: object
  github_com_driftwise_backend_internal_llm.CredentialField:
    properties:
      key:
        type: string
      label:
        type: string
      required:
        type: boolean
      secret:
        type: boolean
      type:
        description: Type is one of "text", "password", "select".
        type: string
    type: object
  github_com_driftwise_backend_internal_llm.ProviderDescriptor:
    properties:
      categories:
        items:
          type: string
        type: array
      credential_fields:
        items:
          $ref: >-
            #/definitions/github_com_driftwise_backend_internal_llm.CredentialField
        type: array
      label:
        type: string
      name:
        type: string
    type: object
  github_com_driftwise_backend_internal_policy.Policy:
    properties:
      rules:
        items:
          $ref: >-
            #/definitions/github_com_driftwise_backend_internal_policy.PolicyRule
        type: array
      updated_at:
        type: string
      version:
        type: integer
    type: object
  github_com_driftwise_backend_internal_policy.PolicyRule:
    properties:
      flag_pattern:
        description: glob match on flag IDs, e.g. "public-*"
        type: string
      id:
        type: string
      reason:
        description: sanitized, ≤200 chars
        type: string
      resource_pattern:
        description: glob match on resource type, e.g. "aws_lb*"
        type: string
      severity:
        description: '"critical"|"high"|"medium"|"low"|"ignore"'
        type: string
    type: object
  github_com_driftwise_backend_internal_repo.AdminMembershipRow:
    properties:
      accepted_at:
        type: string
      id:
        type: string
      org_id:
        type: string
      org_slug:
        type: string
      role:
        type: string
    type: object
  github_com_driftwise_backend_internal_repo.AdminOrgRow:
    properties:
      created_at:
        type: string
      display_name:
        type: string
      id:
        type: string
      member_count:
        type: integer
      plan:
        type: string
      slug:
        type: string
    type: object
  github_com_driftwise_backend_internal_repo.AllowedDomain:
    properties:
      created_at:
        type: string
      created_by:
        type: string
      domain:
        type: string
      id:
        type: string
    type: object
  github_com_driftwise_backend_internal_repo.AuditEntry:
    properties:
      action:
        type: string
      actor_email:
        type: string
      created_at:
        type: string
      detail:
        items:
          type: integer
        type: array
      id:
        type: string
      org_id:
        type: string
    type: object
  github_com_driftwise_backend_internal_repo.OrgMembership:
    properties:
      acceptedAt:
        type: string
      createdAt:
        type: string
      expiresAt:
        type: string
      id:
        type: string
      invitedBy:
        type: string
      lastActiveAt:
        type: string
      orgID:
        type: string
      role:
        type: string
      updatedAt:
        type: string
      userID:
        type: string
    type: object
  github_com_driftwise_backend_internal_repo.TokenStatus:
    enum:
      - active
      - expiring_soon
      - expired
      - needs_rotation
    type: string
    x-enum-varnames:
      - TokenStatusActive
      - TokenStatusExpiringSoon
      - TokenStatusExpired
      - TokenStatusNeedsRotation
  github_com_driftwise_backend_internal_tfregistry.ResourceMeta:
    properties:
      docs_url:
        type: string
      fetched_at:
        type: string
      latest_version:
        type: string
      min_version:
        type: string
      provider:
        type: string
      resource_type:
        type: string
    type: object
  github_com_driftwise_backend_internal_trace.LLMTrace:
    properties:
      cached:
        type: boolean
      created_at:
        type: string
      error:
        type: string
      fallback:
        type: boolean
      id:
        type: string
      input_tokens:
        type: integer
      inputs:
        items:
          type: integer
        type: array
      kind:
        type: string
      latency_ms:
        type: integer
      model:
        type: string
      org_id:
        type: string
      output_tokens:
        type: integer
      parsed:
        items:
          type: integer
        type: array
      raw_response:
        type: string
      scan_run_id:
        type: string
      system_prompt:
        type: string
      user_prompt:
        type: string
    type: object
  github_com_driftwise_backend_internal_trace.TraceSummary:
    properties:
      cached:
        type: boolean
      created_at:
        type: string
      fallback:
        type: boolean
      id:
        type: string
      input_tokens:
        type: integer
      kind:
        type: string
      latency_ms:
        type: integer
      model:
        type: string
      output_tokens:
        type: integer
    type: object
  internal_api.APIKeyListResponse:
    properties:
      api_keys:
        items:
          $ref: '#/definitions/internal_api.APIKeyResponse'
        type: array
      limit:
        example: 100
        type: integer
      offset:
        example: 0
        type: integer
      total:
        example: 2
        type: integer
    type: object
  internal_api.APIKeyResponse:
    properties:
      created_at:
        type: string
      id:
        type: string
      key_prefix:
        type: string
      name:
        type: string
      raw_key:
        description: RawKey is only populated on creation. Never stored server-side.
        type: string
      scopes:
        items:
          type: string
        type: array
    type: object
  internal_api.AccountPosture:
    properties:
      account_name:
        type: string
      cloud_account_id:
        type: string
      coverage_pct:
        type: number
      provider:
        type: string
      risk_level:
        type: string
      total_live:
        type: integer
      undeclared_count:
        type: integer
    type: object
  internal_api.AnalyzePlanRequest:
    properties:
      ci:
        $ref: '#/definitions/internal_api.CIMetadataRequest'
      plan_json:
        type: string
    required:
      - plan_json
    type: object
  internal_api.AnalyzePlanResponse:
    properties:
      changes:
        items:
          type: integer
        type: array
      narrative:
        type: string
      plan_noise:
        $ref: '#/definitions/internal_api.PlanNoiseResult'
      risk_level:
        type: string
      scan_run:
        $ref: '#/definitions/internal_api.ScanRunResponse'
      summary:
        items:
          type: integer
        type: array
    type: object
  internal_api.AuditExportDTO:
    properties:
      artifact_bytes:
        type: integer
      artifact_sha256:
        type: string
      created_at:
        type: string
      error_message:
        type: string
      expires_at:
        type: string
      finished_at:
        type: string
      id:
        type: string
      period_end:
        type: string
      period_start:
        type: string
      requested_by:
        type: string
      retry_count:
        type: integer
      started_at:
        type: string
      status:
        type: string
    type: object
  internal_api.BuiltinRulesResponse:
    properties:
      noise_rules:
        items:
          $ref: '#/definitions/internal_api.MatchedRuleDTO'
        type: array
      risk_encryption_types:
        items:
          type: string
        type: array
      risk_iam_types:
        items:
          type: string
        type: array
      risk_network_types:
        items:
          type: string
        type: array
      risk_security_types:
        items:
          type: string
        type: array
      risk_stateful_types:
        items:
          type: string
        type: array
    type: object
  internal_api.BulkScanRequest:
    properties:
      account_ids:
        description: nil/empty = all accounts
        items:
          type: string
        type: array
      filter_regions:
        items:
          type: string
        type: array
    type: object
  internal_api.BulkScanResponse:
    properties:
      count:
        type: integer
      scan_ids:
        items:
          type: string
        type: array
    type: object
  internal_api.CIMetadataRequest:
    properties:
      branch:
        type: string
      commit_sha:
        type: string
      pr_number:
        type: integer
      provider:
        type: string
      repo_name:
        type: string
      repo_owner:
        type: string
      repo_url:
        type: string
    type: object
  internal_api.CloudAccountListResponse:
    properties:
      accounts:
        items:
          $ref: '#/definitions/internal_api.CloudAccountResponse'
        type: array
      limit:
        example: 100
        type: integer
      offset:
        example: 0
        type: integer
      total:
        example: 3
        type: integer
    type: object
  internal_api.CloudAccountResponse:
    properties:
      created_at:
        type: string
      credential_type:
        type: string
      default_region:
        type: string
      display_name:
        type: string
      external_account_id:
        type: string
      id:
        type: string
      last_validated_at:
        type: string
      org_id:
        type: string
      provider:
        type: string
    type: object
  internal_api.CreateAPIKeyRequest:
    properties:
      name:
        type: string
      scopes:
        items:
          type: string
        type: array
    required:
      - name
    type: object
  internal_api.CreateCloudAccountRequest:
    properties:
      credential_ref:
        type: string
      credential_type:
        type: string
      default_region:
        type: string
      display_name:
        type: string
      external_account_id:
        type: string
      provider:
        type: string
    required:
      - credential_ref
      - credential_type
      - display_name
      - external_account_id
      - provider
    type: object
  internal_api.CreateCustomRuleRequest:
    properties:
      config:
        items:
          type: integer
        type: array
      description:
        type: string
      name:
        type: string
      rule_type:
        enum:
          - noise
          - risk
        type: string
    required:
      - config
      - description
      - name
      - rule_type
    type: object
  internal_api.CreateGitHubInstallationRequest:
    properties:
      account_login:
        type: string
      installation_id:
        type: integer
    required:
      - account_login
      - installation_id
    type: object
  internal_api.CreateOrgRequest:
    properties:
      display_name:
        type: string
      slug:
        type: string
    required:
      - display_name
      - slug
    type: object
  internal_api.CreateScanRequest:
    properties:
      ci:
        $ref: '#/definitions/internal_api.CIMetadataRequest'
      cloud_account_id:
        type: string
      filter_regions:
        items:
          type: string
        type: array
    required:
      - cloud_account_id
    type: object
  internal_api.CreateScheduledScanRequest:
    properties:
      cloud_account_id:
        description: nil = all accounts
        type: string
      enabled:
        description: default true
        type: boolean
      filter_regions:
        items:
          type: string
        type: array
      name:
        type: string
      notify_slack_channel:
        type: string
      notify_webhook_config_ids:
        items:
          type: string
        type: array
      schedule:
        type: string
    required:
      - name
      - schedule
    type: object
  internal_api.CreateStateSourceRequest:
    properties:
      cloud_account_id:
        type: string
      config:
        items:
          type: integer
        type: array
      display_name:
        type: string
      kind:
        type: string
    required:
      - config
      - display_name
      - kind
    type: object
  internal_api.CreateWebhookConfigRequest:
    properties:
      api_token:
        type: string
      label:
        type: string
      provider:
        type: string
      provider_base_url:
        type: string
      repo_path:
        type: string
    required:
      - label
      - provider
    type: object
  internal_api.CustomRuleDTO:
    properties:
      config:
        items:
          type: integer
        type: array
      created_at:
        type: string
      created_by:
        type: string
      description:
        type: string
      enabled:
        type: boolean
      id:
        type: string
      name:
        type: string
      rule_type:
        type: string
      updated_at:
        type: string
    type: object
  internal_api.CustomRuleListResponse:
    properties:
      rules:
        items:
          $ref: '#/definitions/internal_api.CustomRuleDTO'
        type: array
    type: object
  internal_api.DisableBuiltinRuleRequest:
    properties:
      builtin_rule_id:
        type: string
      reason:
        type: string
      rule_type:
        enum:
          - noise
          - risk
        type: string
    required:
      - builtin_rule_id
      - rule_type
    type: object
  internal_api.DisabledBuiltinRuleDTO:
    properties:
      builtin_rule_id:
        type: string
      created_at:
        type: string
      disabled_by:
        type: string
      id:
        type: string
      reason:
        type: string
      rule_type:
        type: string
    type: object
  internal_api.DisabledBuiltinRuleListResponse:
    properties:
      rules:
        items:
          $ref: '#/definitions/internal_api.DisabledBuiltinRuleDTO'
        type: array
    type: object
  internal_api.DriftItemResponse:
    properties:
      attribute_diffs:
        items:
          type: integer
        type: array
      drift_type:
        type: string
      iac_resource_id:
        type: string
      id:
        type: string
      live_resource_id:
        type: string
      provider:
        type: string
      region:
        type: string
      resolution:
        type: string
      resource_name:
        type: string
      resource_type:
        type: string
    type: object
  internal_api.DriftSnapshotResponse:
    properties:
      changed_count:
        type: integer
      coverage_pct:
        type: number
      created_at:
        type: string
      extra_count:
        type: integer
      id:
        type: string
      items:
        items:
          $ref: '#/definitions/internal_api.DriftItemResponse'
        type: array
      matched_count:
        type: integer
      missing_count:
        type: integer
      narrative:
        type: string
      narrative_status:
        type: string
      parse_failure_count:
        description: |-
          ParseFailureCount is the number of resources whose IaC attributes or
          live properties could not be parsed as JSON. These resources are NOT
          drift — we couldn't compare them — but they're surfaced so the UI
          can render a warning badge. Non-zero values mean the scan is partial
          and operator attention is needed.
        type: integer
      risk_level:
        type: string
      scan_run_id:
        type: string
      total_iac:
        type: integer
      total_live:
        type: integer
    type: object
  internal_api.EnrichedResourceResponse:
    properties:
      enrichment_failure_reason:
        type: string
      enrichment_status:
        description: |-
          EnrichmentStatus is one of 'none', 'enriched', 'failed', 'n/a' —
          exposed on list responses so the UI can decide whether to offer
          a "Fetch details" button without an extra single-resource GET.
        type: string
      iac_resource_id:
        type: string
      id:
        type: string
      last_seen_at:
        type: string
      name:
        type: string
      normalized_type:
        type: string
      properties:
        items:
          type: integer
        type: array
      provider:
        type: string
      provider_resource_id:
        type: string
      provider_type:
        type: string
      region:
        type: string
      status:
        type: string
    type: object
  internal_api.ErrorResponse:
    properties:
      error:
        example: resource not found
        type: string
      request_id:
        example: req_01HXABC...
        type: string
    type: object
  internal_api.FixTierDTO:
    properties:
      cons:
        items:
          type: string
        type: array
      detail:
        type: string
      label:
        type: string
      pros:
        items:
          type: string
        type: array
      summary:
        type: string
    type: object
  internal_api.GenerateFixResponse:
    properties:
      confidence:
        example: high
        type: string
      fixes:
        items:
          $ref: '#/definitions/internal_api.FixTierDTO'
        type: array
      trace_id:
        example: trc-abc123
        type: string
    type: object
  internal_api.GitHubInstallationListResponse:
    properties:
      github_installations:
        items:
          $ref: '#/definitions/internal_api.GitHubInstallationResponse'
        type: array
    type: object
  internal_api.GitHubInstallationResponse:
    properties:
      account_login:
        type: string
      created_at:
        type: string
      id:
        type: string
      installation_id:
        type: integer
      updated_at:
        type: string
    type: object
  internal_api.LiveResourceResponse:
    properties:
      enrichment_status:
        description: |-
          EnrichmentStatus is one of 'none', 'enriched', 'failed', 'n/a' —
          exposed on list responses so the UI can decide whether to offer
          a "Fetch details" button without an extra single-resource GET.
        type: string
      iac_resource_id:
        type: string
      id:
        type: string
      last_seen_at:
        type: string
      name:
        type: string
      normalized_type:
        type: string
      provider:
        type: string
      provider_resource_id:
        type: string
      provider_type:
        type: string
      region:
        type: string
      status:
        type: string
    type: object
  internal_api.MatchedRuleDTO:
    properties:
      category:
        type: string
      description:
        type: string
      fixed_in_version:
        type: string
      fixes:
        items:
          $ref: '#/definitions/internal_api.FixTierDTO'
        type: array
      id:
        type: string
      provider:
        type: string
      severity:
        type: string
      title:
        type: string
    type: object
  internal_api.OperatorReportRequest:
    properties:
      cluster_id:
        type: string
      custom_prompt:
        type: string
      generate_iac:
        type: boolean
      iac_format:
        type: string
      namespace:
        type: string
      resources:
        items:
          $ref: '#/definitions/github_com_driftwise_backend_internal_cloud.Resource'
        type: array
    required:
      - cluster_id
      - namespace
      - resources
    type: object
  internal_api.OperatorReportResponse:
    properties:
      report_id:
        type: string
      resource_count:
        type: integer
    type: object
  internal_api.OrgResponse:
    properties:
      created_at:
        type: string
      display_name:
        type: string
      features:
        description: |-
          Features is the list of feature flags active for this org per its
          plan_limits row. Frontends gate UI on features.includes(flag)
          rather than on Plan name — a custom Enterprise plan may ship
          without "audit_compliance" even though the plan string is
          "enterprise", and a future plan rename should not ripple into
          the UI. Always non-nil; empty slice for free plans.
        items:
          type: string
        type: array
      id:
        type: string
      plan:
        type: string
      slug:
        type: string
    type: object
  internal_api.PaymentRequiredResponse:
    properties:
      code:
        example: plan_limit_exceeded
        type: string
      error:
        description: mirrors Code; legacy
        example: plan_limit_exceeded
        type: string
      limit:
        example: 10
        type: integer
      message:
        example: Seat limit reached for your plan.
        type: string
      plan:
        example: free
        type: string
      required_plan:
        example: team
        type: string
      resource:
        description: |-
          The following are examples of code-specific fields hoisted from
          Details for plan_limit_exceeded. Other codes may emit different
          fields; additionalProperties: true on the schema lets clients
          tolerate new fields without breaking.
        example: seats
        type: string
      used:
        example: 10
        type: integer
    type: object
  internal_api.PlanNoiseListResponse:
    properties:
      patterns:
        items:
          $ref: '#/definitions/internal_api.PlanNoisePatternDTO'
        type: array
      settings:
        $ref: '#/definitions/internal_api.PlanNoiseSettingsDTO'
    type: object
  internal_api.PlanNoisePatternDTO:
    properties:
      action:
        type: string
      attribute:
        type: string
      classification:
        type: string
      fingerprint:
        type: string
      first_seen:
        type: string
      last_seen:
        type: string
      matched_rule:
        $ref: '#/definitions/internal_api.MatchedRuleDTO'
      occurrence_count:
        type: integer
      repo_name:
        type: string
      repo_owner:
        type: string
      resource_address:
        type: string
      resource_type:
        type: string
    type: object
  internal_api.PlanNoiseResult:
    properties:
      known_patterns:
        type: integer
      novel_count:
        type: integer
      recurring_count:
        type: integer
      signals:
        items:
          $ref: '#/definitions/internal_api.PlanNoiseSignalDTO'
        type: array
    type: object
  internal_api.PlanNoiseSettingsDTO:
    properties:
      recurrence_threshold:
        type: integer
      window_days:
        type: integer
    type: object
  internal_api.PlanNoiseSignalDTO:
    properties:
      action:
        type: string
      attribute:
        type: string
      classification:
        type: string
      matched_rule:
        $ref: '#/definitions/internal_api.MatchedRuleDTO'
      occurrence_count:
        type: integer
      resource_address:
        type: string
    type: object
  internal_api.PostureResponse:
    properties:
      accounts:
        items:
          $ref: '#/definitions/internal_api.AccountPosture'
        type: array
      org_id:
        type: string
    type: object
  internal_api.ResourceListResponse:
    properties:
      limit:
        type: integer
      offset:
        type: integer
      resources:
        items:
          $ref: '#/definitions/internal_api.LiveResourceResponse'
        type: array
      total:
        type: integer
    type: object
  internal_api.ScanErrorDTO:
    properties:
      code:
        type: string
      error:
        type: string
      region:
        type: string
      resource_type:
        type: string
    type: object
  internal_api.ScanListResponse:
    properties:
      limit:
        example: 50
        type: integer
      offset:
        example: 0
        type: integer
      scans:
        items:
          $ref: '#/definitions/internal_api.ScanRunResponse'
        type: array
      total:
        example: 42
        type: integer
    type: object
  internal_api.ScanRunResponse:
    properties:
      branch:
        type: string
      changed_count:
        type: integer
      cloud_account_id:
        type: string
      commit_sha:
        type: string
      created_at:
        type: string
      drift_status:
        type: string
      finished_at:
        type: string
      id:
        type: string
      missing_count:
        type: integer
      new_count:
        type: integer
      org_id:
        type: string
      pr_number:
        type: integer
      provider:
        type: string
      repo_name:
        type: string
      repo_owner:
        type: string
      repo_url:
        type: string
      resource_count:
        type: integer
      result_json:
        items:
          type: integer
        type: array
      scan_errors:
        items:
          $ref: '#/definitions/internal_api.ScanErrorDTO'
        type: array
      scan_type:
        type: string
      started_at:
        type: string
      status:
        type: string
      status_kind:
        description: '"ok" | "partial" | "failed"; omitted for other statuses'
        type: string
    type: object
  internal_api.ScheduledScanListResponse:
    properties:
      limit:
        example: 50
        type: integer
      offset:
        example: 0
        type: integer
      schedules:
        items:
          $ref: '#/definitions/internal_api.ScheduledScanResponse'
        type: array
      total:
        example: 3
        type: integer
    type: object
  internal_api.ScheduledScanResponse:
    properties:
      cloud_account_id:
        type: string
      consecutive_failures:
        type: integer
      created_at:
        type: string
      created_by:
        type: string
      disabled_reason:
        type: string
      enabled:
        type: boolean
      filter_regions:
        items:
          type: string
        type: array
      id:
        type: string
      last_error:
        type: string
      last_run_at:
        type: string
      name:
        type: string
      next_run_at:
        type: string
      notify_slack_channel:
        type: string
      notify_webhook_config_ids:
        items:
          type: string
        type: array
      org_id:
        type: string
      schedule:
        type: string
      updated_at:
        type: string
    type: object
  internal_api.StateSourceListResponse:
    properties:
      limit:
        example: 100
        type: integer
      offset:
        example: 0
        type: integer
      state_sources:
        items:
          $ref: '#/definitions/internal_api.StateSourceResponse'
        type: array
      total:
        example: 2
        type: integer
    type: object
  internal_api.StateSourceResponse:
    properties:
      cloud_account_id:
        type: string
      config:
        items:
          type: integer
        type: array
      created_at:
        type: string
      display_name:
        type: string
      id:
        type: string
      kind:
        type: string
      last_fetched_at:
        type: string
      latest_snapshot_id:
        type: string
      org_id:
        type: string
      workspaces:
        items:
          type: string
        type: array
    type: object
  internal_api.SuppressRequest:
    properties:
      duration:
        enum:
          - 7d
          - 30d
          - 90d
          - forever
        example: 7d
        type: string
      fingerprints:
        items:
          type: string
        maxItems: 100
        minItems: 1
        type: array
      is_false_positive:
        type: boolean
      reason:
        type: string
      resource_addresses:
        items:
          type: string
        maxItems: 100
        minItems: 1
        type: array
    required:
      - duration
      - fingerprints
      - reason
      - resource_addresses
    type: object
  internal_api.SuppressionDTO:
    properties:
      created_at:
        type: string
      duration:
        type: string
      expires_at:
        type: string
      fingerprint:
        type: string
      id:
        type: string
      is_false_positive:
        type: boolean
      reason:
        type: string
      resource_address:
        type: string
      suppressed_by:
        type: string
    type: object
  internal_api.SuppressionListResponse:
    properties:
      suppressions:
        items:
          $ref: '#/definitions/internal_api.SuppressionDTO'
        type: array
    type: object
  internal_api.ToggleCustomRuleRequest:
    properties:
      enabled:
        type: boolean
    type: object
  internal_api.UpdateCustomRuleRequest:
    properties:
      config:
        items:
          type: integer
        type: array
      description:
        type: string
      name:
        type: string
    required:
      - config
      - description
      - name
    type: object
  internal_api.UpdatePlanNoiseSettingsRequest:
    properties:
      recurrence_threshold:
        minimum: 1
        type: integer
      window_days:
        minimum: 1
        type: integer
    required:
      - recurrence_threshold
      - window_days
    type: object
  internal_api.UpdateScheduledScanRequest:
    properties:
      cloud_account_id:
        type: string
      enabled:
        type: boolean
      filter_regions:
        items:
          type: string
        type: array
      name:
        type: string
      notify_slack_channel:
        type: string
      notify_webhook_config_ids:
        items:
          type: string
        type: array
      schedule:
        type: string
    type: object
  internal_api.UpdateWebhookConfigRequest:
    properties:
      api_token:
        description: non-empty to replace; empty string clears the token
        type: string
      enabled:
        type: boolean
    type: object
  internal_api.WebhookConfigListResponse:
    properties:
      limit:
        example: 100
        type: integer
      offset:
        example: 0
        type: integer
      total:
        example: 3
        type: integer
      webhook_configs:
        items:
          $ref: '#/definitions/internal_api.WebhookConfigResponse'
        type: array
    type: object
  internal_api.WebhookConfigResponse:
    properties:
      api_token_expires_at:
        type: string
      api_token_prefix:
        type: string
      api_token_status:
        $ref: '#/definitions/github_com_driftwise_backend_internal_repo.TokenStatus'
      created_at:
        type: string
      enabled:
        type: boolean
      id:
        type: string
      label:
        type: string
      provider:
        type: string
      provider_base_url:
        type: string
      raw_secret:
        description: populated ONLY on POST response
        type: string
      repo_path:
        type: string
      secret_prefix:
        type: string
      updated_at:
        type: string
    type: object
  internal_api.adminMeResponse:
    properties:
      is_platform_admin:
        type: boolean
    type: object
  internal_api.billingCheckoutRequest:
    properties:
      interval:
        description: |-
          Interval selects the Stripe price: "month" for monthly billing,
          "year" for annual. Anything else returns 400.
        enum:
          - month
          - year
        example: month
        type: string
    required:
      - interval
    type: object
  internal_api.billingURLResponse:
    properties:
      url:
        example: https://checkout.stripe.com/c/pay/cs_test_a1...
        type: string
    type: object
  internal_api.billingUsageResponse:
    properties:
      plan:
        example: team
        type: string
      role:
        description: |-
          Role is the caller's role in this org (owner, admin, member,
          viewer). API-key callers always see "admin" here — scopes
          govern what they can actually do, role is a UX hint.
        example: owner
        type: string
      seats:
        $ref: '#/definitions/internal_api.billingUsageSeats'
      subscription_status:
        example: active
        type: string
    type: object
  internal_api.billingUsageSeats:
    properties:
      included:
        description: Included is the plan's base seat allowance. -1 means unlimited.
        example: 10
        type: integer
      used:
        example: 7
        type: integer
    type: object
  internal_api.createDomainRequest:
    properties:
      domain:
        description: |-
          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.
        example: acme.com
        type: string
    required:
      - domain
    type: object
  internal_api.createMembershipRequest:
    properties:
      org_id:
        example: b2c3d4e5-6789-01bc-defa-2345678901bc
        type: string
      role:
        description: Role must be one of owner, admin, member, viewer. Case-sensitive.
        enum:
          - owner
          - admin
          - member
          - viewer
        example: member
        type: string
      user_id:
        example: a1b2c3d4-5678-90ab-cdef-1234567890ab
        type: string
    required:
      - org_id
      - role
      - user_id
    type: object
  internal_api.enqueueAuditExportRequest:
    properties:
      period_end:
        type: string
      period_start:
        type: string
    type: object
  internal_api.federationInfoResponse:
    properties:
      audience:
        example: https://driftwise-federation.example.com
        type: string
      issuer_url:
        example: https://token.actions.githubusercontent.com
        type: string
      service_account_email:
        example: driftwise-federation@my-project.iam.gserviceaccount.com
        type: string
      subject:
        example: repo:myorg/myrepo:ref:refs/heads/main
        type: string
    type: object
  internal_api.listAuditExportsResponse:
    properties:
      items:
        items:
          $ref: '#/definitions/internal_api.AuditExportDTO'
        type: array
      total:
        type: integer
    type: object
  internal_api.llmConfigPublic:
    properties:
      configured_at:
        type: string
      last_validated_at:
        type: string
      provider:
        type: string
    type: object
  internal_api.llmProviderListResponse:
    properties:
      providers:
        items:
          $ref: >-
            #/definitions/github_com_driftwise_backend_internal_llm.ProviderDescriptor
        type: array
    type: object
  internal_api.llmUsageResponse:
    properties:
      byok_configured:
        description: |-
          BYOKConfigured reports whether the org has a persisted BYOK row; when
          true, the caps are advisory — BYOK calls bypass both gates.
        type: boolean
      hour_resets_at:
        type: string
      hourly_cap:
        type: integer
      hourly_used:
        type: integer
      week_resets_at:
        type: string
      weekly_cap:
        type: integer
      weekly_used:
        type: integer
    type: object
  internal_api.providerListResponse:
    properties:
      providers:
        items:
          $ref: >-
            #/definitions/github_com_driftwise_backend_internal_cloud.ProviderDescriptor
        type: array
    type: object
  internal_api.putLLMConfigRequest:
    properties:
      api_key:
        description: Anthropic / OpenAI / Gemini
        type: string
      aws_access_key_id:
        type: string
      aws_region:
        description: AWS Bedrock
        type: string
      aws_role_arn:
        type: string
      aws_secret_access_key:
        type: string
      aws_session_token:
        type: string
      azure_api_key:
        type: string
      azure_api_version:
        type: string
      azure_deployment:
        type: string
      azure_endpoint:
        description: Azure OpenAI
        type: string
      model:
        type: string
      provider:
        type: string
    type: object
  internal_api.slackAuthURLResponse:
    properties:
      url:
        example: https://slack.com/oauth/v2/authorize?client_id=...
        type: string
    type: object
  internal_api.slackStatusResponse:
    properties:
      available:
        type: boolean
      created_at:
        type: string
      installed:
        type: boolean
      scopes:
        items:
          type: string
        type: array
      team_id:
        type: string
      team_name:
        type: string
    type: object
  internal_api.slackUninstallResponse:
    properties:
      uninstalled:
        example: true
        type: boolean
    type: object
  internal_api.ssoConfigResponse:
    properties:
      enabled:
        type: boolean
      idp_entity_id:
        type: string
      idp_metadata_url:
        type: string
      scim_endpoint:
        description: |-
          SCIMEndpoint is only populated when the org's plan includes the
          FeatureSCIM flag (enterprise-tier). Empty string = plan does not
          include SCIM; consumers should hide the SCIM UI.
        type: string
    type: object
  internal_api.ssoUpdateResponse:
    properties:
      ok:
        example: true
        type: boolean
    type: object
  internal_api.updateMembershipRoleRequest:
    properties:
      role:
        enum:
          - owner
          - admin
          - member
          - viewer
        example: admin
        type: string
    required:
      - role
    type: object
  internal_api.updateMembershipRoleResponse:
    properties:
      membership_id:
        type: string
      noop:
        type: boolean
      org_id:
        type: string
      role:
        type: string
      user_id:
        type: string
    type: object
  internal_api.updateSSORequest:
    properties:
      idp_entity_id:
        type: string
      idp_metadata_url:
        type: string
    type: object
  internal_api.userResp:
    properties:
      created_at:
        type: string
      display_name:
        type: string
      email:
        type: string
      id:
        type: string
      is_platform_admin:
        type: boolean
    type: object
  internal_api.verifyResponseBroken:
    properties:
      checked:
        type: integer
      first_broken_seq:
        type: integer
      head_seq:
        type: integer
      reason:
        type: string
      status:
        type: string
    type: object
  internal_api.verifyResponseOK:
    properties:
      checked:
        type: integer
      head_hash:
        description: hex, or "" if empty chain
        type: string
      head_seq:
        type: integer
      status:
        type: string
    type: object
host: app.driftwise.ai
info:
  contact:
    email: support@driftwise.ai
    name: DriftWise Support
    url: https://www.driftwise.ai
  description: >-
    DriftWise multi-cloud infrastructure drift detection API. All endpoints live
    under /api/v2.
  license:
    name: Proprietary
  termsOfService: https://www.driftwise.ai/terms
  title: DriftWise API
  version: '2'
paths:
  /admin/audit-log:
    get:
      description: >-
        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.
      parameters:
        - description: page size (default 50, max 200)
          in: query
          name: limit
          type: integer
        - description: page offset
          in: query
          name: offset
          type: integer
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            items:
              $ref: >-
                #/definitions/github_com_driftwise_backend_internal_repo.AuditEntry
            type: array
        '403':
          description: platform admin required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
      summary: List audit log entries
      tags:
        - admin
        - audit
      x-codeSamples:
        - lang: cURL
          source: |-
            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'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/admin/audit-log"


            querystring =
            {"limit":"SOME_INTEGER_VALUE","offset":"SOME_INTEGER_VALUE"}


            headers = {"Authorization": "REPLACE_KEY_VALUE"}


            response = requests.request("GET", url, headers=headers,
            params=querystring)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {Authorization:
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/admin/audit-log?limit=SOME_INTEGER_VALUE&offset=SOME_INTEGER_VALUE',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/admin/audit-log?limit=SOME_INTEGER_VALUE&offset=SOME_INTEGER_VALUE\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"Authorization\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /admin/audit-log/verify:
    get:
      description: >-
        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.
      parameters:
        - description: Operator-tracked watermark; broken if current head_seq is lower
          in: query
          name: expected_min_seq
          type: integer
      produces:
        - application/json
      responses:
        '200':
          description: Chain intact
          schema:
            $ref: '#/definitions/internal_api.verifyResponseOK'
        '400':
          description: invalid expected_min_seq
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '403':
          description: platform admin required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '409':
          description: Chain broken or truncated
          schema:
            $ref: '#/definitions/internal_api.verifyResponseBroken'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
      summary: Verify the platform audit chain
      tags:
        - admin
        - audit
      x-codeSamples:
        - lang: cURL
          source: |-
            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'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/admin/audit-log/verify"


            querystring = {"expected_min_seq":"SOME_INTEGER_VALUE"}


            headers = {"Authorization": "REPLACE_KEY_VALUE"}


            response = requests.request("GET", url, headers=headers,
            params=querystring)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {Authorization:
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/admin/audit-log/verify?expected_min_seq=SOME_INTEGER_VALUE',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/admin/audit-log/verify?expected_min_seq=SOME_INTEGER_VALUE\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"Authorization\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /admin/domains:
    get:
      description: >-
        Email domains whose users may authenticate. Users with domains not on
        this list are rejected at login with 403.
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            items:
              $ref: >-
                #/definitions/github_com_driftwise_backend_internal_repo.AllowedDomain
            type: array
        '403':
          description: platform admin required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
      summary: List allowed email domains
      tags:
        - admin
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request GET \
              --url https://app.driftwise.ai/api/v2/admin/domains \
              --header 'Authorization: REPLACE_KEY_VALUE'
        - lang: Python
          source: |-
            import requests

            url = "https://app.driftwise.ai/api/v2/admin/domains"

            headers = {"Authorization": "REPLACE_KEY_VALUE"}

            response = requests.request("GET", url, headers=headers)

            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {Authorization:
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/admin/domains', options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/admin/domains\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"Authorization\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
    post:
      consumes:
        - application/json
      description: >-
        Validation: must contain a dot, must not contain @, spaces, or a
        protocol prefix. Automatically lowercased.
      parameters:
        - description: Domain to allow
          in: body
          name: body
          required: true
          schema:
            properties:
              domain:
                description: >-
                  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.
                example: acme.com
                type: string
            required:
              - domain
            type: object
      produces:
        - application/json
      responses:
        '201':
          description: Created
          schema:
            $ref: >-
              #/definitions/github_com_driftwise_backend_internal_repo.AllowedDomain
        '400':
          description: invalid domain
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '403':
          description: platform admin required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '409':
          description: domain already exists
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
      summary: Add an allowed email domain
      tags:
        - admin
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request POST \
              --url https://app.driftwise.ai/api/v2/admin/domains \
              --header 'Authorization: REPLACE_KEY_VALUE' \
              --header 'accept: application/json' \
              --header 'content-type: application/json' \
              --data '{"domain":"acme.com"}'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/admin/domains"


            payload = {"domain": "acme.com"}

            headers = {
                "accept": "application/json",
                "Authorization": "REPLACE_KEY_VALUE",
                "content-type": "application/json"
            }


            response = requests.request("POST", url, json=payload,
            headers=headers)


            print(response.text)
        - lang: TypeScript
          source: |-
            const options = {
              method: 'POST',
              headers: {
                accept: 'application/json',
                Authorization: 'REPLACE_KEY_VALUE',
                'content-type': 'application/json'
              },
              body: '{"domain":"acme.com"}'
            };

            fetch('https://app.driftwise.ai/api/v2/admin/domains', options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/admin/domains\"\n\n\tpayload := strings.NewReader(\"{\\\"domain\\\":\\\"acme.com\\\"}\")\n\n\treq, _ := http.NewRequest(\"POST\", url, payload)\n\n\treq.Header.Add(\"accept\", \"application/json\")\n\treq.Header.Add(\"Authorization\", \"REPLACE_KEY_VALUE\")\n\treq.Header.Add(\"content-type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /admin/domains/{id}:
    delete:
      description: >-
        Users with this domain will be rejected at next login. Existing sessions
        survive up to 30s (cache TTL) before invalidation.
      parameters:
        - description: Domain allowlist entry ID (UUID)
          in: path
          name: id
          required: true
          type: string
      responses:
        '204':
          description: No Content
        '403':
          description: platform admin required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '404':
          description: domain not found
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
      summary: Remove an allowed email domain
      tags:
        - admin
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request DELETE \
              --url https://app.driftwise.ai/api/v2/admin/domains/%7Bid%7D \
              --header 'Authorization: REPLACE_KEY_VALUE'
        - lang: Python
          source: |-
            import requests

            url = "https://app.driftwise.ai/api/v2/admin/domains/%7Bid%7D"

            headers = {"Authorization": "REPLACE_KEY_VALUE"}

            response = requests.request("DELETE", url, headers=headers)

            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'DELETE', headers: {Authorization:
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/admin/domains/%7Bid%7D',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/admin/domains/%7Bid%7D\"\n\n\treq, _ := http.NewRequest(\"DELETE\", url, nil)\n\n\treq.Header.Add(\"Authorization\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /admin/me:
    get:
      description: >-
        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.
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.adminMeResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Report admin status (and bootstrap first admin)
      tags:
        - admin
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request GET \
              --url https://app.driftwise.ai/api/v2/admin/me \
              --header 'X-API-Key: REPLACE_KEY_VALUE'
        - lang: Python
          source: |-
            import requests

            url = "https://app.driftwise.ai/api/v2/admin/me"

            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}

            response = requests.request("GET", url, headers=headers)

            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/admin/me', options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/admin/me\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /admin/memberships:
    post:
      consumes:
        - application/json
      description: >-
        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.
      parameters:
        - description: user + org + role
          in: body
          name: body
          required: true
          schema:
            properties:
              org_id:
                example: b2c3d4e5-6789-01bc-defa-2345678901bc
                type: string
              role:
                description: >-
                  Role must be one of owner, admin, member, viewer.
                  Case-sensitive.
                enum:
                  - owner
                  - admin
                  - member
                  - viewer
                example: member
                type: string
              user_id:
                example: a1b2c3d4-5678-90ab-cdef-1234567890ab
                type: string
            required:
              - org_id
              - role
              - user_id
            type: object
      produces:
        - application/json
      responses:
        '201':
          description: Created
          schema:
            $ref: >-
              #/definitions/github_com_driftwise_backend_internal_repo.OrgMembership
        '400':
          description: invalid body or role
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '402':
          description: seat limit reached
          schema:
            $ref: '#/definitions/internal_api.PaymentRequiredResponse'
        '403':
          description: platform admin required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '409':
          description: user already a member
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
      summary: Add a user to an org
      tags:
        - admin
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request POST \
              --url https://app.driftwise.ai/api/v2/admin/memberships \
              --header 'Authorization: REPLACE_KEY_VALUE' \
              --header 'accept: application/json' \
              --header 'content-type: application/json' \
              --data '{"org_id":"b2c3d4e5-6789-01bc-defa-2345678901bc","role":"member","user_id":"a1b2c3d4-5678-90ab-cdef-1234567890ab"}'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/admin/memberships"


            payload = {
                "org_id": "b2c3d4e5-6789-01bc-defa-2345678901bc",
                "role": "member",
                "user_id": "a1b2c3d4-5678-90ab-cdef-1234567890ab"
            }

            headers = {
                "accept": "application/json",
                "Authorization": "REPLACE_KEY_VALUE",
                "content-type": "application/json"
            }


            response = requests.request("POST", url, json=payload,
            headers=headers)


            print(response.text)
        - lang: TypeScript
          source: |-
            const options = {
              method: 'POST',
              headers: {
                accept: 'application/json',
                Authorization: 'REPLACE_KEY_VALUE',
                'content-type': 'application/json'
              },
              body: '{"org_id":"b2c3d4e5-6789-01bc-defa-2345678901bc","role":"member","user_id":"a1b2c3d4-5678-90ab-cdef-1234567890ab"}'
            };

            fetch('https://app.driftwise.ai/api/v2/admin/memberships', options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/admin/memberships\"\n\n\tpayload := strings.NewReader(\"{\\\"org_id\\\":\\\"b2c3d4e5-6789-01bc-defa-2345678901bc\\\",\\\"role\\\":\\\"member\\\",\\\"user_id\\\":\\\"a1b2c3d4-5678-90ab-cdef-1234567890ab\\\"}\")\n\n\treq, _ := http.NewRequest(\"POST\", url, payload)\n\n\treq.Header.Add(\"accept\", \"application/json\")\n\treq.Header.Add(\"Authorization\", \"REPLACE_KEY_VALUE\")\n\treq.Header.Add(\"content-type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /admin/memberships/{id}:
    delete:
      description: >-
        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.
      parameters:
        - description: Membership ID (UUID)
          in: path
          name: id
          required: true
          type: string
      responses:
        '204':
          description: No Content
        '403':
          description: platform admin required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '404':
          description: membership not found
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
      summary: Remove a membership
      tags:
        - admin
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request DELETE \
              --url https://app.driftwise.ai/api/v2/admin/memberships/%7Bid%7D \
              --header 'Authorization: REPLACE_KEY_VALUE'
        - lang: Python
          source: |-
            import requests

            url = "https://app.driftwise.ai/api/v2/admin/memberships/%7Bid%7D"

            headers = {"Authorization": "REPLACE_KEY_VALUE"}

            response = requests.request("DELETE", url, headers=headers)

            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'DELETE', headers: {Authorization:
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/admin/memberships/%7Bid%7D',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/admin/memberships/%7Bid%7D\"\n\n\treq, _ := http.NewRequest(\"DELETE\", url, nil)\n\n\treq.Header.Add(\"Authorization\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
    patch:
      consumes:
        - application/json
      description: >-
        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.
      parameters:
        - description: Membership ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: New role
          in: body
          name: body
          required: true
          schema:
            properties:
              role:
                enum:
                  - owner
                  - admin
                  - member
                  - viewer
                example: admin
                type: string
            required:
              - role
            type: object
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.updateMembershipRoleResponse'
        '400':
          description: invalid role
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '403':
          description: platform admin required or API key used
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '404':
          description: membership not found
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '409':
          description: cannot demote the last owner
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
      summary: Change a membership's role (admin)
      tags:
        - admin
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request PATCH \
              --url https://app.driftwise.ai/api/v2/admin/memberships/%7Bid%7D \
              --header 'Authorization: REPLACE_KEY_VALUE' \
              --header 'accept: application/json' \
              --header 'content-type: application/json' \
              --data '{"role":"admin"}'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/admin/memberships/%7Bid%7D"


            payload = {"role": "admin"}

            headers = {
                "accept": "application/json",
                "Authorization": "REPLACE_KEY_VALUE",
                "content-type": "application/json"
            }


            response = requests.request("PATCH", url, json=payload,
            headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {
              method: 'PATCH',
              headers: {
                accept: 'application/json',
                Authorization: 'REPLACE_KEY_VALUE',
                'content-type': 'application/json'
              },
              body: '{"role":"admin"}'
            };


            fetch('https://app.driftwise.ai/api/v2/admin/memberships/%7Bid%7D',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/admin/memberships/%7Bid%7D\"\n\n\tpayload := strings.NewReader(\"{\\\"role\\\":\\\"admin\\\"}\")\n\n\treq, _ := http.NewRequest(\"PATCH\", url, payload)\n\n\treq.Header.Add(\"accept\", \"application/json\")\n\treq.Header.Add(\"Authorization\", \"REPLACE_KEY_VALUE\")\n\treq.Header.Add(\"content-type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /admin/orgs:
    get:
      description: >-
        Platform-admin-only paginated list of every org. Includes free, team,
        and enterprise plans. Ordered by created_at descending.
      parameters:
        - description: page size (default 100, max 500)
          in: query
          name: limit
          type: integer
        - description: page offset
          in: query
          name: offset
          type: integer
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            items:
              $ref: >-
                #/definitions/github_com_driftwise_backend_internal_repo.AdminOrgRow
            type: array
        '403':
          description: platform admin required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
      summary: List all organizations
      tags:
        - admin
      x-codeSamples:
        - lang: cURL
          source: |-
            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'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/admin/orgs"


            querystring =
            {"limit":"SOME_INTEGER_VALUE","offset":"SOME_INTEGER_VALUE"}


            headers = {"Authorization": "REPLACE_KEY_VALUE"}


            response = requests.request("GET", url, headers=headers,
            params=querystring)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {Authorization:
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/admin/orgs?limit=SOME_INTEGER_VALUE&offset=SOME_INTEGER_VALUE',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/admin/orgs?limit=SOME_INTEGER_VALUE&offset=SOME_INTEGER_VALUE\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"Authorization\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
    post:
      consumes:
        - application/json
      description: >-
        Provision a new org on the Free plan. Does NOT make the caller an owner
        — use POST /admin/memberships afterward to assign ownership.
      parameters:
        - description: Org slug + display name
          in: body
          name: body
          required: true
          schema:
            properties:
              display_name:
                type: string
              slug:
                type: string
            required:
              - display_name
              - slug
            type: object
      produces:
        - application/json
      responses:
        '201':
          description: Created
          schema:
            $ref: '#/definitions/internal_api.OrgResponse'
        '400':
          description: invalid body
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '403':
          description: platform admin required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '409':
          description: org slug already exists
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
      summary: Create an organization (admin)
      tags:
        - admin
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request POST \
              --url https://app.driftwise.ai/api/v2/admin/orgs \
              --header 'Authorization: REPLACE_KEY_VALUE' \
              --header 'accept: application/json' \
              --header 'content-type: application/json' \
              --data '{"display_name":"string","slug":"string"}'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/admin/orgs"


            payload = {
                "display_name": "string",
                "slug": "string"
            }

            headers = {
                "accept": "application/json",
                "Authorization": "REPLACE_KEY_VALUE",
                "content-type": "application/json"
            }


            response = requests.request("POST", url, json=payload,
            headers=headers)


            print(response.text)
        - lang: TypeScript
          source: |-
            const options = {
              method: 'POST',
              headers: {
                accept: 'application/json',
                Authorization: 'REPLACE_KEY_VALUE',
                'content-type': 'application/json'
              },
              body: '{"display_name":"string","slug":"string"}'
            };

            fetch('https://app.driftwise.ai/api/v2/admin/orgs', options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/admin/orgs\"\n\n\tpayload := strings.NewReader(\"{\\\"display_name\\\":\\\"string\\\",\\\"slug\\\":\\\"string\\\"}\")\n\n\treq, _ := http.NewRequest(\"POST\", url, payload)\n\n\treq.Header.Add(\"accept\", \"application/json\")\n\treq.Header.Add(\"Authorization\", \"REPLACE_KEY_VALUE\")\n\treq.Header.Add(\"content-type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /admin/users:
    get:
      description: >-
        Paginated list of every user known to the platform. Includes both admins
        and regular users; the is_platform_admin flag distinguishes.
      parameters:
        - description: page size (default 100, max 500)
          in: query
          name: limit
          type: integer
        - description: page offset
          in: query
          name: offset
          type: integer
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            items:
              $ref: '#/definitions/internal_api.userResp'
            type: array
        '403':
          description: platform admin required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
      summary: List users
      tags:
        - admin
      x-codeSamples:
        - lang: cURL
          source: |-
            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'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/admin/users"


            querystring =
            {"limit":"SOME_INTEGER_VALUE","offset":"SOME_INTEGER_VALUE"}


            headers = {"Authorization": "REPLACE_KEY_VALUE"}


            response = requests.request("GET", url, headers=headers,
            params=querystring)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {Authorization:
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/admin/users?limit=SOME_INTEGER_VALUE&offset=SOME_INTEGER_VALUE',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/admin/users?limit=SOME_INTEGER_VALUE&offset=SOME_INTEGER_VALUE\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"Authorization\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /admin/users/{id}:
    delete:
      description: >-
        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.
      parameters:
        - description: User ID (UUID)
          in: path
          name: id
          required: true
          type: string
      responses:
        '204':
          description: No Content
        '400':
          description: cannot delete yourself
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '403':
          description: platform admin required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '404':
          description: user not found
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
      summary: Delete a user (soft)
      tags:
        - admin
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request DELETE \
              --url https://app.driftwise.ai/api/v2/admin/users/%7Bid%7D \
              --header 'Authorization: REPLACE_KEY_VALUE'
        - lang: Python
          source: |-
            import requests

            url = "https://app.driftwise.ai/api/v2/admin/users/%7Bid%7D"

            headers = {"Authorization": "REPLACE_KEY_VALUE"}

            response = requests.request("DELETE", url, headers=headers)

            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'DELETE', headers: {Authorization:
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/admin/users/%7Bid%7D',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/admin/users/%7Bid%7D\"\n\n\treq, _ := http.NewRequest(\"DELETE\", url, nil)\n\n\treq.Header.Add(\"Authorization\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /admin/users/{id}/memberships:
    get:
      description: Returns the user's role in every org they belong to. Order unspecified.
      parameters:
        - description: User ID (UUID)
          in: path
          name: id
          required: true
          type: string
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            items:
              $ref: >-
                #/definitions/github_com_driftwise_backend_internal_repo.AdminMembershipRow
            type: array
        '403':
          description: platform admin required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
      summary: List a user's org memberships
      tags:
        - admin
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request GET \
              --url https://app.driftwise.ai/api/v2/admin/users/%7Bid%7D/memberships \
              --header 'Authorization: REPLACE_KEY_VALUE'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/admin/users/%7Bid%7D/memberships"


            headers = {"Authorization": "REPLACE_KEY_VALUE"}


            response = requests.request("GET", url, headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {Authorization:
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/admin/users/%7Bid%7D/memberships',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/admin/users/%7Bid%7D/memberships\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"Authorization\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /builtin-rules:
    get:
      description: >-
        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.
      parameters:
        - description: Filter types by provider
          enum:
            - aws
            - gcp
            - azure
          in: query
          name: provider
          type: string
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.BuiltinRulesResponse'
        '400':
          description: invalid provider
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: List built-in rules
      tags:
        - policy
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request GET \
              --url 'https://app.driftwise.ai/api/v2/builtin-rules?provider=SOME_STRING_VALUE' \
              --header 'X-API-Key: REPLACE_KEY_VALUE'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/builtin-rules"


            querystring = {"provider":"SOME_STRING_VALUE"}


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("GET", url, headers=headers,
            params=querystring)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/builtin-rules?provider=SOME_STRING_VALUE',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/builtin-rules?provider=SOME_STRING_VALUE\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /federation-info:
    get:
      description: >-
        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.
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.federationInfoResponse'
        '403':
          description: platform admin required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '503':
          description: federation not configured on server
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
      summary: Get WIF configuration for CI integration
      tags:
        - admin
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request GET \
              --url https://app.driftwise.ai/api/v2/federation-info \
              --header 'Authorization: REPLACE_KEY_VALUE'
        - lang: Python
          source: |-
            import requests

            url = "https://app.driftwise.ai/api/v2/federation-info"

            headers = {"Authorization": "REPLACE_KEY_VALUE"}

            response = requests.request("GET", url, headers=headers)

            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {Authorization:
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/federation-info', options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/federation-info\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"Authorization\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /llm-providers:
    get:
      description: >-
        Returns the compiled-in LLM provider enums (anthropic, openai, bedrock,
        gemini, azure_openai). Response is static per server build; frontend
        caches indefinitely.
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.llmProviderListResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: List supported LLM providers
      tags:
        - llm
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request GET \
              --url https://app.driftwise.ai/api/v2/llm-providers \
              --header 'X-API-Key: REPLACE_KEY_VALUE'
        - lang: Python
          source: |-
            import requests

            url = "https://app.driftwise.ai/api/v2/llm-providers"

            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}

            response = requests.request("GET", url, headers=headers)

            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/llm-providers', options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/llm-providers\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /operator/report:
    post:
      consumes:
        - application/json
      description: >-
        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.
      parameters:
        - description: Cluster + namespace + resources
          in: body
          name: body
          required: true
          schema:
            properties:
              cluster_id:
                type: string
              custom_prompt:
                type: string
              generate_iac:
                type: boolean
              iac_format:
                type: string
              namespace:
                type: string
              resources:
                items:
                  $ref: >-
                    #/definitions/github_com_driftwise_backend_internal_cloud.Resource
                type: array
            required:
              - cluster_id
              - namespace
              - resources
            type: object
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.OperatorReportResponse'
        '400':
          description: invalid body or unserializable resource
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '403':
          description: API key required (OIDC rejected)
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - APIKey: []
      summary: Ingest K8s operator resource report
      tags:
        - accounts
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request POST \
              --url https://app.driftwise.ai/api/v2/operator/report \
              --header 'X-API-Key: REPLACE_KEY_VALUE' \
              --header 'accept: application/json' \
              --header 'content-type: application/json' \
              --data '{"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"}}]}'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/operator/report"


            payload = {
                "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"
                        }
                    }
                ]
            }

            headers = {
                "accept": "application/json",
                "X-API-Key": "REPLACE_KEY_VALUE",
                "content-type": "application/json"
            }


            response = requests.request("POST", url, json=payload,
            headers=headers)


            print(response.text)
        - lang: TypeScript
          source: |-
            const options = {
              method: 'POST',
              headers: {
                accept: 'application/json',
                'X-API-Key': 'REPLACE_KEY_VALUE',
                'content-type': 'application/json'
              },
              body: '{"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"}}]}'
            };

            fetch('https://app.driftwise.ai/api/v2/operator/report', options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/operator/report\"\n\n\tpayload := strings.NewReader(\"{\\\"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\\\"}}]}\")\n\n\treq, _ := http.NewRequest(\"POST\", url, payload)\n\n\treq.Header.Add(\"accept\", \"application/json\")\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\treq.Header.Add(\"content-type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs:
    post:
      consumes:
        - application/json
      description: >-
        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.
      parameters:
        - description: Org slug + display name
          in: body
          name: body
          required: true
          schema:
            properties:
              display_name:
                type: string
              slug:
                type: string
            required:
              - display_name
              - slug
            type: object
      produces:
        - application/json
      responses:
        '201':
          description: Created
          schema:
            $ref: '#/definitions/internal_api.OrgResponse'
        '400':
          description: invalid body or X-Org-ID header set
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '403':
          description: org creation requires OIDC
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '409':
          description: org slug already exists
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
      summary: Create a new org (self-service)
      tags:
        - admin
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request POST \
              --url https://app.driftwise.ai/api/v2/orgs \
              --header 'Authorization: REPLACE_KEY_VALUE' \
              --header 'accept: application/json' \
              --header 'content-type: application/json' \
              --data '{"display_name":"string","slug":"string"}'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/orgs"


            payload = {
                "display_name": "string",
                "slug": "string"
            }

            headers = {
                "accept": "application/json",
                "Authorization": "REPLACE_KEY_VALUE",
                "content-type": "application/json"
            }


            response = requests.request("POST", url, json=payload,
            headers=headers)


            print(response.text)
        - lang: TypeScript
          source: |-
            const options = {
              method: 'POST',
              headers: {
                accept: 'application/json',
                Authorization: 'REPLACE_KEY_VALUE',
                'content-type': 'application/json'
              },
              body: '{"display_name":"string","slug":"string"}'
            };

            fetch('https://app.driftwise.ai/api/v2/orgs', options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs\"\n\n\tpayload := strings.NewReader(\"{\\\"display_name\\\":\\\"string\\\",\\\"slug\\\":\\\"string\\\"}\")\n\n\treq, _ := http.NewRequest(\"POST\", url, payload)\n\n\treq.Header.Add(\"accept\", \"application/json\")\n\treq.Header.Add(\"Authorization\", \"REPLACE_KEY_VALUE\")\n\treq.Header.Add(\"content-type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}:
    get:
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.OrgResponse'
        '404':
          description: organization not found
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Get org metadata
      tags:
        - admin
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request GET \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D \
              --header 'X-API-Key: REPLACE_KEY_VALUE'
        - lang: Python
          source: |-
            import requests

            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D"

            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}

            response = requests.request("GET", url, headers=headers)

            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D', options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/accounts:
    get:
      description: >-
        Paginated list of registered cloud accounts. Credentials are redacted —
        callers see only metadata (display name, provider, external account ID,
        credential type).
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: page size (default 100, max 500)
          in: query
          name: limit
          type: integer
        - description: page offset
          in: query
          name: offset
          type: integer
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.CloudAccountListResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: List cloud accounts
      tags:
        - accounts
      x-codeSamples:
        - lang: cURL
          source: |-
            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'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/accounts"


            querystring =
            {"limit":"SOME_INTEGER_VALUE","offset":"SOME_INTEGER_VALUE"}


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("GET", url, headers=headers,
            params=querystring)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/accounts?limit=SOME_INTEGER_VALUE&offset=SOME_INTEGER_VALUE',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/accounts?limit=SOME_INTEGER_VALUE&offset=SOME_INTEGER_VALUE\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
    post:
      consumes:
        - application/json
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Cloud account config
          in: body
          name: body
          required: true
          schema:
            properties:
              credential_ref:
                type: string
              credential_type:
                type: string
              default_region:
                type: string
              display_name:
                type: string
              external_account_id:
                type: string
              provider:
                type: string
            required:
              - credential_ref
              - credential_type
              - display_name
              - external_account_id
              - provider
            type: object
      produces:
        - application/json
      responses:
        '201':
          description: Created
          schema:
            $ref: '#/definitions/internal_api.CloudAccountResponse'
        '400':
          description: invalid provider, credential_type, or credentials
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '402':
          description: cloud account limit reached
          schema:
            $ref: '#/definitions/internal_api.PaymentRequiredResponse'
        '403':
          description: owner/admin role required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '503':
          description: no encryption key configured
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Register a cloud account
      tags:
        - accounts
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request POST \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/accounts \
              --header 'X-API-Key: REPLACE_KEY_VALUE' \
              --header 'accept: application/json' \
              --header 'content-type: application/json' \
              --data '{"credential_ref":"string","credential_type":"string","default_region":"string","display_name":"string","external_account_id":"string","provider":"string"}'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/accounts"


            payload = {
                "credential_ref": "string",
                "credential_type": "string",
                "default_region": "string",
                "display_name": "string",
                "external_account_id": "string",
                "provider": "string"
            }

            headers = {
                "accept": "application/json",
                "X-API-Key": "REPLACE_KEY_VALUE",
                "content-type": "application/json"
            }


            response = requests.request("POST", url, json=payload,
            headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {
              method: 'POST',
              headers: {
                accept: 'application/json',
                'X-API-Key': 'REPLACE_KEY_VALUE',
                'content-type': 'application/json'
              },
              body: '{"credential_ref":"string","credential_type":"string","default_region":"string","display_name":"string","external_account_id":"string","provider":"string"}'
            };


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/accounts',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/accounts\"\n\n\tpayload := strings.NewReader(\"{\\\"credential_ref\\\":\\\"string\\\",\\\"credential_type\\\":\\\"string\\\",\\\"default_region\\\":\\\"string\\\",\\\"display_name\\\":\\\"string\\\",\\\"external_account_id\\\":\\\"string\\\",\\\"provider\\\":\\\"string\\\"}\")\n\n\treq, _ := http.NewRequest(\"POST\", url, payload)\n\n\treq.Header.Add(\"accept\", \"application/json\")\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\treq.Header.Add(\"content-type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/analyze:
    post:
      consumes:
        - application/json
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Terraform plan JSON + optional CI metadata
          in: body
          name: body
          required: true
          schema:
            properties:
              ci:
                $ref: '#/definitions/internal_api.CIMetadataRequest'
              plan_json:
                type: string
            required:
              - plan_json
            type: object
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.AnalyzePlanResponse'
        '400':
          description: invalid plan, body, or CI metadata
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '402':
          description: platform LLM quota exhausted
          schema:
            $ref: '#/definitions/internal_api.PaymentRequiredResponse'
        '403':
          description: owner/admin role required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '429':
          description: BYOK failure throttle or platform hourly limit
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '502':
          description: BYOK LLM call failed — does not fall back to platform
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Analyze a Terraform plan
      tags:
        - drift
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request POST \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/analyze \
              --header 'X-API-Key: REPLACE_KEY_VALUE' \
              --header 'accept: application/json' \
              --header 'content-type: application/json' \
              --data '{"ci":{"branch":"string","commit_sha":"string","pr_number":0,"provider":"string","repo_name":"string","repo_owner":"string","repo_url":"string"},"plan_json":"string"}'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/analyze"


            payload = {
                "ci": {
                    "branch": "string",
                    "commit_sha": "string",
                    "pr_number": 0,
                    "provider": "string",
                    "repo_name": "string",
                    "repo_owner": "string",
                    "repo_url": "string"
                },
                "plan_json": "string"
            }

            headers = {
                "accept": "application/json",
                "X-API-Key": "REPLACE_KEY_VALUE",
                "content-type": "application/json"
            }


            response = requests.request("POST", url, json=payload,
            headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {
              method: 'POST',
              headers: {
                accept: 'application/json',
                'X-API-Key': 'REPLACE_KEY_VALUE',
                'content-type': 'application/json'
              },
              body: '{"ci":{"branch":"string","commit_sha":"string","pr_number":0,"provider":"string","repo_name":"string","repo_owner":"string","repo_url":"string"},"plan_json":"string"}'
            };


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/analyze',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/analyze\"\n\n\tpayload := strings.NewReader(\"{\\\"ci\\\":{\\\"branch\\\":\\\"string\\\",\\\"commit_sha\\\":\\\"string\\\",\\\"pr_number\\\":0,\\\"provider\\\":\\\"string\\\",\\\"repo_name\\\":\\\"string\\\",\\\"repo_owner\\\":\\\"string\\\",\\\"repo_url\\\":\\\"string\\\"},\\\"plan_json\\\":\\\"string\\\"}\")\n\n\treq, _ := http.NewRequest(\"POST\", url, payload)\n\n\treq.Header.Add(\"accept\", \"application/json\")\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\treq.Header.Add(\"content-type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/api-keys:
    get:
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: page size (default 100, max 500)
          in: query
          name: limit
          type: integer
        - description: page offset
          in: query
          name: offset
          type: integer
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.APIKeyListResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: List API keys
      tags:
        - api-keys
      x-codeSamples:
        - lang: cURL
          source: |-
            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'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/api-keys"


            querystring =
            {"limit":"SOME_INTEGER_VALUE","offset":"SOME_INTEGER_VALUE"}


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("GET", url, headers=headers,
            params=querystring)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/api-keys?limit=SOME_INTEGER_VALUE&offset=SOME_INTEGER_VALUE',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/api-keys?limit=SOME_INTEGER_VALUE&offset=SOME_INTEGER_VALUE\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
    post:
      consumes:
        - application/json
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Key name + scopes
          in: body
          name: body
          required: true
          schema:
            properties:
              name:
                type: string
              scopes:
                items:
                  type: string
                type: array
            required:
              - name
            type: object
      produces:
        - application/json
      responses:
        '201':
          description: raw_key field shown once only
          schema:
            $ref: '#/definitions/internal_api.APIKeyResponse'
        '400':
          description: invalid body or scope
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '402':
          description: API key limit reached
          schema:
            $ref: '#/definitions/internal_api.PaymentRequiredResponse'
        '403':
          description: owner/admin role required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Create an API key
      tags:
        - api-keys
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request POST \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/api-keys \
              --header 'X-API-Key: REPLACE_KEY_VALUE' \
              --header 'accept: application/json' \
              --header 'content-type: application/json' \
              --data '{"name":"string","scopes":["string"]}'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/api-keys"


            payload = {
                "name": "string",
                "scopes": ["string"]
            }

            headers = {
                "accept": "application/json",
                "X-API-Key": "REPLACE_KEY_VALUE",
                "content-type": "application/json"
            }


            response = requests.request("POST", url, json=payload,
            headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {
              method: 'POST',
              headers: {
                accept: 'application/json',
                'X-API-Key': 'REPLACE_KEY_VALUE',
                'content-type': 'application/json'
              },
              body: '{"name":"string","scopes":["string"]}'
            };


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/api-keys',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/api-keys\"\n\n\tpayload := strings.NewReader(\"{\\\"name\\\":\\\"string\\\",\\\"scopes\\\":[\\\"string\\\"]}\")\n\n\treq, _ := http.NewRequest(\"POST\", url, payload)\n\n\treq.Header.Add(\"accept\", \"application/json\")\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\treq.Header.Add(\"content-type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/api-keys/{keyID}:
    delete:
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: API key ID (UUID)
          in: path
          name: keyID
          required: true
          type: string
      responses:
        '204':
          description: No Content
        '403':
          description: owner/admin required or API key caller
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '404':
          description: api key not found or already revoked
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
      summary: Revoke an API key
      tags:
        - api-keys
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request DELETE \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/api-keys/%7BkeyID%7D \
              --header 'Authorization: REPLACE_KEY_VALUE'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/api-keys/%7BkeyID%7D"


            headers = {"Authorization": "REPLACE_KEY_VALUE"}


            response = requests.request("DELETE", url, headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'DELETE', headers: {Authorization:
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/api-keys/%7BkeyID%7D',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/api-keys/%7BkeyID%7D\"\n\n\treq, _ := http.NewRequest(\"DELETE\", url, nil)\n\n\treq.Header.Add(\"Authorization\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/audit-exports:
    get:
      description: >-
        Paginated list of the org's Compliance Pack rows in every status
        (pending, running, done, error). Any org member can list.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: page size (default 50, max 200)
          in: query
          name: limit
          type: integer
        - description: page offset
          in: query
          name: offset
          type: integer
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.listAuditExportsResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: List Compliance Packs
      tags:
        - audit
      x-codeSamples:
        - lang: cURL
          source: |-
            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'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/audit-exports"


            querystring =
            {"limit":"SOME_INTEGER_VALUE","offset":"SOME_INTEGER_VALUE"}


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("GET", url, headers=headers,
            params=querystring)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/audit-exports?limit=SOME_INTEGER_VALUE&offset=SOME_INTEGER_VALUE',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/audit-exports?limit=SOME_INTEGER_VALUE&offset=SOME_INTEGER_VALUE\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
    post:
      consumes:
        - application/json
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Period start + end (UTC)
          in: body
          name: body
          required: true
          schema:
            properties:
              period_end:
                type: string
              period_start:
                type: string
            type: object
      produces:
        - application/json
      responses:
        '202':
          description: Accepted
          schema:
            $ref: '#/definitions/internal_api.AuditExportDTO'
        '400':
          description: period invalid or outside retention
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '402':
          description: plan does not include compliance pack
          schema:
            $ref: '#/definitions/internal_api.PaymentRequiredResponse'
        '403':
          description: owner/admin required or API key used
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '429':
          description: rate limit exceeded (5/24h)
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '503':
          description: compliance pack storage not configured
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
      summary: Enqueue a Compliance Pack
      tags:
        - audit
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request POST \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/audit-exports \
              --header 'Authorization: REPLACE_KEY_VALUE' \
              --header 'accept: application/json' \
              --header 'content-type: application/json' \
              --data '{"period_end":"string","period_start":"string"}'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/audit-exports"


            payload = {
                "period_end": "string",
                "period_start": "string"
            }

            headers = {
                "accept": "application/json",
                "Authorization": "REPLACE_KEY_VALUE",
                "content-type": "application/json"
            }


            response = requests.request("POST", url, json=payload,
            headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {
              method: 'POST',
              headers: {
                accept: 'application/json',
                Authorization: 'REPLACE_KEY_VALUE',
                'content-type': 'application/json'
              },
              body: '{"period_end":"string","period_start":"string"}'
            };


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/audit-exports',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/audit-exports\"\n\n\tpayload := strings.NewReader(\"{\\\"period_end\\\":\\\"string\\\",\\\"period_start\\\":\\\"string\\\"}\")\n\n\treq, _ := http.NewRequest(\"POST\", url, payload)\n\n\treq.Header.Add(\"accept\", \"application/json\")\n\treq.Header.Add(\"Authorization\", \"REPLACE_KEY_VALUE\")\n\treq.Header.Add(\"content-type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/audit-exports/{eid}:
    delete:
      description: >-
        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).
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Export ID (UUID)
          in: path
          name: eid
          required: true
          type: string
      responses:
        '204':
          description: No Content
        '403':
          description: owner/admin required or API key used
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '404':
          description: audit export not found
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '503':
          description: compliance pack storage not configured
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
      summary: Delete a Compliance Pack
      tags:
        - audit
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request DELETE \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/audit-exports/%7Beid%7D \
              --header 'Authorization: REPLACE_KEY_VALUE'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/audit-exports/%7Beid%7D"


            headers = {"Authorization": "REPLACE_KEY_VALUE"}


            response = requests.request("DELETE", url, headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'DELETE', headers: {Authorization:
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/audit-exports/%7Beid%7D',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/audit-exports/%7Beid%7D\"\n\n\treq, _ := http.NewRequest(\"DELETE\", url, nil)\n\n\treq.Header.Add(\"Authorization\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
    get:
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Export ID (UUID)
          in: path
          name: eid
          required: true
          type: string
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.AuditExportDTO'
        '404':
          description: audit export not found
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Get Compliance Pack status
      tags:
        - audit
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request GET \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/audit-exports/%7Beid%7D \
              --header 'X-API-Key: REPLACE_KEY_VALUE'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/audit-exports/%7Beid%7D"


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("GET", url, headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/audit-exports/%7Beid%7D',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/audit-exports/%7Beid%7D\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/audit-exports/{eid}/download:
    get:
      description: >-
        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).
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Export ID (UUID)
          in: path
          name: eid
          required: true
          type: string
      produces:
        - application/zip
      responses:
        '200':
          description: >-
            ZIP stream; filename in Content-Disposition, SHA-256 in
            X-Checksum-SHA256
        '403':
          description: owner/admin required or API key used
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '404':
          description: audit export not found
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '409':
          description: compliance pack is not ready for download
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '410':
          description: artifact no longer available; re-generate
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '503':
          description: compliance pack storage not configured
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
      summary: Download Compliance Pack ZIP
      tags:
        - audit
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request GET \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/audit-exports/%7Beid%7D/download \
              --header 'Authorization: REPLACE_KEY_VALUE'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/audit-exports/%7Beid%7D/download"


            headers = {"Authorization": "REPLACE_KEY_VALUE"}


            response = requests.request("GET", url, headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {Authorization:
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/audit-exports/%7Beid%7D/download',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/audit-exports/%7Beid%7D/download\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"Authorization\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/audit-log/verify:
    get:
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Caller-tracked watermark; broken if current head_seq is lower
          in: query
          name: expected_min_seq
          type: integer
      produces:
        - application/json
      responses:
        '200':
          description: Chain intact
          schema:
            $ref: '#/definitions/internal_api.verifyResponseOK'
        '400':
          description: invalid expected_min_seq
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '403':
          description: owner/admin role required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '409':
          description: Chain broken or truncated
          schema:
            $ref: '#/definitions/internal_api.verifyResponseBroken'
        '429':
          description: rate limit exceeded
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Verify the org audit chain
      tags:
        - audit
      x-codeSamples:
        - lang: cURL
          source: |-
            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'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/audit-log/verify"


            querystring = {"expected_min_seq":"SOME_INTEGER_VALUE"}


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("GET", url, headers=headers,
            params=querystring)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/audit-log/verify?expected_min_seq=SOME_INTEGER_VALUE',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/audit-log/verify?expected_min_seq=SOME_INTEGER_VALUE\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/billing/checkout:
    post:
      consumes:
        - application/json
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Billing interval
          in: body
          name: body
          required: true
          schema:
            properties:
              interval:
                description: >-
                  Interval selects the Stripe price: "month" for monthly
                  billing,

                  "year" for annual. Anything else returns 400.
                enum:
                  - month
                  - year
                example: month
                type: string
            required:
              - interval
            type: object
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.billingURLResponse'
        '400':
          description: invalid interval
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '403':
          description: owner/admin required or API key used
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '409':
          description: org already on team or enterprise plan
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '503':
          description: billing not configured (STRIPE_SECRET_KEY unset)
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
      summary: Create Stripe checkout session
      tags:
        - billing
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request POST \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/billing/checkout \
              --header 'Authorization: REPLACE_KEY_VALUE' \
              --header 'accept: application/json' \
              --header 'content-type: application/json' \
              --data '{"interval":"month"}'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/billing/checkout"


            payload = {"interval": "month"}

            headers = {
                "accept": "application/json",
                "Authorization": "REPLACE_KEY_VALUE",
                "content-type": "application/json"
            }


            response = requests.request("POST", url, json=payload,
            headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {
              method: 'POST',
              headers: {
                accept: 'application/json',
                Authorization: 'REPLACE_KEY_VALUE',
                'content-type': 'application/json'
              },
              body: '{"interval":"month"}'
            };


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/billing/checkout',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/billing/checkout\"\n\n\tpayload := strings.NewReader(\"{\\\"interval\\\":\\\"month\\\"}\")\n\n\treq, _ := http.NewRequest(\"POST\", url, payload)\n\n\treq.Header.Add(\"accept\", \"application/json\")\n\treq.Header.Add(\"Authorization\", \"REPLACE_KEY_VALUE\")\n\treq.Header.Add(\"content-type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/billing/portal:
    post:
      description: >-
        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).
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.billingURLResponse'
        '400':
          description: org has no billing account
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '403':
          description: owner/admin required or API key used
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '503':
          description: billing not configured
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
      summary: Create Stripe customer portal session
      tags:
        - billing
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request POST \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/billing/portal \
              --header 'Authorization: REPLACE_KEY_VALUE'
        - lang: Python
          source: |-
            import requests

            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/billing/portal"

            headers = {"Authorization": "REPLACE_KEY_VALUE"}

            response = requests.request("POST", url, headers=headers)

            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'POST', headers: {Authorization:
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/billing/portal',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/billing/portal\"\n\n\treq, _ := http.NewRequest(\"POST\", url, nil)\n\n\treq.Header.Add(\"Authorization\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/billing/usage:
    get:
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.billingUsageResponse'
        '403':
          description: not a member of this org
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Get current billing usage
      tags:
        - billing
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request GET \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/billing/usage \
              --header 'X-API-Key: REPLACE_KEY_VALUE'
        - lang: Python
          source: |-
            import requests

            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/billing/usage"

            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}

            response = requests.request("GET", url, headers=headers)

            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/billing/usage',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/billing/usage\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/custom-rules:
    get:
      description: >-
        Returns both enabled and disabled rules. Optional rule_type narrows to
        "noise" or "risk". Not paginated — rule counts are bounded in practice.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Filter by rule type
          enum:
            - noise
            - risk
          in: query
          name: rule_type
          type: string
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.CustomRuleListResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: List custom rules
      tags:
        - policy
      x-codeSamples:
        - lang: cURL
          source: |-
            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'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/custom-rules"


            querystring = {"rule_type":"SOME_STRING_VALUE"}


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("GET", url, headers=headers,
            params=querystring)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/custom-rules?rule_type=SOME_STRING_VALUE',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/custom-rules?rule_type=SOME_STRING_VALUE\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
    post:
      consumes:
        - application/json
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Rule definition
          in: body
          name: body
          required: true
          schema:
            properties:
              config:
                items:
                  type: integer
                type: array
              description:
                type: string
              name:
                type: string
              rule_type:
                enum:
                  - noise
                  - risk
                type: string
            required:
              - config
              - description
              - name
              - rule_type
            type: object
      produces:
        - application/json
      responses:
        '201':
          description: Created
          schema:
            $ref: '#/definitions/internal_api.CustomRuleDTO'
        '400':
          description: invalid body or config schema
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '403':
          description: >-
            owner/admin role required (message: role required) OR authenticated
            user identity required (message: user identity required for audit
            trail) — emitted as two distinct bodies so clients can discriminate
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Create a custom rule
      tags:
        - policy
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request POST \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/custom-rules \
              --header 'X-API-Key: REPLACE_KEY_VALUE' \
              --header 'accept: application/json' \
              --header 'content-type: application/json' \
              --data '{"config":[0],"description":"string","name":"string","rule_type":"noise"}'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/custom-rules"


            payload = {
                "config": [0],
                "description": "string",
                "name": "string",
                "rule_type": "noise"
            }

            headers = {
                "accept": "application/json",
                "X-API-Key": "REPLACE_KEY_VALUE",
                "content-type": "application/json"
            }


            response = requests.request("POST", url, json=payload,
            headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {
              method: 'POST',
              headers: {
                accept: 'application/json',
                'X-API-Key': 'REPLACE_KEY_VALUE',
                'content-type': 'application/json'
              },
              body: '{"config":[0],"description":"string","name":"string","rule_type":"noise"}'
            };


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/custom-rules',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/custom-rules\"\n\n\tpayload := strings.NewReader(\"{\\\"config\\\":[0],\\\"description\\\":\\\"string\\\",\\\"name\\\":\\\"string\\\",\\\"rule_type\\\":\\\"noise\\\"}\")\n\n\treq, _ := http.NewRequest(\"POST\", url, payload)\n\n\treq.Header.Add(\"accept\", \"application/json\")\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\treq.Header.Add(\"content-type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/custom-rules/{rule_id}:
    delete:
      description: >-
        Hard delete. To temporarily disable a rule without losing config, PATCH
        with enabled=false instead.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Custom rule ID (UUID)
          in: path
          name: rule_id
          required: true
          type: string
      responses:
        '204':
          description: No Content
        '403':
          description: owner/admin role required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '404':
          description: custom rule not found
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Delete a custom rule
      tags:
        - policy
      x-codeSamples:
        - lang: cURL
          source: |-
            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'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/custom-rules/%7Brule_id%7D"


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("DELETE", url, headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'DELETE', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/custom-rules/%7Brule_id%7D',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/custom-rules/%7Brule_id%7D\"\n\n\treq, _ := http.NewRequest(\"DELETE\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
    get:
      description: Returns the rule including its config blob and enabled flag.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Custom rule ID (UUID)
          in: path
          name: rule_id
          required: true
          type: string
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.CustomRuleDTO'
        '404':
          description: custom rule not found
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Get a custom rule
      tags:
        - policy
      x-codeSamples:
        - lang: cURL
          source: |-
            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'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/custom-rules/%7Brule_id%7D"


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("GET", url, headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/custom-rules/%7Brule_id%7D',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/custom-rules/%7Brule_id%7D\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
    patch:
      consumes:
        - application/json
      description: >-
        Flips the enabled flag. Preserves config so re-enabling later restores
        the rule exactly as configured.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Custom rule ID (UUID)
          in: path
          name: rule_id
          required: true
          type: string
        - description: Desired enabled state
          in: body
          name: body
          required: true
          schema:
            properties:
              enabled:
                type: boolean
            type: object
      responses:
        '204':
          description: No Content
        '400':
          description: invalid body
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '403':
          description: owner/admin role required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '404':
          description: custom rule not found
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Toggle a custom rule
      tags:
        - policy
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request PATCH \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/custom-rules/%7Brule_id%7D \
              --header 'X-API-Key: REPLACE_KEY_VALUE' \
              --header 'accept: application/json' \
              --header 'content-type: application/json' \
              --data '{"enabled":true}'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/custom-rules/%7Brule_id%7D"


            payload = {"enabled": True}

            headers = {
                "accept": "application/json",
                "X-API-Key": "REPLACE_KEY_VALUE",
                "content-type": "application/json"
            }


            response = requests.request("PATCH", url, json=payload,
            headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {
              method: 'PATCH',
              headers: {
                accept: 'application/json',
                'X-API-Key': 'REPLACE_KEY_VALUE',
                'content-type': 'application/json'
              },
              body: '{"enabled":true}'
            };


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/custom-rules/%7Brule_id%7D',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/custom-rules/%7Brule_id%7D\"\n\n\tpayload := strings.NewReader(\"{\\\"enabled\\\":true}\")\n\n\treq, _ := http.NewRequest(\"PATCH\", url, payload)\n\n\treq.Header.Add(\"accept\", \"application/json\")\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\treq.Header.Add(\"content-type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
    put:
      consumes:
        - application/json
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Custom rule ID (UUID)
          in: path
          name: rule_id
          required: true
          type: string
        - description: Updated fields
          in: body
          name: body
          required: true
          schema:
            properties:
              config:
                items:
                  type: integer
                type: array
              description:
                type: string
              name:
                type: string
            required:
              - config
              - description
              - name
            type: object
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.CustomRuleDTO'
        '400':
          description: invalid body or config schema
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '403':
          description: owner/admin role required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '404':
          description: custom rule not found
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Update a custom rule
      tags:
        - policy
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request PUT \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/custom-rules/%7Brule_id%7D \
              --header 'X-API-Key: REPLACE_KEY_VALUE' \
              --header 'accept: application/json' \
              --header 'content-type: application/json' \
              --data '{"config":[0],"description":"string","name":"string"}'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/custom-rules/%7Brule_id%7D"


            payload = {
                "config": [0],
                "description": "string",
                "name": "string"
            }

            headers = {
                "accept": "application/json",
                "X-API-Key": "REPLACE_KEY_VALUE",
                "content-type": "application/json"
            }


            response = requests.request("PUT", url, json=payload,
            headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {
              method: 'PUT',
              headers: {
                accept: 'application/json',
                'X-API-Key': 'REPLACE_KEY_VALUE',
                'content-type': 'application/json'
              },
              body: '{"config":[0],"description":"string","name":"string"}'
            };


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/custom-rules/%7Brule_id%7D',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/custom-rules/%7Brule_id%7D\"\n\n\tpayload := strings.NewReader(\"{\\\"config\\\":[0],\\\"description\\\":\\\"string\\\",\\\"name\\\":\\\"string\\\"}\")\n\n\treq, _ := http.NewRequest(\"PUT\", url, payload)\n\n\treq.Header.Add(\"accept\", \"application/json\")\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\treq.Header.Add(\"content-type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/disabled-rules:
    get:
      description: >-
        Returns the org's exception list against the built-in rule catalog.
        Optional rule_type narrows to "noise" or "risk".
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Filter by rule type
          enum:
            - noise
            - risk
          in: query
          name: rule_type
          type: string
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.DisabledBuiltinRuleListResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: List disabled built-in rules
      tags:
        - policy
      x-codeSamples:
        - lang: cURL
          source: |-
            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'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/disabled-rules"


            querystring = {"rule_type":"SOME_STRING_VALUE"}


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("GET", url, headers=headers,
            params=querystring)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/disabled-rules?rule_type=SOME_STRING_VALUE',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/disabled-rules?rule_type=SOME_STRING_VALUE\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
    post:
      consumes:
        - application/json
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Which rule + why
          in: body
          name: body
          required: true
          schema:
            properties:
              builtin_rule_id:
                type: string
              reason:
                type: string
              rule_type:
                enum:
                  - noise
                  - risk
                type: string
            required:
              - builtin_rule_id
              - rule_type
            type: object
      produces:
        - application/json
      responses:
        '201':
          description: Created
          schema:
            $ref: '#/definitions/internal_api.DisabledBuiltinRuleDTO'
        '400':
          description: invalid body
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '403':
          description: >-
            owner/admin role required (message: role required) OR authenticated
            user identity required (message: user identity required for audit
            trail) — emitted as two distinct bodies so clients can discriminate
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Disable a built-in rule
      tags:
        - policy
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request POST \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/disabled-rules \
              --header 'X-API-Key: REPLACE_KEY_VALUE' \
              --header 'accept: application/json' \
              --header 'content-type: application/json' \
              --data '{"builtin_rule_id":"string","reason":"string","rule_type":"noise"}'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/disabled-rules"


            payload = {
                "builtin_rule_id": "string",
                "reason": "string",
                "rule_type": "noise"
            }

            headers = {
                "accept": "application/json",
                "X-API-Key": "REPLACE_KEY_VALUE",
                "content-type": "application/json"
            }


            response = requests.request("POST", url, json=payload,
            headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {
              method: 'POST',
              headers: {
                accept: 'application/json',
                'X-API-Key': 'REPLACE_KEY_VALUE',
                'content-type': 'application/json'
              },
              body: '{"builtin_rule_id":"string","reason":"string","rule_type":"noise"}'
            };


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/disabled-rules',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/disabled-rules\"\n\n\tpayload := strings.NewReader(\"{\\\"builtin_rule_id\\\":\\\"string\\\",\\\"reason\\\":\\\"string\\\",\\\"rule_type\\\":\\\"noise\\\"}\")\n\n\treq, _ := http.NewRequest(\"POST\", url, payload)\n\n\treq.Header.Add(\"accept\", \"application/json\")\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\treq.Header.Add(\"content-type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/disabled-rules/{disabled_rule_id}:
    delete:
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Disabled rule record ID (UUID)
          in: path
          name: disabled_rule_id
          required: true
          type: string
      responses:
        '204':
          description: No Content
        '403':
          description: owner/admin role required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '404':
          description: disabled rule not found
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Re-enable a built-in rule
      tags:
        - policy
      x-codeSamples:
        - lang: cURL
          source: |-
            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'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/disabled-rules/%7Bdisabled_rule_id%7D"


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("DELETE", url, headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'DELETE', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/disabled-rules/%7Bdisabled_rule_id%7D',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/disabled-rules/%7Bdisabled_rule_id%7D\"\n\n\treq, _ := http.NewRequest(\"DELETE\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/github-installations:
    get:
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: max installations to return (default 100, max 500)
          in: query
          name: limit
          type: integer
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.GitHubInstallationListResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: List GitHub installations
      tags:
        - webhooks
      x-codeSamples:
        - lang: cURL
          source: |-
            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'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/github-installations"


            querystring = {"limit":"SOME_INTEGER_VALUE"}


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("GET", url, headers=headers,
            params=querystring)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/github-installations?limit=SOME_INTEGER_VALUE',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/github-installations?limit=SOME_INTEGER_VALUE\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
    post:
      consumes:
        - application/json
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: GitHub installation ID + account login
          in: body
          name: body
          required: true
          schema:
            properties:
              account_login:
                type: string
              installation_id:
                type: integer
            required:
              - account_login
              - installation_id
            type: object
      produces:
        - application/json
      responses:
        '201':
          description: Created
          schema:
            $ref: '#/definitions/internal_api.GitHubInstallationResponse'
        '400':
          description: invalid body
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '403':
          description: owner/admin role required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '409':
          description: installation already linked
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Link a GitHub App installation
      tags:
        - webhooks
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request POST \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/github-installations \
              --header 'X-API-Key: REPLACE_KEY_VALUE' \
              --header 'accept: application/json' \
              --header 'content-type: application/json' \
              --data '{"account_login":"string","installation_id":0}'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/github-installations"


            payload = {
                "account_login": "string",
                "installation_id": 0
            }

            headers = {
                "accept": "application/json",
                "X-API-Key": "REPLACE_KEY_VALUE",
                "content-type": "application/json"
            }


            response = requests.request("POST", url, json=payload,
            headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {
              method: 'POST',
              headers: {
                accept: 'application/json',
                'X-API-Key': 'REPLACE_KEY_VALUE',
                'content-type': 'application/json'
              },
              body: '{"account_login":"string","installation_id":0}'
            };


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/github-installations',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/github-installations\"\n\n\tpayload := strings.NewReader(\"{\\\"account_login\\\":\\\"string\\\",\\\"installation_id\\\":0}\")\n\n\treq, _ := http.NewRequest(\"POST\", url, payload)\n\n\treq.Header.Add(\"accept\", \"application/json\")\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\treq.Header.Add(\"content-type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/github-installations/{install_id}:
    delete:
      description: >-
        Removes the DriftWise-side record. Does not uninstall the App from
        GitHub — the user must do that separately via GitHub's UI.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Installation link ID (UUID)
          in: path
          name: install_id
          required: true
          type: string
      responses:
        '204':
          description: No Content
        '403':
          description: owner/admin role required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '404':
          description: github installation not found
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Unlink a GitHub installation
      tags:
        - webhooks
      x-codeSamples:
        - lang: cURL
          source: |-
            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'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/github-installations/%7Binstall_id%7D"


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("DELETE", url, headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'DELETE', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/github-installations/%7Binstall_id%7D',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/github-installations/%7Binstall_id%7D\"\n\n\treq, _ := http.NewRequest(\"DELETE\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/llm-config:
    delete:
      description: >-
        Hard delete. Subsequent LLM calls fall back to the platform default +
        its quota gate (weekly/hourly). 404 if no row exists.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
      responses:
        '204':
          description: No Content
        '403':
          description: owner/admin role required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '404':
          description: no BYOK configuration to delete
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Delete BYOK LLM configuration
      tags:
        - llm
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request DELETE \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/llm-config \
              --header 'X-API-Key: REPLACE_KEY_VALUE'
        - lang: Python
          source: |-
            import requests

            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/llm-config"

            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}

            response = requests.request("DELETE", url, headers=headers)

            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'DELETE', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/llm-config',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/llm-config\"\n\n\treq, _ := http.NewRequest(\"DELETE\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
    get:
      description: >-
        Returns only metadata — provider name + timestamps. Credentials never
        exposed: the raw api_key / aws_secret_access_key / azure_api_key are
        write-only.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.llmConfigPublic'
        '403':
          description: not a member of this org
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '404':
          description: no BYOK configuration for this organization
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Get BYOK LLM configuration
      tags:
        - llm
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request GET \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/llm-config \
              --header 'X-API-Key: REPLACE_KEY_VALUE'
        - lang: Python
          source: |-
            import requests

            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/llm-config"

            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}

            response = requests.request("GET", url, headers=headers)

            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/llm-config',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/llm-config\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
    put:
      consumes:
        - application/json
      description: >-
        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).
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: BYOK credentials
          in: body
          name: body
          required: true
          schema:
            properties:
              api_key:
                description: Anthropic / OpenAI / Gemini
                type: string
              aws_access_key_id:
                type: string
              aws_region:
                description: AWS Bedrock
                type: string
              aws_role_arn:
                type: string
              aws_secret_access_key:
                type: string
              aws_session_token:
                type: string
              azure_api_key:
                type: string
              azure_api_version:
                type: string
              azure_deployment:
                type: string
              azure_endpoint:
                description: Azure OpenAI
                type: string
              model:
                type: string
              provider:
                type: string
            type: object
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.llmConfigPublic'
        '400':
          description: >-
            invalid body, unknown fields, or provider-specific validation
            failure
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '403':
          description: owner/admin role required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '503':
          description: no encryption key configured
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Replace BYOK LLM configuration
      tags:
        - llm
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request PUT \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/llm-config \
              --header 'X-API-Key: REPLACE_KEY_VALUE' \
              --header 'accept: application/json' \
              --header 'content-type: application/json' \
              --data '{"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"}'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/llm-config"


            payload = {
                "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"
            }

            headers = {
                "accept": "application/json",
                "X-API-Key": "REPLACE_KEY_VALUE",
                "content-type": "application/json"
            }


            response = requests.request("PUT", url, json=payload,
            headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {
              method: 'PUT',
              headers: {
                accept: 'application/json',
                'X-API-Key': 'REPLACE_KEY_VALUE',
                'content-type': 'application/json'
              },
              body: '{"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"}'
            };


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/llm-config',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/llm-config\"\n\n\tpayload := strings.NewReader(\"{\\\"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\\\"}\")\n\n\treq, _ := http.NewRequest(\"PUT\", url, payload)\n\n\treq.Header.Add(\"accept\", \"application/json\")\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\treq.Header.Add(\"content-type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/llm-usage:
    get:
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.llmUsageResponse'
        '403':
          description: not a member of this org
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Get LLM usage + caps
      tags:
        - llm
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request GET \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/llm-usage \
              --header 'X-API-Key: REPLACE_KEY_VALUE'
        - lang: Python
          source: |-
            import requests

            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/llm-usage"

            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}

            response = requests.request("GET", url, headers=headers)

            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/llm-usage',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/llm-usage\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/memberships/{membership_id}/role:
    patch:
      consumes:
        - application/json
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Membership ID (UUID) — must belong to the org in the URL
          in: path
          name: membership_id
          required: true
          type: string
        - description: New role
          in: body
          name: body
          required: true
          schema:
            properties:
              role:
                enum:
                  - owner
                  - admin
                  - member
                  - viewer
                example: admin
                type: string
            required:
              - role
            type: object
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.updateMembershipRoleResponse'
        '400':
          description: invalid body or role
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '403':
          description: owner required or API key used
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '404':
          description: membership not found or in another org
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '409':
          description: self-demotion or last owner
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
      summary: Change a team member's role
      tags:
        - admin
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request PATCH \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/memberships/%7Bmembership_id%7D/role \
              --header 'Authorization: REPLACE_KEY_VALUE' \
              --header 'accept: application/json' \
              --header 'content-type: application/json' \
              --data '{"role":"admin"}'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/memberships/%7Bmembership_id%7D/role"


            payload = {"role": "admin"}

            headers = {
                "accept": "application/json",
                "Authorization": "REPLACE_KEY_VALUE",
                "content-type": "application/json"
            }


            response = requests.request("PATCH", url, json=payload,
            headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {
              method: 'PATCH',
              headers: {
                accept: 'application/json',
                Authorization: 'REPLACE_KEY_VALUE',
                'content-type': 'application/json'
              },
              body: '{"role":"admin"}'
            };


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/memberships/%7Bmembership_id%7D/role',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/memberships/%7Bmembership_id%7D/role\"\n\n\tpayload := strings.NewReader(\"{\\\"role\\\":\\\"admin\\\"}\")\n\n\treq, _ := http.NewRequest(\"PATCH\", url, payload)\n\n\treq.Header.Add(\"accept\", \"application/json\")\n\treq.Header.Add(\"Authorization\", \"REPLACE_KEY_VALUE\")\n\treq.Header.Add(\"content-type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/plan-noise:
    get:
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Filter by repo owner
          in: query
          name: repo_owner
          type: string
        - description: Filter by repo name
          in: query
          name: repo_name
          type: string
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.PlanNoiseListResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: List plan-noise patterns
      tags:
        - policy
      x-codeSamples:
        - lang: cURL
          source: |-
            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'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/plan-noise"


            querystring =
            {"repo_owner":"SOME_STRING_VALUE","repo_name":"SOME_STRING_VALUE"}


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("GET", url, headers=headers,
            params=querystring)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/plan-noise?repo_owner=SOME_STRING_VALUE&repo_name=SOME_STRING_VALUE',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/plan-noise?repo_owner=SOME_STRING_VALUE&repo_name=SOME_STRING_VALUE\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/plan-noise/{fingerprint}/fix:
    post:
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Pattern fingerprint (hash of resource+attribute+action)
          in: path
          name: fingerprint
          required: true
          type: string
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.GenerateFixResponse'
        '402':
          description: platform LLM quota exhausted
          schema:
            $ref: '#/definitions/internal_api.PaymentRequiredResponse'
        '404':
          description: no signals found for this fingerprint
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '429':
          description: BYOK failure throttle or platform hourly limit
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: fix generation failed
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '503':
          description: LLM service not configured
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Generate fix recommendations
      tags:
        - policy
      x-codeSamples:
        - lang: cURL
          source: |-
            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'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/plan-noise/%7Bfingerprint%7D/fix"


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("POST", url, headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'POST', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/plan-noise/%7Bfingerprint%7D/fix',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/plan-noise/%7Bfingerprint%7D/fix\"\n\n\treq, _ := http.NewRequest(\"POST\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/plan-noise/settings:
    get:
      description: >-
        Returns recurrence_threshold (minimum occurrences to flag) and
        window_days (lookback period). Defaults applied server-side when the org
        has never customized.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.PlanNoiseSettingsDTO'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Get plan-noise settings
      tags:
        - policy
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request GET \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/plan-noise/settings \
              --header 'X-API-Key: REPLACE_KEY_VALUE'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/plan-noise/settings"


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("GET", url, headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/plan-noise/settings',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/plan-noise/settings\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
    put:
      consumes:
        - application/json
      description: >-
        Replaces recurrence_threshold and window_days. Takes effect on the next
        ListPlanNoise call — no re-aggregation job triggered.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: New thresholds
          in: body
          name: body
          required: true
          schema:
            properties:
              recurrence_threshold:
                minimum: 1
                type: integer
              window_days:
                minimum: 1
                type: integer
            required:
              - recurrence_threshold
              - window_days
            type: object
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.PlanNoiseSettingsDTO'
        '400':
          description: invalid body
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '403':
          description: owner/admin role required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Update plan-noise settings
      tags:
        - policy
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request PUT \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/plan-noise/settings \
              --header 'X-API-Key: REPLACE_KEY_VALUE' \
              --header 'accept: application/json' \
              --header 'content-type: application/json' \
              --data '{"recurrence_threshold":1,"window_days":1}'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/plan-noise/settings"


            payload = {
                "recurrence_threshold": 1,
                "window_days": 1
            }

            headers = {
                "accept": "application/json",
                "X-API-Key": "REPLACE_KEY_VALUE",
                "content-type": "application/json"
            }


            response = requests.request("PUT", url, json=payload,
            headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {
              method: 'PUT',
              headers: {
                accept: 'application/json',
                'X-API-Key': 'REPLACE_KEY_VALUE',
                'content-type': 'application/json'
              },
              body: '{"recurrence_threshold":1,"window_days":1}'
            };


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/plan-noise/settings',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/plan-noise/settings\"\n\n\tpayload := strings.NewReader(\"{\\\"recurrence_threshold\\\":1,\\\"window_days\\\":1}\")\n\n\treq, _ := http.NewRequest(\"PUT\", url, payload)\n\n\treq.Header.Add(\"accept\", \"application/json\")\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\treq.Header.Add(\"content-type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/plan-noise/suppress:
    post:
      consumes:
        - application/json
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Fingerprints + addresses + duration
          in: body
          name: body
          required: true
          schema:
            properties:
              duration:
                enum:
                  - 7d
                  - 30d
                  - 90d
                  - forever
                example: 7d
                type: string
              fingerprints:
                items:
                  type: string
                maxItems: 100
                minItems: 1
                type: array
              is_false_positive:
                type: boolean
              reason:
                type: string
              resource_addresses:
                items:
                  type: string
                maxItems: 100
                minItems: 1
                type: array
            required:
              - duration
              - fingerprints
              - reason
              - resource_addresses
            type: object
      produces:
        - application/json
      responses:
        '201':
          description: Created
          schema:
            $ref: '#/definitions/internal_api.SuppressionListResponse'
        '400':
          description: invalid body or array length mismatch
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '403':
          description: >-
            owner/admin role required (message: role required) OR authenticated
            user identity required (message: user identity required for audit
            trail) — emitted as two distinct bodies so clients can discriminate
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Suppress plan-noise fingerprints
      tags:
        - policy
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request POST \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/plan-noise/suppress \
              --header 'X-API-Key: REPLACE_KEY_VALUE' \
              --header 'accept: application/json' \
              --header 'content-type: application/json' \
              --data '{"duration":"7d","fingerprints":["string"],"is_false_positive":true,"reason":"string","resource_addresses":["string"]}'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/plan-noise/suppress"


            payload = {
                "duration": "7d",
                "fingerprints": ["string"],
                "is_false_positive": True,
                "reason": "string",
                "resource_addresses": ["string"]
            }

            headers = {
                "accept": "application/json",
                "X-API-Key": "REPLACE_KEY_VALUE",
                "content-type": "application/json"
            }


            response = requests.request("POST", url, json=payload,
            headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {
              method: 'POST',
              headers: {
                accept: 'application/json',
                'X-API-Key': 'REPLACE_KEY_VALUE',
                'content-type': 'application/json'
              },
              body: '{"duration":"7d","fingerprints":["string"],"is_false_positive":true,"reason":"string","resource_addresses":["string"]}'
            };


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/plan-noise/suppress',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/plan-noise/suppress\"\n\n\tpayload := strings.NewReader(\"{\\\"duration\\\":\\\"7d\\\",\\\"fingerprints\\\":[\\\"string\\\"],\\\"is_false_positive\\\":true,\\\"reason\\\":\\\"string\\\",\\\"resource_addresses\\\":[\\\"string\\\"]}\")\n\n\treq, _ := http.NewRequest(\"POST\", url, payload)\n\n\treq.Header.Add(\"accept\", \"application/json\")\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\treq.Header.Add(\"content-type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/plan-noise/suppressions:
    get:
      description: >-
        Returns active suppressions. Pass include_expired=true to include
        suppressions past their expiry (useful for audit). Not paginated.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Include already-expired suppressions
          in: query
          name: include_expired
          type: boolean
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.SuppressionListResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: List plan-noise suppressions
      tags:
        - policy
      x-codeSamples:
        - lang: cURL
          source: |-
            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'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/plan-noise/suppressions"


            querystring = {"include_expired":"SOME_BOOLEAN_VALUE"}


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("GET", url, headers=headers,
            params=querystring)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/plan-noise/suppressions?include_expired=SOME_BOOLEAN_VALUE',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/plan-noise/suppressions?include_expired=SOME_BOOLEAN_VALUE\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/plan-noise/suppressions/{suppression_id}:
    delete:
      description: >-
        Restores visibility of the suppressed drift. Already-deleted
        suppressions return 404.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Suppression ID (UUID)
          in: path
          name: suppression_id
          required: true
          type: string
      responses:
        '204':
          description: No Content
        '403':
          description: owner/admin role required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '404':
          description: suppression not found
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Remove a plan-noise suppression
      tags:
        - policy
      x-codeSamples:
        - lang: cURL
          source: |-
            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'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/plan-noise/suppressions/%7Bsuppression_id%7D"


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("DELETE", url, headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'DELETE', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/plan-noise/suppressions/%7Bsuppression_id%7D',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/plan-noise/suppressions/%7Bsuppression_id%7D\"\n\n\treq, _ := http.NewRequest(\"DELETE\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/policy:
    get:
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/github_com_driftwise_backend_internal_policy.Policy'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Get risk policy
      tags:
        - policy
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request GET \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/policy \
              --header 'X-API-Key: REPLACE_KEY_VALUE'
        - lang: Python
          source: |-
            import requests

            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/policy"

            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}

            response = requests.request("GET", url, headers=headers)

            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/policy',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/policy\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
    put:
      consumes:
        - application/json
      description: >-
        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).
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Policy rules + version
          in: body
          name: body
          required: true
          schema:
            properties:
              rules:
                items:
                  $ref: >-
                    #/definitions/github_com_driftwise_backend_internal_policy.PolicyRule
                type: array
              updated_at:
                type: string
              version:
                type: integer
            type: object
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/github_com_driftwise_backend_internal_policy.Policy'
        '400':
          description: invalid body
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '403':
          description: owner/admin role required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '422':
          description: policy validation failed
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Replace risk policy
      tags:
        - policy
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request PUT \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/policy \
              --header 'X-API-Key: REPLACE_KEY_VALUE' \
              --header 'accept: application/json' \
              --header 'content-type: application/json' \
              --data '{"rules":[{"flag_pattern":"string","id":"string","reason":"string","resource_pattern":"string","severity":"string"}],"updated_at":"string","version":0}'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/policy"


            payload = {
                "rules": [
                    {
                        "flag_pattern": "string",
                        "id": "string",
                        "reason": "string",
                        "resource_pattern": "string",
                        "severity": "string"
                    }
                ],
                "updated_at": "string",
                "version": 0
            }

            headers = {
                "accept": "application/json",
                "X-API-Key": "REPLACE_KEY_VALUE",
                "content-type": "application/json"
            }


            response = requests.request("PUT", url, json=payload,
            headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {
              method: 'PUT',
              headers: {
                accept: 'application/json',
                'X-API-Key': 'REPLACE_KEY_VALUE',
                'content-type': 'application/json'
              },
              body: '{"rules":[{"flag_pattern":"string","id":"string","reason":"string","resource_pattern":"string","severity":"string"}],"updated_at":"string","version":0}'
            };


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/policy',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/policy\"\n\n\tpayload := strings.NewReader(\"{\\\"rules\\\":[{\\\"flag_pattern\\\":\\\"string\\\",\\\"id\\\":\\\"string\\\",\\\"reason\\\":\\\"string\\\",\\\"resource_pattern\\\":\\\"string\\\",\\\"severity\\\":\\\"string\\\"}],\\\"updated_at\\\":\\\"string\\\",\\\"version\\\":0}\")\n\n\treq, _ := http.NewRequest(\"PUT\", url, payload)\n\n\treq.Header.Add(\"accept\", \"application/json\")\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\treq.Header.Add(\"content-type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/posture:
    get:
      description: >-
        Per-account coverage percentage, risk level, and undeclared-resource
        count aggregated over the latest scan/drift data. Drives the dashboard's
        posture card.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.PostureResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Get posture summary
      tags:
        - drift
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request GET \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/posture \
              --header 'X-API-Key: REPLACE_KEY_VALUE'
        - lang: Python
          source: |-
            import requests

            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/posture"

            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}

            response = requests.request("GET", url, headers=headers)

            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/posture',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/posture\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/resources:
    get:
      description: >-
        Paginated list of cloud resources discovered by recent scans. Optional
        account_id narrows to one cloud account.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Filter by cloud account ID
          in: query
          name: account_id
          type: string
        - description: page size (default 100, max 500)
          in: query
          name: limit
          type: integer
        - description: page offset
          in: query
          name: offset
          type: integer
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.ResourceListResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: List live resources
      tags:
        - accounts
      x-codeSamples:
        - lang: cURL
          source: |-
            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'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/resources"


            querystring =
            {"account_id":"SOME_STRING_VALUE","limit":"SOME_INTEGER_VALUE","offset":"SOME_INTEGER_VALUE"}


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("GET", url, headers=headers,
            params=querystring)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/resources?account_id=SOME_STRING_VALUE&limit=SOME_INTEGER_VALUE&offset=SOME_INTEGER_VALUE',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/resources?account_id=SOME_STRING_VALUE&limit=SOME_INTEGER_VALUE&offset=SOME_INTEGER_VALUE\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/resources/{resource_id}/enrich:
    post:
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Live resource ID (UUID)
          in: path
          name: resource_id
          required: true
          type: string
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.EnrichedResourceResponse'
        '403':
          description: owner/admin role required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '404':
          description: resource not found
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '422':
          description: enrichment failed (reason in body)
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '503':
          description: cloud registry not configured
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Enrich one resource
      tags:
        - accounts
      x-codeSamples:
        - lang: cURL
          source: |-
            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'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/resources/%7Bresource_id%7D/enrich"


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("POST", url, headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'POST', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/resources/%7Bresource_id%7D/enrich',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/resources/%7Bresource_id%7D/enrich\"\n\n\treq, _ := http.NewRequest(\"POST\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/scans:
    get:
      description: >-
        Paginated scan history, filtered by retention window (free: 24h, team+:
        unlimited). Optional account_id and scan_type narrow results.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Filter by cloud account ID
          in: query
          name: account_id
          type: string
        - description: Filter by scan type
          enum:
            - manual
            - scheduled
            - webhook
          in: query
          name: scan_type
          type: string
        - description: page size (default 50, max 200)
          in: query
          name: limit
          type: integer
        - description: page offset
          in: query
          name: offset
          type: integer
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.ScanListResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: List scan runs
      tags:
        - scans
      x-codeSamples:
        - lang: cURL
          source: |-
            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'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/scans"


            querystring =
            {"account_id":"SOME_STRING_VALUE","scan_type":"SOME_STRING_VALUE","limit":"SOME_INTEGER_VALUE","offset":"SOME_INTEGER_VALUE"}


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("GET", url, headers=headers,
            params=querystring)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('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',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"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\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
    post:
      consumes:
        - application/json
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: 'Scan config: cloud account, region filters, CI metadata'
          in: body
          name: body
          required: true
          schema:
            properties:
              ci:
                $ref: '#/definitions/internal_api.CIMetadataRequest'
              cloud_account_id:
                type: string
              filter_regions:
                items:
                  type: string
                type: array
            required:
              - cloud_account_id
            type: object
      produces:
        - application/json
      responses:
        '202':
          description: Accepted
          schema:
            $ref: '#/definitions/internal_api.ScanRunResponse'
        '400':
          description: invalid body or CI metadata
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '403':
          description: owner/admin role required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '404':
          description: cloud account not found
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Queue a new scan run
      tags:
        - scans
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request POST \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/scans \
              --header 'X-API-Key: REPLACE_KEY_VALUE' \
              --header 'accept: application/json' \
              --header 'content-type: application/json' \
              --data '{"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"]}'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/scans"


            payload = {
                "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"]
            }

            headers = {
                "accept": "application/json",
                "X-API-Key": "REPLACE_KEY_VALUE",
                "content-type": "application/json"
            }


            response = requests.request("POST", url, json=payload,
            headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {
              method: 'POST',
              headers: {
                accept: 'application/json',
                'X-API-Key': 'REPLACE_KEY_VALUE',
                'content-type': 'application/json'
              },
              body: '{"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"]}'
            };


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/scans',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/scans\"\n\n\tpayload := strings.NewReader(\"{\\\"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\\\"]}\")\n\n\treq, _ := http.NewRequest(\"POST\", url, payload)\n\n\treq.Header.Add(\"accept\", \"application/json\")\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\treq.Header.Add(\"content-type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/scans/{scan_id}:
    get:
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Scan run ID (UUID)
          in: path
          name: scan_id
          required: true
          type: string
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.ScanRunResponse'
        '404':
          description: scan not found
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Get a scan run
      tags:
        - scans
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request GET \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/scans/%7Bscan_id%7D \
              --header 'X-API-Key: REPLACE_KEY_VALUE'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/scans/%7Bscan_id%7D"


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("GET", url, headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/scans/%7Bscan_id%7D',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/scans/%7Bscan_id%7D\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/scans/{scan_id}/drift:
    get:
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Scan run ID (UUID)
          in: path
          name: scan_id
          required: true
          type: string
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.DriftSnapshotResponse'
        '404':
          description: drift results not found for this scan
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Get drift results for a scan
      tags:
        - drift
        - scans
      x-codeSamples:
        - lang: cURL
          source: |-
            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'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/scans/%7Bscan_id%7D/drift"


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("GET", url, headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/scans/%7Bscan_id%7D/drift',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/scans/%7Bscan_id%7D/drift\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/scans/{scan_id}/drift/compute:
    post:
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Scan run ID (UUID)
          in: path
          name: scan_id
          required: true
          type: string
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.DriftSnapshotResponse'
        '400':
          description: scan not complete or unsupported scan_type
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '403':
          description: owner/admin role required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '404':
          description: scan not found
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Compute drift for a scan (sync)
      tags:
        - drift
        - scans
      x-codeSamples:
        - lang: cURL
          source: |-
            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'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/scans/%7Bscan_id%7D/drift/compute"


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("POST", url, headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'POST', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/scans/%7Bscan_id%7D/drift/compute',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/scans/%7Bscan_id%7D/drift/compute\"\n\n\treq, _ := http.NewRequest(\"POST\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/scans/{scan_id}/traces:
    get:
      description: >-
        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}.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Scan run ID (UUID)
          in: path
          name: scan_id
          required: true
          type: string
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            items:
              $ref: >-
                #/definitions/github_com_driftwise_backend_internal_trace.TraceSummary
            type: array
        '404':
          description: scan run not found
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: List LLM traces for a scan
      tags:
        - scans
      x-codeSamples:
        - lang: cURL
          source: |-
            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'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/scans/%7Bscan_id%7D/traces"


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("GET", url, headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/scans/%7Bscan_id%7D/traces',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/scans/%7Bscan_id%7D/traces\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/scans/bulk:
    post:
      consumes:
        - application/json
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Account selection + region filters
          in: body
          name: body
          required: true
          schema:
            properties:
              account_ids:
                description: nil/empty = all accounts
                items:
                  type: string
                type: array
              filter_regions:
                items:
                  type: string
                type: array
            type: object
      produces:
        - application/json
      responses:
        '202':
          description: Accepted
          schema:
            $ref: '#/definitions/internal_api.BulkScanResponse'
        '400':
          description: no cloud accounts configured or invalid body
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '402':
          description: concurrent scan limit
          schema:
            $ref: '#/definitions/internal_api.PaymentRequiredResponse'
        '403':
          description: owner/admin role required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '404':
          description: cloud account not found
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '429':
          description: rate limit exceeded
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Queue bulk scans
      tags:
        - scans
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request POST \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/scans/bulk \
              --header 'X-API-Key: REPLACE_KEY_VALUE' \
              --header 'accept: application/json' \
              --header 'content-type: application/json' \
              --data '{"account_ids":["string"],"filter_regions":["string"]}'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/scans/bulk"


            payload = {
                "account_ids": ["string"],
                "filter_regions": ["string"]
            }

            headers = {
                "accept": "application/json",
                "X-API-Key": "REPLACE_KEY_VALUE",
                "content-type": "application/json"
            }


            response = requests.request("POST", url, json=payload,
            headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {
              method: 'POST',
              headers: {
                accept: 'application/json',
                'X-API-Key': 'REPLACE_KEY_VALUE',
                'content-type': 'application/json'
              },
              body: '{"account_ids":["string"],"filter_regions":["string"]}'
            };


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/scans/bulk',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/scans/bulk\"\n\n\tpayload := strings.NewReader(\"{\\\"account_ids\\\":[\\\"string\\\"],\\\"filter_regions\\\":[\\\"string\\\"]}\")\n\n\treq, _ := http.NewRequest(\"POST\", url, payload)\n\n\treq.Header.Add(\"accept\", \"application/json\")\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\treq.Header.Add(\"content-type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/schedules:
    get:
      description: >-
        Paginated list. Disabled schedules (by user or auto-disable after too
        many consecutive failures) are included — check the `enabled` and
        `disabled_reason` fields.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: page size (default 50, max 200)
          in: query
          name: limit
          type: integer
        - description: page offset
          in: query
          name: offset
          type: integer
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.ScheduledScanListResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: List scheduled scans
      tags:
        - scans
      x-codeSamples:
        - lang: cURL
          source: |-
            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'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/schedules"


            querystring =
            {"limit":"SOME_INTEGER_VALUE","offset":"SOME_INTEGER_VALUE"}


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("GET", url, headers=headers,
            params=querystring)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/schedules?limit=SOME_INTEGER_VALUE&offset=SOME_INTEGER_VALUE',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/schedules?limit=SOME_INTEGER_VALUE&offset=SOME_INTEGER_VALUE\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
    post:
      consumes:
        - application/json
      description: >-
        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).
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Schedule config
          in: body
          name: body
          required: true
          schema:
            properties:
              cloud_account_id:
                description: nil = all accounts
                type: string
              enabled:
                description: default true
                type: boolean
              filter_regions:
                items:
                  type: string
                type: array
              name:
                type: string
              notify_slack_channel:
                type: string
              notify_webhook_config_ids:
                items:
                  type: string
                type: array
              schedule:
                type: string
            required:
              - name
              - schedule
            type: object
      produces:
        - application/json
      responses:
        '201':
          description: Created
          schema:
            $ref: '#/definitions/internal_api.ScheduledScanResponse'
        '400':
          description: invalid body or cron expression
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '402':
          description: schedule count or frequency limit
          schema:
            $ref: '#/definitions/internal_api.PaymentRequiredResponse'
        '403':
          description: owner/admin role required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '404':
          description: cloud account not found
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Create a scheduled scan
      tags:
        - scans
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request POST \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/schedules \
              --header 'X-API-Key: REPLACE_KEY_VALUE' \
              --header 'accept: application/json' \
              --header 'content-type: application/json' \
              --data '{"cloud_account_id":"string","enabled":true,"filter_regions":["string"],"name":"string","notify_slack_channel":"string","notify_webhook_config_ids":["string"],"schedule":"string"}'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/schedules"


            payload = {
                "cloud_account_id": "string",
                "enabled": True,
                "filter_regions": ["string"],
                "name": "string",
                "notify_slack_channel": "string",
                "notify_webhook_config_ids": ["string"],
                "schedule": "string"
            }

            headers = {
                "accept": "application/json",
                "X-API-Key": "REPLACE_KEY_VALUE",
                "content-type": "application/json"
            }


            response = requests.request("POST", url, json=payload,
            headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {
              method: 'POST',
              headers: {
                accept: 'application/json',
                'X-API-Key': 'REPLACE_KEY_VALUE',
                'content-type': 'application/json'
              },
              body: '{"cloud_account_id":"string","enabled":true,"filter_regions":["string"],"name":"string","notify_slack_channel":"string","notify_webhook_config_ids":["string"],"schedule":"string"}'
            };


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/schedules',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/schedules\"\n\n\tpayload := strings.NewReader(\"{\\\"cloud_account_id\\\":\\\"string\\\",\\\"enabled\\\":true,\\\"filter_regions\\\":[\\\"string\\\"],\\\"name\\\":\\\"string\\\",\\\"notify_slack_channel\\\":\\\"string\\\",\\\"notify_webhook_config_ids\\\":[\\\"string\\\"],\\\"schedule\\\":\\\"string\\\"}\")\n\n\treq, _ := http.NewRequest(\"POST\", url, payload)\n\n\treq.Header.Add(\"accept\", \"application/json\")\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\treq.Header.Add(\"content-type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/schedules/{sid}:
    delete:
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Schedule ID (UUID)
          in: path
          name: sid
          required: true
          type: string
      responses:
        '204':
          description: No Content
        '403':
          description: owner/admin role required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '404':
          description: scheduled scan not found
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Delete a scheduled scan
      tags:
        - scans
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request DELETE \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/schedules/%7Bsid%7D \
              --header 'X-API-Key: REPLACE_KEY_VALUE'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/schedules/%7Bsid%7D"


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("DELETE", url, headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'DELETE', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/schedules/%7Bsid%7D',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/schedules/%7Bsid%7D\"\n\n\treq, _ := http.NewRequest(\"DELETE\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
    get:
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Schedule ID (UUID)
          in: path
          name: sid
          required: true
          type: string
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.ScheduledScanResponse'
        '404':
          description: scheduled scan not found
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Get a scheduled scan
      tags:
        - scans
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request GET \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/schedules/%7Bsid%7D \
              --header 'X-API-Key: REPLACE_KEY_VALUE'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/schedules/%7Bsid%7D"


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("GET", url, headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/schedules/%7Bsid%7D',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/schedules/%7Bsid%7D\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
    put:
      consumes:
        - application/json
      description: >-
        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).
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Schedule ID (UUID)
          in: path
          name: sid
          required: true
          type: string
        - description: Fields to update
          in: body
          name: body
          required: true
          schema:
            properties:
              cloud_account_id:
                type: string
              enabled:
                type: boolean
              filter_regions:
                items:
                  type: string
                type: array
              name:
                type: string
              notify_slack_channel:
                type: string
              notify_webhook_config_ids:
                items:
                  type: string
                type: array
              schedule:
                type: string
            type: object
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.ScheduledScanResponse'
        '400':
          description: invalid body or cron expression
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '402':
          description: schedule frequency limit
          schema:
            $ref: '#/definitions/internal_api.PaymentRequiredResponse'
        '403':
          description: owner/admin role required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '404':
          description: scheduled scan not found
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Update a scheduled scan
      tags:
        - scans
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request PUT \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/schedules/%7Bsid%7D \
              --header 'X-API-Key: REPLACE_KEY_VALUE' \
              --header 'accept: application/json' \
              --header 'content-type: application/json' \
              --data '{"cloud_account_id":"string","enabled":true,"filter_regions":["string"],"name":"string","notify_slack_channel":"string","notify_webhook_config_ids":["string"],"schedule":"string"}'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/schedules/%7Bsid%7D"


            payload = {
                "cloud_account_id": "string",
                "enabled": True,
                "filter_regions": ["string"],
                "name": "string",
                "notify_slack_channel": "string",
                "notify_webhook_config_ids": ["string"],
                "schedule": "string"
            }

            headers = {
                "accept": "application/json",
                "X-API-Key": "REPLACE_KEY_VALUE",
                "content-type": "application/json"
            }


            response = requests.request("PUT", url, json=payload,
            headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {
              method: 'PUT',
              headers: {
                accept: 'application/json',
                'X-API-Key': 'REPLACE_KEY_VALUE',
                'content-type': 'application/json'
              },
              body: '{"cloud_account_id":"string","enabled":true,"filter_regions":["string"],"name":"string","notify_slack_channel":"string","notify_webhook_config_ids":["string"],"schedule":"string"}'
            };


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/schedules/%7Bsid%7D',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/schedules/%7Bsid%7D\"\n\n\tpayload := strings.NewReader(\"{\\\"cloud_account_id\\\":\\\"string\\\",\\\"enabled\\\":true,\\\"filter_regions\\\":[\\\"string\\\"],\\\"name\\\":\\\"string\\\",\\\"notify_slack_channel\\\":\\\"string\\\",\\\"notify_webhook_config_ids\\\":[\\\"string\\\"],\\\"schedule\\\":\\\"string\\\"}\")\n\n\treq, _ := http.NewRequest(\"PUT\", url, payload)\n\n\treq.Header.Add(\"accept\", \"application/json\")\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\treq.Header.Add(\"content-type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/schedules/{sid}/run:
    post:
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Schedule ID (UUID)
          in: path
          name: sid
          required: true
          type: string
      produces:
        - application/json
      responses:
        '202':
          description: Accepted
          schema:
            $ref: '#/definitions/internal_api.BulkScanResponse'
        '400':
          description: no cloud accounts configured
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '402':
          description: concurrent scan limit
          schema:
            $ref: '#/definitions/internal_api.PaymentRequiredResponse'
        '403':
          description: owner/admin role required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '404':
          description: scheduled scan or cloud account not found
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Run a scheduled scan now
      tags:
        - scans
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request POST \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/schedules/%7Bsid%7D/run \
              --header 'X-API-Key: REPLACE_KEY_VALUE'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/schedules/%7Bsid%7D/run"


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("POST", url, headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'POST', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/schedules/%7Bsid%7D/run',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/schedules/%7Bsid%7D/run\"\n\n\treq, _ := http.NewRequest(\"POST\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/slack/install:
    post:
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.slackAuthURLResponse'
        '402':
          description: plan does not include Slack
          schema:
            $ref: '#/definitions/internal_api.PaymentRequiredResponse'
        '403':
          description: owner/admin role required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '503':
          description: Slack integration or encryption not configured
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Start Slack OAuth flow
      tags:
        - slack
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request POST \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/slack/install \
              --header 'X-API-Key: REPLACE_KEY_VALUE'
        - lang: Python
          source: |-
            import requests

            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/slack/install"

            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}

            response = requests.request("POST", url, headers=headers)

            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'POST', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/slack/install',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/slack/install\"\n\n\treq, _ := http.NewRequest(\"POST\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/slack/status:
    get:
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.slackStatusResponse'
        '402':
          description: plan does not include Slack
          schema:
            $ref: '#/definitions/internal_api.PaymentRequiredResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Get Slack installation status
      tags:
        - slack
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request GET \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/slack/status \
              --header 'X-API-Key: REPLACE_KEY_VALUE'
        - lang: Python
          source: |-
            import requests

            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/slack/status"

            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}

            response = requests.request("GET", url, headers=headers)

            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/slack/status',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/slack/status\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/slack/uninstall:
    delete:
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.slackUninstallResponse'
        '403':
          description: owner/admin role required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '404':
          description: no Slack installation found
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '503':
          description: Slack integration not configured
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Uninstall Slack integration
      tags:
        - slack
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request DELETE \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/slack/uninstall \
              --header 'X-API-Key: REPLACE_KEY_VALUE'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/slack/uninstall"


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("DELETE", url, headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'DELETE', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/slack/uninstall',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/slack/uninstall\"\n\n\treq, _ := http.NewRequest(\"DELETE\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/sso-config:
    get:
      description: >-
        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).
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.ssoConfigResponse'
        '402':
          description: plan does not include SSO
          schema:
            $ref: '#/definitions/internal_api.PaymentRequiredResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '502':
          description: identity provider unreachable
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '503':
          description: SSO not configured on server
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Get SSO (SAML) configuration
      tags:
        - sso
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request GET \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/sso-config \
              --header 'X-API-Key: REPLACE_KEY_VALUE'
        - lang: Python
          source: |-
            import requests

            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/sso-config"

            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}

            response = requests.request("GET", url, headers=headers)

            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/sso-config',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/sso-config\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
    put:
      consumes:
        - application/json
      description: >-
        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).
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: IdP metadata URL + entity ID
          in: body
          name: body
          required: true
          schema:
            properties:
              idp_entity_id:
                type: string
              idp_metadata_url:
                type: string
            type: object
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.ssoUpdateResponse'
        '400':
          description: invalid body
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '402':
          description: plan does not include SSO
          schema:
            $ref: '#/definitions/internal_api.PaymentRequiredResponse'
        '403':
          description: owner/admin required or API key used
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '502':
          description: identity provider unreachable
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '503':
          description: SSO not configured on server
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
      summary: Update SSO (SAML) configuration
      tags:
        - sso
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request PUT \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/sso-config \
              --header 'Authorization: REPLACE_KEY_VALUE' \
              --header 'accept: application/json' \
              --header 'content-type: application/json' \
              --data '{"idp_entity_id":"string","idp_metadata_url":"string"}'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/sso-config"


            payload = {
                "idp_entity_id": "string",
                "idp_metadata_url": "string"
            }

            headers = {
                "accept": "application/json",
                "Authorization": "REPLACE_KEY_VALUE",
                "content-type": "application/json"
            }


            response = requests.request("PUT", url, json=payload,
            headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {
              method: 'PUT',
              headers: {
                accept: 'application/json',
                Authorization: 'REPLACE_KEY_VALUE',
                'content-type': 'application/json'
              },
              body: '{"idp_entity_id":"string","idp_metadata_url":"string"}'
            };


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/sso-config',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/sso-config\"\n\n\tpayload := strings.NewReader(\"{\\\"idp_entity_id\\\":\\\"string\\\",\\\"idp_metadata_url\\\":\\\"string\\\"}\")\n\n\treq, _ := http.NewRequest(\"PUT\", url, payload)\n\n\treq.Header.Add(\"accept\", \"application/json\")\n\treq.Header.Add(\"Authorization\", \"REPLACE_KEY_VALUE\")\n\treq.Header.Add(\"content-type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/state-sources:
    get:
      description: >-
        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).
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: page size (default 100, max 500)
          in: query
          name: limit
          type: integer
        - description: page offset
          in: query
          name: offset
          type: integer
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.StateSourceListResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: List Terraform state sources
      tags:
        - state-sources
      x-codeSamples:
        - lang: cURL
          source: |-
            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'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/state-sources"


            querystring =
            {"limit":"SOME_INTEGER_VALUE","offset":"SOME_INTEGER_VALUE"}


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("GET", url, headers=headers,
            params=querystring)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/state-sources?limit=SOME_INTEGER_VALUE&offset=SOME_INTEGER_VALUE',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/state-sources?limit=SOME_INTEGER_VALUE&offset=SOME_INTEGER_VALUE\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
    post:
      consumes:
        - application/json
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: State source config
          in: body
          name: body
          required: true
          schema:
            properties:
              cloud_account_id:
                type: string
              config:
                items:
                  type: integer
                type: array
              display_name:
                type: string
              kind:
                type: string
            required:
              - config
              - display_name
              - kind
            type: object
      produces:
        - application/json
      responses:
        '201':
          description: Created
          schema:
            $ref: '#/definitions/internal_api.StateSourceResponse'
        '400':
          description: invalid kind or config
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '402':
          description: state source limit reached
          schema:
            $ref: '#/definitions/internal_api.PaymentRequiredResponse'
        '403':
          description: owner/admin role required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Register a Terraform state source
      tags:
        - state-sources
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request POST \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/state-sources \
              --header 'X-API-Key: REPLACE_KEY_VALUE' \
              --header 'accept: application/json' \
              --header 'content-type: application/json' \
              --data '{"cloud_account_id":"string","config":[0],"display_name":"string","kind":"string"}'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/state-sources"


            payload = {
                "cloud_account_id": "string",
                "config": [0],
                "display_name": "string",
                "kind": "string"
            }

            headers = {
                "accept": "application/json",
                "X-API-Key": "REPLACE_KEY_VALUE",
                "content-type": "application/json"
            }


            response = requests.request("POST", url, json=payload,
            headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {
              method: 'POST',
              headers: {
                accept: 'application/json',
                'X-API-Key': 'REPLACE_KEY_VALUE',
                'content-type': 'application/json'
              },
              body: '{"cloud_account_id":"string","config":[0],"display_name":"string","kind":"string"}'
            };


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/state-sources',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/state-sources\"\n\n\tpayload := strings.NewReader(\"{\\\"cloud_account_id\\\":\\\"string\\\",\\\"config\\\":[0],\\\"display_name\\\":\\\"string\\\",\\\"kind\\\":\\\"string\\\"}\")\n\n\treq, _ := http.NewRequest(\"POST\", url, payload)\n\n\treq.Header.Add(\"accept\", \"application/json\")\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\treq.Header.Add(\"content-type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/traces/{trace_id}:
    get:
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Trace ID (starts with 'trc-')
          in: path
          name: trace_id
          required: true
          type: string
      produces:
        - application/json
      responses:
        '200':
          description: Trace body — redacted for non-admins
          schema:
            $ref: '#/definitions/github_com_driftwise_backend_internal_trace.LLMTrace'
        '202':
          description: Trace upload still in progress
          schema:
            $ref: '#/definitions/github_com_driftwise_backend_internal_trace.LLMTrace'
        '400':
          description: invalid trace ID
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '404':
          description: trace not found
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '410':
          description: >-
            Trace expired past retention window — body still returned for
            timeline context; clients should treat as read-only
          schema:
            $ref: '#/definitions/github_com_driftwise_backend_internal_trace.LLMTrace'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Get an LLM trace
      tags:
        - scans
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request GET \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/traces/%7Btrace_id%7D \
              --header 'X-API-Key: REPLACE_KEY_VALUE'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/traces/%7Btrace_id%7D"


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("GET", url, headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/traces/%7Btrace_id%7D',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/traces/%7Btrace_id%7D\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/webhook-configs:
    get:
      description: >-
        Paginated list. raw_secret is always empty — shown only once at
        creation. Optional provider filter narrows by GitLab/Atlantis.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Filter by provider
          enum:
            - gitlab
            - atlantis
          in: query
          name: provider
          type: string
        - description: page size (default 100, max 500)
          in: query
          name: limit
          type: integer
        - description: page offset
          in: query
          name: offset
          type: integer
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.WebhookConfigListResponse'
        '400':
          description: invalid provider
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: List webhook configs
      tags:
        - webhooks
      x-codeSamples:
        - lang: cURL
          source: |-
            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'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/webhook-configs"


            querystring =
            {"provider":"SOME_STRING_VALUE","limit":"SOME_INTEGER_VALUE","offset":"SOME_INTEGER_VALUE"}


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("GET", url, headers=headers,
            params=querystring)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/webhook-configs?provider=SOME_STRING_VALUE&limit=SOME_INTEGER_VALUE&offset=SOME_INTEGER_VALUE',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/webhook-configs?provider=SOME_STRING_VALUE&limit=SOME_INTEGER_VALUE&offset=SOME_INTEGER_VALUE\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
    post:
      consumes:
        - application/json
      description: >-
        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).
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Webhook config
          in: body
          name: body
          required: true
          schema:
            properties:
              api_token:
                type: string
              label:
                type: string
              provider:
                type: string
              provider_base_url:
                type: string
              repo_path:
                type: string
            required:
              - label
              - provider
            type: object
      produces:
        - application/json
      responses:
        '201':
          description: secret field populated once only
          schema:
            $ref: '#/definitions/internal_api.WebhookConfigResponse'
        '400':
          description: invalid provider, config, or GitLab token
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '403':
          description: owner/admin role required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '503':
          description: encryption key or GitLab introspection not configured
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Register a webhook config
      tags:
        - webhooks
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request POST \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/webhook-configs \
              --header 'X-API-Key: REPLACE_KEY_VALUE' \
              --header 'accept: application/json' \
              --header 'content-type: application/json' \
              --data '{"api_token":"string","label":"string","provider":"string","provider_base_url":"string","repo_path":"string"}'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/webhook-configs"


            payload = {
                "api_token": "string",
                "label": "string",
                "provider": "string",
                "provider_base_url": "string",
                "repo_path": "string"
            }

            headers = {
                "accept": "application/json",
                "X-API-Key": "REPLACE_KEY_VALUE",
                "content-type": "application/json"
            }


            response = requests.request("POST", url, json=payload,
            headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {
              method: 'POST',
              headers: {
                accept: 'application/json',
                'X-API-Key': 'REPLACE_KEY_VALUE',
                'content-type': 'application/json'
              },
              body: '{"api_token":"string","label":"string","provider":"string","provider_base_url":"string","repo_path":"string"}'
            };


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/webhook-configs',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/webhook-configs\"\n\n\tpayload := strings.NewReader(\"{\\\"api_token\\\":\\\"string\\\",\\\"label\\\":\\\"string\\\",\\\"provider\\\":\\\"string\\\",\\\"provider_base_url\\\":\\\"string\\\",\\\"repo_path\\\":\\\"string\\\"}\")\n\n\treq, _ := http.NewRequest(\"POST\", url, payload)\n\n\treq.Header.Add(\"accept\", \"application/json\")\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\treq.Header.Add(\"content-type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /orgs/{id}/webhook-configs/{config_id}:
    delete:
      description: >-
        Removes the config and stops accepting webhooks signed with its secret.
        Already-deleted configs return 404.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Webhook config ID (UUID)
          in: path
          name: config_id
          required: true
          type: string
      responses:
        '204':
          description: No Content
        '403':
          description: owner/admin role required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '404':
          description: webhook config not found
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Delete a webhook config
      tags:
        - webhooks
      x-codeSamples:
        - lang: cURL
          source: |-
            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'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/webhook-configs/%7Bconfig_id%7D"


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("DELETE", url, headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'DELETE', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/webhook-configs/%7Bconfig_id%7D',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/webhook-configs/%7Bconfig_id%7D\"\n\n\treq, _ := http.NewRequest(\"DELETE\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
    patch:
      consumes:
        - application/json
      description: >-
        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.
      parameters:
        - description: Org ID (UUID)
          in: path
          name: id
          required: true
          type: string
        - description: Webhook config ID (UUID)
          in: path
          name: config_id
          required: true
          type: string
        - description: Fields to update
          in: body
          name: body
          required: true
          schema:
            properties:
              api_token:
                description: non-empty to replace; empty string clears the token
                type: string
              enabled:
                type: boolean
            type: object
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.WebhookConfigResponse'
        '400':
          description: invalid body, token length, or GitLab token
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '403':
          description: owner/admin role required
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '404':
          description: webhook config not found
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '503':
          description: encryption key not configured (token rotation only)
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Update a webhook config
      tags:
        - webhooks
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request PATCH \
              --url https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/webhook-configs/%7Bconfig_id%7D \
              --header 'X-API-Key: REPLACE_KEY_VALUE' \
              --header 'accept: application/json' \
              --header 'content-type: application/json' \
              --data '{"api_token":"string","enabled":true}'
        - lang: Python
          source: >-
            import requests


            url =
            "https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/webhook-configs/%7Bconfig_id%7D"


            payload = {
                "api_token": "string",
                "enabled": True
            }

            headers = {
                "accept": "application/json",
                "X-API-Key": "REPLACE_KEY_VALUE",
                "content-type": "application/json"
            }


            response = requests.request("PATCH", url, json=payload,
            headers=headers)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {
              method: 'PATCH',
              headers: {
                accept: 'application/json',
                'X-API-Key': 'REPLACE_KEY_VALUE',
                'content-type': 'application/json'
              },
              body: '{"api_token":"string","enabled":true}'
            };


            fetch('https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/webhook-configs/%7Bconfig_id%7D',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/orgs/%7Bid%7D/webhook-configs/%7Bconfig_id%7D\"\n\n\tpayload := strings.NewReader(\"{\\\"api_token\\\":\\\"string\\\",\\\"enabled\\\":true}\")\n\n\treq, _ := http.NewRequest(\"PATCH\", url, payload)\n\n\treq.Header.Add(\"accept\", \"application/json\")\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\treq.Header.Add(\"content-type\", \"application/json\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /providers:
    get:
      description: >-
        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.
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/internal_api.providerListResponse'
        '403':
          description: domain not allowlisted
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '503':
          description: cloud registry not configured
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: List supported cloud providers
      tags:
        - accounts
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request GET \
              --url https://app.driftwise.ai/api/v2/providers \
              --header 'X-API-Key: REPLACE_KEY_VALUE'
        - lang: Python
          source: |-
            import requests

            url = "https://app.driftwise.ai/api/v2/providers"

            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}

            response = requests.request("GET", url, headers=headers)

            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/providers', options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/providers\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /slack/callback:
    get:
      description: >-
        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.
      parameters:
        - description: HMAC-signed state produced by POST /slack/install
          in: query
          name: state
          required: true
          type: string
        - description: OAuth authorization code (absent on user-denial redirects)
          in: query
          name: code
          type: string
        - description: OAuth error param (present on user denial)
          in: query
          name: error
          type: string
      produces:
        - application/json
      responses:
        '302':
          description: Redirect to /#/settings?slack=connected
        '400':
          description: missing state or code
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '403':
          description: invalid state, revoked membership, or plan downgrade
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '500':
          description: Internal Server Error
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '502':
          description: Slack token exchange failed
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '503':
          description: Slack integration or encryption not configured
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      summary: Slack OAuth callback (redirect)
      tags:
        - slack
      x-codeSamples:
        - lang: cURL
          source: |-
            curl --request GET \
              --url 'https://app.driftwise.ai/api/v2/slack/callback?state=SOME_STRING_VALUE&code=SOME_STRING_VALUE&error=SOME_STRING_VALUE'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/slack/callback"


            querystring =
            {"state":"SOME_STRING_VALUE","code":"SOME_STRING_VALUE","error":"SOME_STRING_VALUE"}


            response = requests.request("GET", url, params=querystring)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET'};


            fetch('https://app.driftwise.ai/api/v2/slack/callback?state=SOME_STRING_VALUE&code=SOME_STRING_VALUE&error=SOME_STRING_VALUE',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/slack/callback?state=SOME_STRING_VALUE&code=SOME_STRING_VALUE&error=SOME_STRING_VALUE\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
  /tf-registry/meta:
    get:
      description: >-
        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.
      parameters:
        - description: Cloud provider
          enum:
            - aws
            - gcp
            - azure
          in: query
          name: provider
          required: true
          type: string
        - description: Terraform resource type (e.g. aws_s3_bucket)
          in: query
          name: resource_type
          required: true
          type: string
      produces:
        - application/json
      responses:
        '200':
          description: OK
          schema:
            $ref: >-
              #/definitions/github_com_driftwise_backend_internal_tfregistry.ResourceMeta
        '400':
          description: invalid provider or resource_type
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
        '429':
          description: rate limit exceeded
          schema:
            $ref: '#/definitions/internal_api.ErrorResponse'
      security:
        - OIDCBearer: []
        - APIKey: []
      summary: Get Terraform Registry resource metadata
      tags:
        - policy
      x-codeSamples:
        - lang: cURL
          source: |-
            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'
        - lang: Python
          source: >-
            import requests


            url = "https://app.driftwise.ai/api/v2/tf-registry/meta"


            querystring =
            {"provider":"SOME_STRING_VALUE","resource_type":"SOME_STRING_VALUE"}


            headers = {"X-API-Key": "REPLACE_KEY_VALUE"}


            response = requests.request("GET", url, headers=headers,
            params=querystring)


            print(response.text)
        - lang: TypeScript
          source: >-
            const options = {method: 'GET', headers: {'X-API-Key':
            'REPLACE_KEY_VALUE'}};


            fetch('https://app.driftwise.ai/api/v2/tf-registry/meta?provider=SOME_STRING_VALUE&resource_type=SOME_STRING_VALUE',
            options)
              .then(response => response.json())
              .then(response => console.log(response))
              .catch(err => console.error(err));
        - lang: Go
          source: "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"io/ioutil\"\n)\n\nfunc main() {\n\n\turl := \"https://app.driftwise.ai/api/v2/tf-registry/meta?provider=SOME_STRING_VALUE&resource_type=SOME_STRING_VALUE\"\n\n\treq, _ := http.NewRequest(\"GET\", url, nil)\n\n\treq.Header.Add(\"X-API-Key\", \"REPLACE_KEY_VALUE\")\n\n\tres, _ := http.DefaultClient.Do(req)\n\n\tdefer res.Body.Close()\n\tbody, _ := ioutil.ReadAll(res.Body)\n\n\tfmt.Println(res)\n\tfmt.Println(string(body))\n\n}"
schemes:
  - https
securityDefinitions:
  APIKey:
    description: >-
      Org-scoped API key (dw2_ prefix). Alternatively sendable as
      "Authorization: Bearer dw2_...". Scope-gated on the server; read-only keys
      cannot invoke mutations.
    in: header
    name: X-API-Key
    type: apiKey
  OIDCBearer:
    description: >-
      OIDC JWT from Casdoor. Format: "Bearer <jwt>". Required for human-driven
      operations (org creation, SSO config, billing). Admin endpoints reject any
      other auth method with 403.
    in: header
    name: Authorization
    type: apiKey
swagger: '2.0'
