{
  "openapi": "3.1.0",
  "info": {
    "title": "outpost API",
    "version": "1.0.0",
    "description": "Distributed probe system: run HTTP/TCP checks from a fleet of workers selected by labels. Guide for AI agents: /llms.txt. Admin UI for tokens: /admin/."
  },
  "servers": [
    {
      "url": "https://outpost.neubauer-digital.at"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API token (opk_…) from the admin UI. Roles: readonly < operator < admin."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          }
        }
      },
      "NoAgentsError": {
        "type": "object",
        "description": "Not enough eligible agents of the requested workload. The check is NOT created and never rerouted to another workload.",
        "properties": {
          "error": {
            "type": "string"
          },
          "workload": {
            "$ref": "#/components/schemas/Workload"
          },
          "eligible": {
            "type": "integer"
          },
          "requested": {
            "type": "integer"
          }
        }
      },
      "Workload": {
        "type": "string",
        "enum": [
          "monitoring",
          "scraping"
        ],
        "description": "Exclusive worker role. Decides the egress IP a measurement leaves from. monitoring = fresh connection per measurement; scraping = keep-alive pool."
      },
      "IPVersion": {
        "type": "string",
        "enum": [
          "auto",
          "ipv4",
          "ipv6"
        ],
        "default": "auto",
        "description": "auto = resolver order (IPv6 first when reachable). ipv4/ipv6 = only that family; if the target has none, the result is status=error (no fallback)."
      },
      "Selector": {
        "type": "object",
        "required": [
          "workload"
        ],
        "description": "Declarative agent selection. Never by hostname or agent id.",
        "properties": {
          "workload": {
            "$ref": "#/components/schemas/Workload"
          },
          "count": {
            "type": "integer",
            "minimum": 1,
            "maximum": 50,
            "default": 1
          },
          "labels": {
            "type": "object",
            "description": "Extra exact-match labels. Allowed keys: browser, port25, egress_ip, created, ring.",
            "additionalProperties": {
              "type": "string"
            },
            "example": {
              "ring": "0"
            }
          }
        }
      },
      "HTTPGetSpec": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Absolute http(s) URL, no credentials."
          },
          "headers": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Request headers, set as given (e.g. Accept, Accept-Language, User-Agent). Default User-Agent: `outpost/1 (+https://outpost.neubauer-digital.at/docs)`. Host, Content-Length and hop-by-hop headers are controlled by the HTTP client."
          },
          "follow_redirects": {
            "type": "boolean",
            "default": false
          },
          "max_redirects": {
            "type": "integer",
            "default": 5
          },
          "insecure_tls": {
            "type": "boolean",
            "default": false,
            "description": "Skip certificate verification (for measuring broken sites)."
          },
          "return_body": {
            "type": "boolean",
            "default": false,
            "description": "Ship the fetched body (up to max_bytes) to the controller. Retrieve it with GET /v1/checks/{id}/body or inline via include_body=true. Kept 7 days."
          }
        }
      },
      "TCPConnectSpec": {
        "type": "object",
        "required": [
          "host",
          "port"
        ],
        "properties": {
          "host": {
            "type": "string"
          },
          "port": {
            "type": "integer",
            "minimum": 1,
            "maximum": 65535
          },
          "tls": {
            "type": "boolean",
            "default": false,
            "description": "Do a TLS handshake after connect."
          },
          "server_name": {
            "type": "string",
            "description": "SNI, defaults to host."
          }
        }
      },
      "CheckRequest": {
        "type": "object",
        "required": [
          "type",
          "agents"
        ],
        "additionalProperties": false,
        "description": "Exactly one of http (for http_get) or tcp (for tcp_connect).",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "http_get",
              "tcp_connect"
            ],
            "description": "Planned, not yet accepted: whois, smtp, imap, pop3, dns."
          },
          "http": {
            "$ref": "#/components/schemas/HTTPGetSpec"
          },
          "tcp": {
            "$ref": "#/components/schemas/TCPConnectSpec"
          },
          "agents": {
            "$ref": "#/components/schemas/Selector"
          },
          "ip_version": {
            "$ref": "#/components/schemas/IPVersion"
          },
          "timeout_ms": {
            "type": "integer",
            "minimum": 100,
            "maximum": 60000,
            "default": 10000
          },
          "max_bytes": {
            "type": "integer",
            "minimum": 1,
            "maximum": 4194304,
            "default": 1048576,
            "description": "Response body limit; larger bodies are truncated (http.truncated=true)."
          }
        }
      },
      "CheckStatus": {
        "type": "string",
        "enum": [
          "queued",
          "running",
          "done",
          "partial",
          "failed"
        ],
        "description": "queued = waiting for rate-limit token; running = dispatched; done = all agents delivered; partial = some delivered or an agent failed (worker outage never yields failed); failed = all agents rejected."
      },
      "JobStatus": {
        "type": "string",
        "enum": [
          "queued",
          "dispatched",
          "running",
          "done",
          "rejected",
          "lost"
        ],
        "description": "Per agent. lost = no result before timeout + 15s grace."
      },
      "Timings": {
        "type": "object",
        "description": "Separate phases in milliseconds, not cumulative. ttfb_ms = request written -> first response byte.",
        "properties": {
          "dns_ms": {
            "type": "number"
          },
          "connect_ms": {
            "type": "number"
          },
          "tls_ms": {
            "type": "number"
          },
          "ttfb_ms": {
            "type": "number"
          },
          "total_ms": {
            "type": "number"
          }
        }
      },
      "TLSInfo": {
        "type": "object",
        "properties": {
          "version": {
            "type": "string"
          },
          "cert_expiry": {
            "type": "string",
            "format": "date-time"
          },
          "cert_subject": {
            "type": "string"
          }
        }
      },
      "HTTPResult": {
        "type": "object",
        "properties": {
          "status_code": {
            "type": "integer",
            "description": "HTTP status of the target. Same value as `http_status` in GET /v1/results (flat row)."
          },
          "final_url": {
            "type": "string"
          },
          "bytes": {
            "type": "integer"
          },
          "truncated": {
            "type": "boolean"
          },
          "sha256": {
            "type": "string",
            "description": "Hash of the body as read (dedup key)."
          },
          "content_type": {
            "type": "string"
          },
          "body_chunks": {
            "type": "integer",
            "description": ">0 when the body was transferred (return_body)."
          }
        }
      },
      "Result": {
        "type": "object",
        "description": "Raw worker result.",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "accepted",
              "result",
              "rejected"
            ]
          },
          "job_id": {
            "type": "string"
          },
          "check_id": {
            "type": "string"
          },
          "agent_id": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "target": {
            "type": "string"
          },
          "ip_version": {
            "$ref": "#/components/schemas/IPVersion"
          },
          "status": {
            "type": "string",
            "enum": [
              "ok",
              "error",
              "timeout",
              "blocked"
            ],
            "description": "error/timeout = valid measurement of a failing target. blocked = target resolved to a private/loopback/link-local/metadata or infrastructure address."
          },
          "error": {
            "type": "string"
          },
          "timings": {
            "$ref": "#/components/schemas/Timings"
          },
          "resolved_ip": {
            "type": "string"
          },
          "reused": {
            "type": "boolean"
          },
          "http": {
            "$ref": "#/components/schemas/HTTPResult"
          },
          "tls": {
            "$ref": "#/components/schemas/TLSInfo"
          },
          "started_at": {
            "type": "string",
            "format": "date-time"
          },
          "finished_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "JobView": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "agent_id": {
            "type": "string"
          },
          "hostname": {
            "type": "string",
            "description": "Label only."
          },
          "status": {
            "$ref": "#/components/schemas/JobStatus"
          },
          "error": {
            "type": "string"
          },
          "dispatched_at": {
            "type": "string",
            "format": "date-time"
          },
          "finished_at": {
            "type": "string",
            "format": "date-time"
          },
          "result": {
            "$ref": "#/components/schemas/Result"
          },
          "body": {
            "$ref": "#/components/schemas/BodyInfo"
          },
          "body_text": {
            "type": "string",
            "description": "Only with include_body=true and only for valid UTF-8 bodies (HTML, JSON, text)."
          }
        }
      },
      "CheckView": {
        "type": "object",
        "properties": {
          "job_id": {
            "type": "string",
            "description": "Same as id. Use it with GET /v1/checks/{id}."
          },
          "id": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "target": {
            "type": "string"
          },
          "domain": {
            "type": "string",
            "description": "Rate-limit bucket (registrable domain)."
          },
          "workload": {
            "$ref": "#/components/schemas/Workload"
          },
          "request": {
            "$ref": "#/components/schemas/CheckRequest"
          },
          "status": {
            "$ref": "#/components/schemas/CheckStatus"
          },
          "error": {
            "type": "string"
          },
          "schedule_id": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "dispatched_at": {
            "type": "string",
            "format": "date-time"
          },
          "finished_at": {
            "type": "string",
            "format": "date-time"
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/JobView"
            }
          },
          "created_by": {
            "type": "string",
            "description": "Id of the token that created the check (absent for scheduled runs)."
          }
        }
      },
      "Agent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "hostname": {
            "type": "string"
          },
          "labels": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "enrolled_at": {
            "type": "string",
            "format": "date-time"
          },
          "revoked": {
            "type": "boolean"
          },
          "online": {
            "type": "boolean",
            "description": "Heartbeat within 30s."
          },
          "last_seen": {
            "type": "string",
            "format": "date-time"
          },
          "version": {
            "type": "string"
          },
          "reported_workload": {
            "type": "string"
          },
          "capabilities": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "pool_size": {
            "type": "integer"
          },
          "busy": {
            "type": "integer"
          },
          "queued": {
            "type": "integer"
          }
        }
      },
      "AgentRegistration": {
        "type": "object",
        "required": [
          "id",
          "hostname",
          "labels"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "ULID from `opc enroll`."
          },
          "hostname": {
            "type": "string"
          },
          "labels": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "workload required."
          },
          "revoked": {
            "type": "boolean"
          }
        }
      },
      "ScheduleRequest": {
        "type": "object",
        "required": [
          "interval_s",
          "check"
        ],
        "properties": {
          "interval_s": {
            "type": "integer",
            "minimum": 10
          },
          "check": {
            "$ref": "#/components/schemas/CheckRequest"
          }
        }
      },
      "Schedule": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "request": {
            "$ref": "#/components/schemas/CheckRequest"
          },
          "interval_s": {
            "type": "integer"
          },
          "enabled": {
            "type": "boolean"
          },
          "next_run": {
            "type": "string",
            "format": "date-time"
          },
          "last_check_id": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ResultRow": {
        "type": "object",
        "properties": {
          "ts": {
            "type": "string",
            "format": "date-time"
          },
          "job_id": {
            "type": "string"
          },
          "check_id": {
            "type": "string"
          },
          "agent_id": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "target": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "error": {
            "type": "string"
          },
          "timings": {
            "$ref": "#/components/schemas/Timings"
          },
          "resolved_ip": {
            "type": "string"
          },
          "http_status": {
            "type": "integer",
            "description": "HTTP status of the target; in check results the same value is `result.http.status_code`."
          },
          "bytes": {
            "type": "integer"
          },
          "sha256": {
            "type": "string"
          }
        },
        "description": "Flat history row. Field names differ from the nested check result: http_status = result.http.status_code, bytes = result.http.bytes, sha256 = result.http.sha256. The body itself is only available via GET /v1/checks/{id}/body."
      },
      "Principal": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "role": {
            "type": "string",
            "enum": [
              "readonly",
              "operator",
              "admin"
            ]
          }
        }
      },
      "Token": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "role": {
            "type": "string",
            "enum": [
              "readonly",
              "operator",
              "admin"
            ]
          },
          "prefix": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_by": {
            "type": "string"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_used_at": {
            "type": "string",
            "format": "date-time"
          },
          "revoked_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "TokenCreateRequest": {
        "type": "object",
        "required": [
          "name",
          "role"
        ],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 100
          },
          "role": {
            "type": "string",
            "enum": [
              "readonly",
              "operator",
              "admin"
            ]
          },
          "expires_in_days": {
            "type": "integer",
            "minimum": 0,
            "maximum": 3650,
            "default": 0,
            "description": "0 = never."
          }
        }
      },
      "TokenCreated": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Token"
          },
          {
            "type": "object",
            "properties": {
              "token": {
                "type": "string",
                "description": "Plaintext. Returned exactly once."
              }
            }
          }
        ]
      },
      "BodyInfo": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "stored",
              "pending"
            ],
            "description": "pending = transfer still being assembled; retry shortly."
          },
          "sha256": {
            "type": "string"
          },
          "bytes": {
            "type": "integer"
          },
          "content_type": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "description": "Relative URL to fetch the raw body."
          }
        }
      },
      "AccessEntry": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "description": "0 for live-streamed entries (not yet persisted)."
          },
          "ts": {
            "type": "string",
            "format": "date-time"
          },
          "method": {
            "type": "string"
          },
          "path": {
            "type": "string"
          },
          "query": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "ms": {
            "type": "number"
          },
          "bytes": {
            "type": "integer"
          },
          "token_id": {
            "type": "string"
          },
          "token_name": {
            "type": "string"
          },
          "role": {
            "type": "string"
          },
          "remote_ip": {
            "type": "string"
          },
          "user_agent": {
            "type": "string"
          },
          "check_id": {
            "type": "string"
          }
        }
      },
      "Point": {
        "type": "object",
        "properties": {
          "t": {
            "type": "string",
            "format": "date-time",
            "description": "Bucket start."
          },
          "v": {
            "type": "object",
            "additionalProperties": {
              "type": "number"
            }
          }
        }
      }
    }
  },
  "paths": {
    "/healthz": {
      "get": {
        "summary": "Liveness + DB check",
        "security": [],
        "responses": {
          "200": {
            "description": "OK"
          },
          "503": {
            "description": "Database unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/whoami": {
      "get": {
        "summary": "Who am I",
        "description": "Returns the principal behind the bearer token.\n\nRequired role: `readonly`.",
        "x-required-role": "readonly",
        "responses": {
          "200": {
            "description": "Principal",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Principal"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Token role too low for this operation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/checks": {
      "post": {
        "summary": "Create a check",
        "description": "Fans out one job per selected agent. Returns 202 with the check (status queued/running). With ?wait=, blocks until the check is terminal (200) or the wait expires (202).\n\nJobs are rate limited centrally per registrable domain across all agents (default 1 rps, burst 5); 429/503 answers back the whole domain off exponentially.\n\nRequired role: `operator`.\n\nWith http.return_body=true and ?wait=, the call also waits until the bodies are stored.",
        "x-required-role": "operator",
        "responses": {
          "200": {
            "description": "Finished within wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckView"
                }
              }
            }
          },
          "202": {
            "description": "Accepted; poll GET /v1/checks/{id}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckView"
                }
              }
            }
          },
          "400": {
            "description": "Validation error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Not enough eligible agents.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NoAgentsError"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Token role too low for this operation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "wait",
            "in": "query",
            "required": false,
            "description": "Block up to this duration (Go syntax, e.g. 10s, max 60s).",
            "schema": {
              "type": "string",
              "example": "10s"
            }
          },
          {
            "name": "include_body",
            "in": "query",
            "required": false,
            "description": "true = add body_text (UTF-8 bodies only) to each result.",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CheckRequest"
              },
              "example": {
                "type": "http_get",
                "http": {
                  "url": "https://example.com/"
                },
                "agents": {
                  "workload": "monitoring",
                  "count": 2
                },
                "ip_version": "auto",
                "timeout_ms": 10000
              }
            }
          }
        }
      }
    },
    "/v1/checks/{id}": {
      "get": {
        "summary": "Get a check",
        "description": "Status plus per-agent result.\n\nRequired role: `readonly`.",
        "x-required-role": "readonly",
        "responses": {
          "200": {
            "description": "Check",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckView"
                }
              }
            }
          },
          "404": {
            "description": "Unknown id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Token role too low for this operation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Check id (job_id from POST).",
            "schema": {
              "type": "string",
              "pattern": "^[0-9A-HJKMNP-TV-Z]{26}$"
            }
          },
          {
            "name": "include_body",
            "in": "query",
            "required": false,
            "description": "true = add body_text (UTF-8 bodies only) to each result.",
            "schema": {
              "type": "boolean"
            }
          }
        ]
      }
    },
    "/v1/agents": {
      "get": {
        "summary": "List agents",
        "description": "Inventory with labels and online state.\n\nRequired role: `readonly`.",
        "x-required-role": "readonly",
        "responses": {
          "200": {
            "description": "Agents",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "agents": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Agent"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Token role too low for this operation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Register an enrolled agent",
        "description": "Enrollment hook used by `opc register-all`. The relay identity is created separately by `opc enroll`.\n\nRequired role: `admin`.",
        "x-required-role": "admin",
        "responses": {
          "200": {
            "description": "Registered",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentRegistration"
                }
              }
            }
          },
          "400": {
            "description": "Validation error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Token role too low for this operation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentRegistration"
              }
            }
          }
        }
      }
    },
    "/v1/schedules": {
      "get": {
        "summary": "List schedules",
        "description": "\n\nRequired role: `readonly`.",
        "x-required-role": "readonly",
        "responses": {
          "200": {
            "description": "Schedules",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "schedules": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Schedule"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Token role too low for this operation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a schedule",
        "description": "Runs the check every interval_s seconds. Missed runs are skipped.\n\nRequired role: `operator`.",
        "x-required-role": "operator",
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Schedule"
                }
              }
            }
          },
          "400": {
            "description": "Validation error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Token role too low for this operation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScheduleRequest"
              },
              "example": {
                "interval_s": 60,
                "check": {
                  "type": "http_get",
                  "http": {
                    "url": "https://example.com/"
                  },
                  "agents": {
                    "workload": "monitoring",
                    "count": 2
                  },
                  "ip_version": "auto",
                  "timeout_ms": 10000
                }
              }
            }
          }
        }
      }
    },
    "/v1/schedules/{id}": {
      "delete": {
        "summary": "Delete a schedule",
        "description": "\n\nRequired role: `operator`.",
        "x-required-role": "operator",
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "404": {
            "description": "Unknown id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Token role too low for this operation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Schedule id.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9A-HJKMNP-TV-Z]{26}$"
            }
          }
        ]
      }
    },
    "/v1/results": {
      "get": {
        "summary": "Result history",
        "description": "Newest first. Default window: last 7 days.\n\nRequired role: `readonly`.",
        "x-required-role": "readonly",
        "responses": {
          "200": {
            "description": "Results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "results": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ResultRow"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad parameter.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Token role too low for this operation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "check_id",
            "in": "query",
            "required": false,
            "description": "Filter by check.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "agent_id",
            "in": "query",
            "required": false,
            "description": "Filter by agent.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "target",
            "in": "query",
            "required": false,
            "description": "Exact target (URL or host:port).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "http_get or tcp_connect.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "RFC 3339 or relative duration like 1h.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "until",
            "in": "query",
            "required": false,
            "description": "RFC 3339 or relative duration.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1..1000, default 100.",
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/tokens": {
      "get": {
        "summary": "List API tokens",
        "description": "Metadata only; plaintext is never returned again.\n\nRequired role: `admin`.",
        "x-required-role": "admin",
        "responses": {
          "200": {
            "description": "Tokens",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tokens": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Token"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Token role too low for this operation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create an API token",
        "description": "The plaintext token is in the response exactly once.\n\nRequired role: `admin`.",
        "x-required-role": "admin",
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenCreated"
                }
              }
            }
          },
          "400": {
            "description": "Validation error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Token role too low for this operation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TokenCreateRequest"
              }
            }
          }
        }
      }
    },
    "/v1/tokens/{id}": {
      "delete": {
        "summary": "Revoke an API token",
        "description": "Immediate. Revoked tokens stay listed for audit. The token used for the request cannot revoke itself.\n\nRequired role: `admin`.",
        "x-required-role": "admin",
        "responses": {
          "204": {
            "description": "Revoked"
          },
          "404": {
            "description": "Unknown id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Refusing to revoke the calling token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Token role too low for this operation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Token id.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9A-HJKMNP-TV-Z]{26}$"
            }
          }
        ]
      }
    },
    "/v1/checks/{id}/body": {
      "get": {
        "summary": "Fetch a stored response body",
        "x-required-role": "readonly",
        "description": "Raw body bytes with the original Content-Type (served under a sandbox CSP). Headers: X-Outpost-Sha256, X-Outpost-Agent, ETag.\n\nRequired role: `readonly`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Check id (job_id from POST).",
            "schema": {
              "type": "string",
              "pattern": "^[0-9A-HJKMNP-TV-Z]{26}$"
            }
          },
          {
            "name": "agent_id",
            "in": "query",
            "required": false,
            "description": "Required when the check has bodies from several agents.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Body",
            "content": {
              "*/*": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "202": {
            "description": "Still being transferred; retry after Retry-After seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "400": {
            "description": "Several agents delivered bodies; pass agent_id.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "agent_ids": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No body (not requested, failed, or expired).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Body file expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Token role too low for this operation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/access": {
      "get": {
        "summary": "Access log",
        "x-required-role": "admin",
        "description": "Every API request with token, client IP, status and duration. Newest first. Kept 30 days.\n\nRequired role: `admin`.",
        "responses": {
          "200": {
            "description": "Entries",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "entries": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AccessEntry"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Token role too low for this operation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "before_id",
            "in": "query",
            "required": false,
            "description": "Page: entries older than this id.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "token_id",
            "in": "query",
            "required": false,
            "description": "Only this token.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "min_status",
            "in": "query",
            "required": false,
            "description": "e.g. 400 for errors only.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1..1000, default 200.",
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/access/stream": {
      "get": {
        "summary": "Live access log",
        "x-required-role": "admin",
        "description": "Server-sent events (`data: <AccessEntry JSON>`), one per API request, plus `: ping` comments every 15 s. Send the bearer token as a header (use fetch, not EventSource).\n\nRequired role: `admin`.",
        "responses": {
          "200": {
            "description": "text/event-stream",
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Token role too low for this operation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/stats/dashboard": {
      "get": {
        "summary": "Dashboard statistics",
        "x-required-role": "readonly",
        "description": "Tiles, time series (API requests, jobs per workload, latency p50/p95), result status counts, HTTP code classes, top domains.\n\nRequired role: `readonly`.",
        "responses": {
          "200": {
            "description": "Dashboard",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "range": {
                      "type": "string"
                    },
                    "bucket_s": {
                      "type": "integer"
                    },
                    "tiles": {
                      "type": "object"
                    },
                    "api": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Point"
                      }
                    },
                    "jobs": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Point"
                      }
                    },
                    "latency": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Point"
                      }
                    },
                    "statuses": {
                      "type": "object"
                    },
                    "http_codes": {
                      "type": "object"
                    },
                    "domains": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad range.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Token role too low for this operation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "range",
            "in": "query",
            "required": false,
            "description": "1h (1-min buckets), 24h (15-min), 7d (2-h). Default 1h.",
            "schema": {
              "type": "string",
              "enum": [
                "1h",
                "24h",
                "7d"
              ]
            }
          }
        ]
      }
    },
    "/v1/stats/agents": {
      "get": {
        "summary": "Worker statistics",
        "x-required-role": "readonly",
        "description": "Inventory plus per-worker jobs, failures, HTTP errors, latency and utilisation series (busy slots / pool size from heartbeats).\n\nRequired role: `readonly`.",
        "responses": {
          "200": {
            "description": "Agents with stats",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "range": {
                      "type": "string"
                    },
                    "bucket_s": {
                      "type": "integer"
                    },
                    "agents": {
                      "type": "array",
                      "items": {
                        "allOf": [
                          {
                            "$ref": "#/components/schemas/Agent"
                          },
                          {
                            "type": "object",
                            "properties": {
                              "stats": {
                                "type": "object"
                              }
                            }
                          }
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad range.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Token role too low for this operation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "range",
            "in": "query",
            "required": false,
            "description": "1h (1-min buckets), 24h (15-min), 7d (2-h). Default 1h.",
            "schema": {
              "type": "string",
              "enum": [
                "1h",
                "24h",
                "7d"
              ]
            }
          }
        ]
      }
    },
    "/v1/run": {
      "get": {
        "summary": "Create a check via GET (for POST-blocking proxies)",
        "x-required-role": "operator",
        "description": "Same as POST /v1/checks, built from query parameters, same responses. Unknown parameters are rejected. The token still goes in the Authorization header, never in the URL. Responses carry Cache-Control: no-store.\n\nExample: `GET /v1/run?type=http_get&url=https%3A%2F%2Fexample.com%2F&workload=scraping&return_body=true&wait=20s&include_body=true`\n\nRequired role: `operator`.",
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "http_get or tcp_connect (required).",
            "schema": {
              "type": "string",
              "enum": [
                "http_get",
                "tcp_connect"
              ]
            }
          },
          {
            "name": "workload",
            "in": "query",
            "required": false,
            "description": "monitoring or scraping (required).",
            "schema": {
              "type": "string",
              "enum": [
                "monitoring",
                "scraping"
              ]
            }
          },
          {
            "name": "url",
            "in": "query",
            "required": false,
            "description": "http_get: absolute URL (URL-encode it).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "host",
            "in": "query",
            "required": false,
            "description": "tcp_connect: host.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "port",
            "in": "query",
            "required": false,
            "description": "tcp_connect: port.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "tls",
            "in": "query",
            "required": false,
            "description": "tcp_connect: TLS handshake.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "server_name",
            "in": "query",
            "required": false,
            "description": "tcp_connect: SNI.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "follow_redirects",
            "in": "query",
            "required": false,
            "description": "http_get.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "max_redirects",
            "in": "query",
            "required": false,
            "description": "http_get.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "insecure_tls",
            "in": "query",
            "required": false,
            "description": "http_get.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "return_body",
            "in": "query",
            "required": false,
            "description": "http_get: ship the body (see POST).",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "count",
            "in": "query",
            "required": false,
            "description": "Number of agents (default 1).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "ip_version",
            "in": "query",
            "required": false,
            "description": "auto | ipv4 | ipv6.",
            "schema": {
              "type": "string",
              "enum": [
                "auto",
                "ipv4",
                "ipv6"
              ]
            }
          },
          {
            "name": "timeout_ms",
            "in": "query",
            "required": false,
            "description": "",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "max_bytes",
            "in": "query",
            "required": false,
            "description": "",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "label.{key}",
            "in": "query",
            "required": false,
            "description": "Label filter, e.g. label.ring=0.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "header.{Name}",
            "in": "query",
            "required": false,
            "description": "Request header, e.g. header.Accept-Language=de-AT.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "wait",
            "in": "query",
            "required": false,
            "description": "As for POST.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "include_body",
            "in": "query",
            "required": false,
            "description": "As for POST.",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Finished within wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckView"
                }
              }
            }
          },
          "202": {
            "description": "Accepted; poll GET /v1/checks/{id}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckView"
                }
              }
            }
          },
          "400": {
            "description": "Validation error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Not enough eligible agents.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NoAgentsError"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Token role too low for this operation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  }
}
