{
  "openapi": "3.1.0",
  "info": {
    "title": "FiveShield public HTTP surface",
    "version": "2026-10-25",
    "summary": "What a program can call at fiveshield.co without an account.",
    "description": "FiveShield is a DDoS-mitigation reverse proxy for FiveM and RedM game servers. Customers manage their servers in a dashboard that signs in with Discord; that dashboard's own API (`/api/auth/*`, `/api/server/*`) authenticates with a browser session cookie, is meant for the dashboard only, and is deliberately not described here.\n\nWhat IS described here is the public surface: a liveness probe, the host agent's installers, binaries and API (called by the `fiveshield-portmanager` agent that customers install on their own machine, not by people), and the files that describe the site to agents.\n\nERRORS. Every error this site produces itself under `/api` is JSON in the `ApiError` shape: a stable `error` reason, a `code` class, the `status`, an English `message` and a `hint` saying what to do. The host agent's API answers the narrower `AgentApiError` shape, which the agent parses; errors relayed from the service behind it use that shape too. Downloads under `/install.*` and `/agent/` answer a one-line shell comment on failure instead, so that `curl | sh` never executes an error page. Pages (`/en/...`, `/fr/...`) answer 404 as HTML, or as Markdown when the request asks for `Accept: text/markdown`.\n\nNo operation here needs an API key.",
    "termsOfService": "https://fiveshield.co/en/terms",
    "contact": {
      "name": "FiveShield support",
      "url": "https://discord.fiveshield.co/"
    }
  },
  "servers": [
    {
      "url": "https://fiveshield.co",
      "description": "Production"
    }
  ],
  "tags": [
    { "name": "Health", "description": "Liveness probes." },
    { "name": "Host agent", "description": "Installers, binaries and the API of the agent customers run on their own server. Used by the agent, not by people." },
    { "name": "Discovery", "description": "Files that describe the site to crawlers and agents." }
  ],
  "externalDocs": {
    "description": "Human-readable documentation of this surface, with example requests (English; French at /fr/developers).",
    "url": "https://fiveshield.co/en/developers"
  },
  "security": [],
  "paths": {
    "/api/health": {
      "get": {
        "operationId": "getApiHealth",
        "tags": ["Health"],
        "summary": "Liveness probe (JSON)",
        "description": "Reports that the site's edge Worker is executing, which build it is and where. It makes no call to any upstream service, so it says nothing about the proxy network or the dashboard's data. Never cached.",
        "responses": {
          "200": {
            "description": "The Worker is up.",
            "headers": {
              "Cache-Control": { "description": "Always `no-store`.", "schema": { "type": "string", "const": "no-store" } }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/HealthStatus" },
                "example": { "status": "ok", "version": "3f2a9c1e-0000-4000-8000-000000000000", "colo": "YYZ", "time": "2026-10-24T12:00:00.000Z" }
              }
            }
          },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" }
        }
      },
      "head": {
        "operationId": "headApiHealth",
        "tags": ["Health"],
        "summary": "Liveness probe, headers only",
        "responses": {
          "200": { "description": "The Worker is up. No body." },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" }
        }
      }
    },
    "/health": {
      "get": {
        "operationId": "getHealth",
        "tags": ["Health"],
        "summary": "Liveness probe (plain text)",
        "description": "The smallest possible probe, for uptime monitors that keyword-match. `/healthz` is an identical alias. Never cached.",
        "responses": {
          "200": {
            "description": "The Worker is up.",
            "content": {
              "text/plain": {
                "schema": { "type": "string", "const": "ok" }
              }
            }
          }
        }
      }
    },
    "/install.sh": {
      "get": {
        "operationId": "getInstallSh",
        "tags": ["Host agent"],
        "summary": "Linux installer for the host agent",
        "description": "The shell installer for the FiveShield host agent. The dashboard generates the complete one-line command, including the single-use enrolment token, so use that command rather than this URL on its own. A query string is refused with 404.",
        "responses": {
          "200": {
            "description": "The installer script.",
            "content": {
              "text/plain": { "schema": { "type": "string" } }
            }
          },
          "404": { "$ref": "#/components/responses/DownloadUnavailable" },
          "502": { "$ref": "#/components/responses/DownloadUnavailable" },
          "504": { "$ref": "#/components/responses/DownloadUnavailable" }
        }
      }
    },
    "/install.ps1": {
      "get": {
        "operationId": "getInstallPs1",
        "tags": ["Host agent"],
        "summary": "Windows installer for the host agent",
        "description": "The PowerShell installer for the FiveShield host agent. The dashboard generates the complete one-line command, including the single-use enrolment token, so use that command rather than this URL on its own. A query string is refused with 404.",
        "responses": {
          "200": {
            "description": "The installer script.",
            "content": {
              "text/plain": { "schema": { "type": "string" } }
            }
          },
          "404": { "$ref": "#/components/responses/DownloadUnavailable" },
          "502": { "$ref": "#/components/responses/DownloadUnavailable" },
          "504": { "$ref": "#/components/responses/DownloadUnavailable" }
        }
      }
    },
    "/agent/{binary}": {
      "get": {
        "operationId": "getAgentBinary",
        "tags": ["Host agent"],
        "summary": "Host agent binary",
        "description": "A build of the host agent. Fetched by the installers; never cached, so a fresh installer cannot pair with a stale binary. A query string is refused with 404.",
        "parameters": [
          {
            "name": "binary",
            "in": "path",
            "required": true,
            "description": "The build to download.",
            "schema": {
              "type": "string",
              "enum": [
                "fiveshield-agent-x86_64-unknown-linux-musl",
                "fiveshield-agent-aarch64-unknown-linux-musl",
                "fsagent-x86_64-pc-windows-msvc.exe"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The executable.",
            "content": {
              "application/octet-stream": { "schema": { "type": "string", "format": "binary" } }
            }
          },
          "404": { "$ref": "#/components/responses/DownloadUnavailable" },
          "502": { "$ref": "#/components/responses/DownloadUnavailable" },
          "504": { "$ref": "#/components/responses/DownloadUnavailable" }
        }
      }
    },
    "/api/agent/enroll": {
      "post": {
        "operationId": "enrollAgent",
        "tags": ["Host agent"],
        "summary": "Exchange an enrolment token for agent credentials",
        "description": "Called once by the host agent at install time. The single-use token in the body is the credential; failed attempts are rate limited per client address. The body is relayed byte for byte and its fields belong to the agent protocol, not to this site, so they are not specified here.",
        "parameters": [
          { "$ref": "#/components/parameters/AgentId" },
          { "$ref": "#/components/parameters/KeyId" },
          { "$ref": "#/components/parameters/Timestamp" },
          { "$ref": "#/components/parameters/Nonce" },
          { "$ref": "#/components/parameters/Signature" }
        ],
        "requestBody": { "$ref": "#/components/requestBodies/AgentBody" },
        "responses": {
          "200": { "$ref": "#/components/responses/AgentOk" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" },
          "413": { "$ref": "#/components/responses/AgentError" },
          "429": { "$ref": "#/components/responses/AgentRateLimited" },
          "4XX": { "$ref": "#/components/responses/AgentError" },
          "5XX": { "$ref": "#/components/responses/AgentError" }
        }
      }
    },
    "/api/agent/reconcile": {
      "post": {
        "operationId": "reconcileAgent",
        "tags": ["Host agent"],
        "summary": "Report the agent's state and receive its desired configuration",
        "description": "Called periodically by an enrolled host agent. The request is authenticated by the agent's HMAC signature in the `X-FS-*` headers over the exact body bytes. The answer is bounded to 9 seconds, inside the agent's own 10 second timeout, so a slow service behind the site is a 504 the agent can back off from. The body is relayed byte for byte and its fields belong to the agent protocol, not to this site.",
        "parameters": [
          { "$ref": "#/components/parameters/AgentId" },
          { "$ref": "#/components/parameters/KeyId" },
          { "$ref": "#/components/parameters/Timestamp" },
          { "$ref": "#/components/parameters/Nonce" },
          { "$ref": "#/components/parameters/Signature" }
        ],
        "requestBody": { "$ref": "#/components/requestBodies/AgentBody" },
        "responses": {
          "200": { "$ref": "#/components/responses/AgentOk" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" },
          "413": { "$ref": "#/components/responses/AgentError" },
          "429": { "$ref": "#/components/responses/AgentRateLimited" },
          "4XX": { "$ref": "#/components/responses/AgentError" },
          "5XX": { "$ref": "#/components/responses/AgentError" }
        }
      }
    },
    "/llms.txt": {
      "get": {
        "operationId": "getLlmsTxt",
        "tags": ["Discovery"],
        "summary": "Site description for agents",
        "description": "A plain-text map of the site: what the service is and is not, every public page in English and French, pricing, locations and support.",
        "responses": {
          "200": {
            "description": "The description.",
            "content": {
              "text/plain": { "schema": { "type": "string" } }
            }
          }
        }
      }
    },
    "/sitemap.xml": {
      "get": {
        "operationId": "getSitemap",
        "tags": ["Discovery"],
        "summary": "Sitemap of every public page",
        "responses": {
          "200": {
            "description": "A sitemaps.org `urlset`.",
            "content": {
              "application/xml": { "schema": { "type": "string" } }
            }
          }
        }
      }
    },
    "/.well-known/agent-skills/index.json": {
      "get": {
        "operationId": "getAgentSkillsIndex",
        "tags": ["Discovery"],
        "summary": "Agent Skills discovery index",
        "description": "Lists the skills (`SKILL.md` files) this site publishes for agents, each with a SHA-256 digest.",
        "responses": {
          "200": {
            "description": "The index.",
            "content": {
              "application/json": { "schema": { "type": "object", "required": ["skills"], "properties": { "skills": { "type": "array", "items": { "type": "object" } } } } }
            }
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "operationId": "getOpenApiDocument",
        "tags": ["Discovery"],
        "summary": "This document",
        "responses": {
          "200": {
            "description": "The OpenAPI 3.1 description of the public surface.",
            "content": {
              "application/json": { "schema": { "type": "object" } }
            }
          }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "AgentId": {
        "name": "X-FS-Agent-Id",
        "in": "header",
        "required": false,
        "description": "One of the five headers that carry the agent's HMAC authentication, set by the agent. Forwarded unchanged.",
        "schema": { "type": "string" }
      },
      "KeyId": {
        "name": "X-FS-Key-Id",
        "in": "header",
        "required": false,
        "description": "One of the five headers that carry the agent's HMAC authentication, set by the agent. Forwarded unchanged.",
        "schema": { "type": "string" }
      },
      "Timestamp": {
        "name": "X-FS-Timestamp",
        "in": "header",
        "required": false,
        "description": "One of the five headers that carry the agent's HMAC authentication, set by the agent. Forwarded unchanged.",
        "schema": { "type": "string" }
      },
      "Nonce": {
        "name": "X-FS-Nonce",
        "in": "header",
        "required": false,
        "description": "One of the five headers that carry the agent's HMAC authentication, set by the agent. Forwarded unchanged.",
        "schema": { "type": "string" }
      },
      "Signature": {
        "name": "X-FS-Signature",
        "in": "header",
        "required": false,
        "description": "The agent's HMAC over the exact request body bytes. The body is relayed as raw bytes and never re-serialised, because that would make every signature fail.",
        "schema": { "type": "string" }
      }
    },
    "requestBodies": {
      "AgentBody": {
        "required": true,
        "description": "A JSON document defined by the agent protocol. Relayed unchanged, up to 256 KiB.",
        "content": {
          "application/json": {
            "schema": { "type": "object", "additionalProperties": true }
          }
        }
      }
    },
    "responses": {
      "MethodNotAllowed": {
        "description": "The route does not accept that HTTP method.",
        "headers": {
          "Allow": { "description": "The methods the route accepts.", "schema": { "type": "string" } }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ApiError" },
            "example": {
              "error": "method_not_allowed",
              "code": "method_not_allowed",
              "status": 405,
              "message": "This route does not accept that HTTP method.",
              "hint": "Use one of: GET, HEAD. The OpenAPI description lists what each route accepts.",
              "docs": "https://fiveshield.co/openapi.json"
            }
          }
        }
      },
      "AgentOk": {
        "description": "The service's answer, relayed unchanged.",
        "headers": {
          "Cache-Control": { "description": "Always `no-store`.", "schema": { "type": "string", "const": "no-store" } }
        },
        "content": {
          "application/json": {
            "schema": { "type": "object", "additionalProperties": true }
          }
        }
      },
      "AgentError": {
        "description": "A refusal, either from this site (`method_not_allowed`, `not_configured`, `body_too_large`, `upstream_timeout`, `upstream_unavailable`) or relayed from the service behind it (for example `malformed`). Anything that is not that service's JSON answer, such as a challenge page, is turned into `upstream_unavailable` rather than passed on.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/AgentApiError" },
            "example": { "error": { "code": "malformed", "detail": "token is required" } }
          }
        }
      },
      "AgentRateLimited": {
        "description": "Too many failed attempts from this address.",
        "headers": {
          "Retry-After": { "description": "Seconds to wait before trying again.", "schema": { "type": "integer", "minimum": 0 } }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/AgentApiError" }
          }
        }
      },
      "DownloadUnavailable": {
        "description": "The file is not available right now. The body is a single shell comment, never HTML or JSON, so piping the URL into a shell runs nothing. Never cached.",
        "content": {
          "text/plain": {
            "schema": { "type": "string", "const": "# fiveshield: this installer file is not available right now\n" }
          }
        }
      }
    },
    "schemas": {
      "ApiError": {
        "type": "object",
        "description": "The error shape of everything this site produces itself under `/api`.",
        "required": ["error", "code", "status", "message", "hint", "docs"],
        "properties": {
          "error": { "type": "string", "description": "A stable snake_case reason, such as `route_not_found`.", "examples": ["route_not_found"] },
          "code": {
            "type": "string",
            "description": "The class of failure.",
            "enum": [
              "invalid_request",
              "unauthorized",
              "forbidden",
              "not_found",
              "method_not_allowed",
              "payload_too_large",
              "rate_limited",
              "internal_error",
              "upstream_error",
              "unavailable",
              "timeout"
            ]
          },
          "status": { "type": "integer", "minimum": 400, "maximum": 599, "description": "The HTTP status, repeated for clients that lose it." },
          "message": { "type": "string", "description": "One English sentence saying what went wrong." },
          "hint": { "type": "string", "description": "What to do about it." },
          "docs": { "type": "string", "format": "uri", "description": "This OpenAPI document." }
        }
      },
      "AgentApiError": {
        "type": "object",
        "description": "The host agent's error shape, which the agent parses.",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "object",
            "required": ["code"],
            "properties": {
              "code": { "type": "string", "description": "A stable snake_case code." },
              "detail": { "type": "string", "description": "Optional human-readable detail." }
            }
          }
        }
      },
      "HealthStatus": {
        "type": "object",
        "required": ["status", "version", "colo", "time"],
        "properties": {
          "status": { "type": "string", "const": "ok" },
          "version": { "type": ["string", "null"], "description": "Identifier of the deployed build. Opaque." },
          "colo": { "type": ["string", "null"], "description": "Cloudflare data centre that answered, as an IATA-style code." },
          "time": { "type": "string", "format": "date-time", "description": "When the request arrived." }
        }
      }
    }
  }
}
