{
  "openapi": "3.0.3",
  "info": {
    "title": "PoisonZero Admin API",
    "version": "1.0.0",
    "description": "Manage your PoisonZero daemon fleet programmatically: create app IDs with enrollment codes, list your fleet, deactivate or delete apps. Authenticate with an API key (pz_live_…) created in the panel at https://console.poisonzero.com/settings."
  },
  "servers": [{ "url": "https://poisonzero.com/api" }],
  "security": [{ "apiKey": [] }],
  "components": {
    "securitySchemes": {
      "apiKey": { "type": "http", "scheme": "bearer", "description": "API key from the panel (pz_live_…)" }
    },
    "schemas": {
      "App": {
        "type": "object",
        "properties": {
          "appId": { "type": "string", "pattern": "^[a-z0-9]{20}$" },
          "name": { "type": "string" },
          "status": { "type": "string", "enum": ["pending", "active", "revoked"] },
          "platform": { "type": "string" },
          "agentVersion": { "type": "string" },
          "lastSeenAt": { "type": "string", "format": "date-time", "nullable": true },
          "createdAt": { "type": "string", "format": "date-time", "nullable": true }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": { "code": { "type": "string" }, "message": { "type": "string" } }
          }
        }
      },
      "OtelConfig": {
        "type": "object",
        "properties": {
          "enabled": { "type": "boolean" },
          "endpoint": { "type": "string" },
          "authHeaderSet": { "type": "boolean", "description": "true if a collector auth header is stored; the value itself is never returned" },
          "evidenceLevel": { "type": "string", "enum": ["hashes", "redacted", "full"] }
        }
      }
    }
  },
  "paths": {
    "/v1/apps": {
      "post": {
        "summary": "Create an app plus enrollment code",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": { "type": "object", "properties": { "name": { "type": "string", "maxLength": 100 } } }
            }
          }
        },
        "responses": {
          "201": {
            "description": "App created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "appId": { "type": "string" },
                    "name": { "type": "string" },
                    "status": { "type": "string", "enum": ["pending"] },
                    "enrollCode": { "type": "string" },
                    "enrollCodeExpiresAt": { "type": "string", "format": "date-time" }
                  }
                }
              }
            }
          },
          "400": { "description": "Validation error (invalid_name, limit_reached)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "description": "Invalid key or disabled account", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "413": { "description": "Request body exceeds 64 KiB (payload_too_large)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "description": "Per-account rate limit exceeded — 600 requests per minute (rate_limited)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      },
      "get": {
        "summary": "List your fleet",
        "responses": {
          "200": {
            "description": "Apps of the authenticated account",
            "content": {
              "application/json": {
                "schema": { "type": "object", "properties": { "apps": { "type": "array", "items": { "$ref": "#/components/schemas/App" } } } }
              }
            }
          },
          "401": { "description": "Invalid key or disabled account", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "description": "Per-account rate limit exceeded — 600 requests per minute (rate_limited)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/v1/apps/{appId}": {
      "parameters": [{ "name": "appId", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^[a-z0-9]{20}$" } }],
      "get": {
        "summary": "Get a single app",
        "responses": {
          "200": { "description": "App", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/App" } } } },
          "404": { "description": "Unknown or foreign app ID", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "description": "Per-account rate limit exceeded — 600 requests per minute (rate_limited)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      },
      "delete": {
        "summary": "Delete an app permanently (app, config, daemon identity, open enrollment codes, review-queue entries; audit logs retained)",
        "responses": {
          "204": { "description": "Deleted" },
          "404": { "description": "Unknown or foreign app ID", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "description": "Per-account rate limit exceeded — 600 requests per minute (rate_limited)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/v1/apps/{appId}/enroll-code": {
      "parameters": [{ "name": "appId", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^[a-z0-9]{20}$" } }],
      "post": {
        "summary": "Create a fresh enrollment code (single-use, 30 days)",
        "responses": {
          "201": {
            "description": "Enrollment code",
            "content": {
              "application/json": {
                "schema": { "type": "object", "properties": { "enrollCode": { "type": "string" }, "expiresAt": { "type": "string", "format": "date-time" } } }
              }
            }
          },
          "404": { "description": "Unknown or foreign app ID", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "description": "Per-account rate limit exceeded — 600 requests per minute (rate_limited)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/v1/apps/{appId}/revoke": {
      "parameters": [{ "name": "appId", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^[a-z0-9]{20}$" } }],
      "post": {
        "summary": "Deactivate an app (daemon credentials stop working immediately)",
        "responses": {
          "200": {
            "description": "Revoked",
            "content": {
              "application/json": {
                "schema": { "type": "object", "properties": { "appId": { "type": "string" }, "status": { "type": "string", "enum": ["revoked"] } } }
              }
            }
          },
          "404": { "description": "Unknown or foreign app ID", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "description": "Per-account rate limit exceeded — 600 requests per minute (rate_limited)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/v1/otel-config": {
      "get": {
        "summary": "Read the fleet-wide OTel export config (Enterprise-only)",
        "description": "authHeader is write-only and never returned; authHeaderSet reports only whether one is stored.",
        "responses": {
          "200": { "description": "Current config (secret-free)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OtelConfig" } } } },
          "403": { "description": "Account is not Enterprise-entitled", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "description": "Per-account rate limit exceeded — 600 requests per minute (rate_limited)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      },
      "put": {
        "summary": "Set the fleet-wide OTel export config (Enterprise-only)",
        "description": "Fully validated before any write; an invalid value returns 400 and stores nothing.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["enabled"],
                "properties": {
                  "enabled": { "type": "boolean" },
                  "endpoint": {
                    "type": "string",
                    "maxLength": 2048,
                    "description": "Required when enabled=true. http/https, host required, no query/fragment/userinfo. Plain http only for loopback/private (LAN, .local, RFC1918/ULA) collectors."
                  },
                  "authHeader": {
                    "type": "string",
                    "maxLength": 8192,
                    "description": "Write-only. Omit to keep the stored value, \"\" to delete it, a value to set/replace it."
                  },
                  "evidenceLevel": { "type": "string", "enum": ["hashes", "redacted", "full"], "default": "hashes" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Stored config (secret-free)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OtelConfig" } } } },
          "400": {
            "description": "Validation error (invalid_enabled, invalid_endpoint, invalid_auth_header, invalid_evidence_level)",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "403": { "description": "Account is not Enterprise-entitled", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "413": { "description": "Request body exceeds 64 KiB (payload_too_large)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "description": "Per-account rate limit exceeded — 600 requests per minute (rate_limited)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      },
      "delete": {
        "summary": "Purge the stored OTel config, including the collector secret",
        "description": "Not Enterprise-gated: a downgraded ex-Enterprise owner can still remove its own stored secret. Auth-required and owner-scoped like every other endpoint. Idempotent.",
        "responses": {
          "200": {
            "description": "Deleted",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "deleted": { "type": "boolean", "enum": [true] } } } } }
          },
          "429": { "description": "Per-account rate limit exceeded — 600 requests per minute (rate_limited)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    }
  }
}
