{
  "openapi": "3.0.3",
  "info": {
    "title": "Go-Live Check",
    "version": "0",
    "description": "Free check of response headers the caller already collected from a deployment they operate. Nothing is for sale. There is no API key, no account, and no price.\n\nThis server does not open the caller's URL. It does not guess the platform from the Server header. It does not check config_text or paths_tested. Those fields are accepted and are not copied into the response.\n\nThe only route is POST /v1/analyze. The body is JSON, at most 65536 bytes. Thirty POST requests per minute per connecting IP, including POSTs that return 400 or 413. A GET does not count toward that limit. Over the limit the response is 429 with Retry-After: 60.\n\nDo not send secrets, cookies, tokens, or personal data. Leave Cookie, Authorization, and Set-Cookie out of headers_raw. The JSON body is not written to disk and is not copied into the access log. Prefer this HTTPS host. The bare IP still answers on port 80 without TLS.\n\nThe response field schema is the string golive-check.analyze.v0. An empty findings list means those seven checks were clear in the text you sent. It is not a pentest, an audit, a certificate, or legal advice. This file describes that POST only. It is not an MCP server."
  },
  "servers": [
    {
      "url": "https://dontdeployonfriday.com",
      "description": "HTTPS. This name on port 80 redirects here. The bare IP still answers on port 80 without TLS. Do not send secrets on either."
    }
  ],
  "externalDocs": {
    "description": "Agent note: collect the headers on a deployment you already operate, then POST the text.",
    "url": "https://dontdeployonfriday.com/llms.txt"
  },
  "paths": {
    "/v1/analyze": {
      "post": {
        "operationId": "analyzeHeaders",
        "summary": "Check a header block the caller already collected.",
        "description": "Parse the pasted header block and return the seven header findings. A fix snippet is attached only when platform is caddy, nginx, vercel, netlify, or nextjs. Any other accepted platform, or no platform, sets fix to null on every finding. source_url inside a fix is a citation. This server does not fetch it. Read the snippet before pasting it. A starter policy can break an app that needs a looser one.\n\nIf the paste contains more than one HTTP status line, only the last block is checked. The status line itself is not a header, so a body of only `HTTP/1.1 200 OK` has parsed_header_count 0.\n\nEvery JSON response on this path sends Content-Type: application/json; charset=utf-8 and Cache-Control: no-store. The request Content-Type is not required. The body must still be JSON. Unknown request properties are ignored.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AnalyzeRequest"
              },
              "examples": {
                "missingHeadersCaddy": {
                  "summary": "Status line only, platform caddy. Returns five findings, each with a Caddy snippet.",
                  "value": {
                    "headers_raw": "HTTP/1.1 200 OK\n",
                    "platform": "caddy"
                  }
                },
                "missingHeadersNoPlatform": {
                  "summary": "Same header text. Platform omitted, so every fix is null.",
                  "value": {
                    "headers_raw": "HTTP/1.1 200 OK\n"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The seven checks ran. findings lists only the ones that fired. checked always lists all seven ids, in this order.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyzeResponse"
                },
                "examples": {
                  "missingHeadersCaddy": {
                    "summary": "The missingHeadersCaddy request, as returned by this host on 2026-10-17. GLC-SERVER-VERSION-LEAK and GLC-CORS-WILDCARD-CREDENTIALS did not fire.",
                    "value": {
                      "schema": "golive-check.analyze.v0",
                      "checked": [
                        "GLC-HSTS-MISSING",
                        "GLC-CSP-MISSING",
                        "GLC-NOSNIFF-MISSING",
                        "GLC-FRAME-MISSING",
                        "GLC-REFERRER-MISSING",
                        "GLC-SERVER-VERSION-LEAK",
                        "GLC-CORS-WILDCARD-CREDENTIALS"
                      ],
                      "findings": [
                        {
                          "id": "GLC-HSTS-MISSING",
                          "severity": "high",
                          "why": "Strict-Transport-Security is missing or max-age is 0, so a browser will not pin this host to HTTPS.",
                          "evidence": "Strict-Transport-Security: absent",
                          "fix": {
                            "platform": "caddy",
                            "snippet": "# Only if this host and its subdomains already answer on HTTPS. Pins them for one year. No preload.\nheader Strict-Transport-Security \"max-age=31536000; includeSubDomains\"\n",
                            "source_url": "https://caddyserver.com/docs/caddyfile/directives/header",
                            "last_verified": "2026-10-17"
                          }
                        },
                        {
                          "id": "GLC-CSP-MISSING",
                          "severity": "high",
                          "why": "Content-Security-Policy is missing, so the browser will not block an injected script. A report-only policy does not count.",
                          "evidence": "Content-Security-Policy: absent",
                          "fix": {
                            "platform": "caddy",
                            "snippet": "# Add the hosts this app actually loads. This starter blocks inline scripts.\nheader Content-Security-Policy \"default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'\"\n",
                            "source_url": "https://caddyserver.com/docs/caddyfile/directives/header",
                            "last_verified": "2026-10-17"
                          }
                        },
                        {
                          "id": "GLC-NOSNIFF-MISSING",
                          "severity": "medium",
                          "why": "X-Content-Type-Options is not nosniff, so a browser may treat a response as a different type than the server declared.",
                          "evidence": "X-Content-Type-Options: absent",
                          "fix": {
                            "platform": "caddy",
                            "snippet": "header X-Content-Type-Options nosniff\n",
                            "source_url": "https://caddyserver.com/docs/caddyfile/directives/header",
                            "last_verified": "2026-10-17"
                          }
                        },
                        {
                          "id": "GLC-FRAME-MISSING",
                          "severity": "medium",
                          "why": "Neither X-Frame-Options (DENY or SAMEORIGIN) nor a CSP frame-ancestors source is set, so another site can frame this page.",
                          "evidence": "X-Frame-Options: absent",
                          "fix": {
                            "platform": "caddy",
                            "snippet": "header X-Frame-Options DENY\n",
                            "source_url": "https://caddyserver.com/docs/caddyfile/directives/header",
                            "last_verified": "2026-10-17"
                          }
                        },
                        {
                          "id": "GLC-REFERRER-MISSING",
                          "severity": "low",
                          "why": "Referrer-Policy is missing, so the browser default decides how much of the URL is sent to other sites.",
                          "evidence": "Referrer-Policy: absent",
                          "fix": {
                            "platform": "caddy",
                            "snippet": "header Referrer-Policy no-referrer\n",
                            "source_url": "https://caddyserver.com/docs/caddyfile/directives/header",
                            "last_verified": "2026-10-17"
                          }
                        }
                      ],
                      "parsed_header_count": 0,
                      "platform": "caddy",
                      "config_text": "absent",
                      "paths_tested_count": 0,
                      "notes": [
                        "Checked only the header text in this request. This server did not contact any host.",
                        "Not a pentest. Seven header checks only. Config text and paths are not checked.",
                        "A fix is attached only when the declared platform is in the local registry. The source URL is a citation and is not fetched."
                      ]
                    }
                  },
                  "missingHeadersNoPlatform": {
                    "summary": "The same header text with no platform. Same five ids. Every fix is null. platform is null.",
                    "value": {
                      "schema": "golive-check.analyze.v0",
                      "checked": [
                        "GLC-HSTS-MISSING",
                        "GLC-CSP-MISSING",
                        "GLC-NOSNIFF-MISSING",
                        "GLC-FRAME-MISSING",
                        "GLC-REFERRER-MISSING",
                        "GLC-SERVER-VERSION-LEAK",
                        "GLC-CORS-WILDCARD-CREDENTIALS"
                      ],
                      "findings": [
                        {
                          "id": "GLC-HSTS-MISSING",
                          "severity": "high",
                          "why": "Strict-Transport-Security is missing or max-age is 0, so a browser will not pin this host to HTTPS.",
                          "evidence": "Strict-Transport-Security: absent",
                          "fix": null
                        },
                        {
                          "id": "GLC-CSP-MISSING",
                          "severity": "high",
                          "why": "Content-Security-Policy is missing, so the browser will not block an injected script. A report-only policy does not count.",
                          "evidence": "Content-Security-Policy: absent",
                          "fix": null
                        },
                        {
                          "id": "GLC-NOSNIFF-MISSING",
                          "severity": "medium",
                          "why": "X-Content-Type-Options is not nosniff, so a browser may treat a response as a different type than the server declared.",
                          "evidence": "X-Content-Type-Options: absent",
                          "fix": null
                        },
                        {
                          "id": "GLC-FRAME-MISSING",
                          "severity": "medium",
                          "why": "Neither X-Frame-Options (DENY or SAMEORIGIN) nor a CSP frame-ancestors source is set, so another site can frame this page.",
                          "evidence": "X-Frame-Options: absent",
                          "fix": null
                        },
                        {
                          "id": "GLC-REFERRER-MISSING",
                          "severity": "low",
                          "why": "Referrer-Policy is missing, so the browser default decides how much of the URL is sent to other sites.",
                          "evidence": "Referrer-Policy: absent",
                          "fix": null
                        }
                      ],
                      "parsed_header_count": 0,
                      "platform": null,
                      "config_text": "absent",
                      "paths_tested_count": 0,
                      "notes": [
                        "Checked only the header text in this request. This server did not contact any host.",
                        "Not a pentest. Seven header checks only. Config text and paths are not checked.",
                        "A fix is attached only when the declared platform is in the local registry. The source URL is a citation and is not fetched."
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body was rejected before the checks ran. error is invalid_json, invalid_body, headers_raw_required, invalid_platform, invalid_config_text, invalid_paths_tested, or bad_request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "invalidJson": {
                    "summary": "Body was not JSON.",
                    "value": {
                      "error": "invalid_json"
                    }
                  },
                  "headersRawRequired": {
                    "summary": "headers_raw was missing or blank.",
                    "value": {
                      "error": "headers_raw_required"
                    }
                  },
                  "invalidPlatform": {
                    "summary": "platform was not a lowercase registry-style name. Caddy is rejected; caddy is accepted.",
                    "value": {
                      "error": "invalid_platform"
                    }
                  }
                }
              }
            }
          },
          "405": {
            "description": "This path accepts POST only. GET, PUT, and the other methods return this. A GET does not use a rate-limit slot.",
            "headers": {
              "Allow": {
                "description": "The only method.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "POST"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "get": {
                    "summary": "GET /v1/analyze.",
                    "value": {
                      "error": "method_not_allowed"
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "The HTTP body is over 65536 bytes, headers_raw is over 65536 bytes, or config_text is over 32768 bytes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "tooLarge": {
                    "value": {
                      "error": "body_too_large"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "This connecting IP has sent 30 POSTs in the current 60-second window. POSTs that returned 400 or 413 count. Wait for Retry-After.",
            "headers": {
              "Retry-After": {
                "description": "Seconds until the caller may retry. The value is 60.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "60"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "limited": {
                    "value": {
                      "error": "rate_limited"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "The checker threw. The body that was sent is not stored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "failed": {
                    "value": {
                      "error": "analyze_failed"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "AnalyzeRequest": {
        "type": "object",
        "additionalProperties": true,
        "required": [
          "headers_raw"
        ],
        "properties": {
          "headers_raw": {
            "type": "string",
            "description": "Status line and response headers from a deployment the caller operates, the kind of text curl -sI prints. Required. Empty after trim is 400 headers_raw_required. Longer than 65536 bytes is 413 body_too_large. Several HTTP status lines: only the last block is checked."
          },
          "platform": {
            "type": "string",
            "nullable": true,
            "pattern": "^$|^[a-z0-9_-]{1,32}$",
            "description": "Optional. Null or an empty string means omitted, and the response platform is null. Otherwise 1–32 characters from a-z, 0-9, underscore, and hyphen. Anything else, including uppercase, is 400 invalid_platform. Snippets are attached only for caddy, nginx, vercel, netlify, and nextjs. Any other accepted name returns fix null. This server does not read the Server header to fill this in."
          },
          "config_text": {
            "type": "string",
            "nullable": true,
            "description": "Optional. Accepted and not checked. Not copied into the response. The response config_text is absent when this is omitted, null, or empty, and not_checked when it is a non-empty string. A non-string is 400 invalid_config_text. Longer than 32768 bytes is 413 body_too_large."
          },
          "paths_tested": {
            "type": "array",
            "nullable": true,
            "maxItems": 40,
            "items": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Optional. Accepted and not checked. The strings are not copied into the response. The response reports only paths_tested_count. More than 40 items, a non-array, a non-string item, or an item longer than 200 characters is 400 invalid_paths_tested."
          }
        }
      },
      "AnalyzeResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schema",
          "checked",
          "findings",
          "parsed_header_count",
          "platform",
          "config_text",
          "paths_tested_count",
          "notes"
        ],
        "properties": {
          "schema": {
            "type": "string",
            "enum": [
              "golive-check.analyze.v0"
            ]
          },
          "checked": {
            "type": "array",
            "minItems": 7,
            "maxItems": 7,
            "description": "The seven ids, in this order, whether or not each one fired.",
            "items": {
              "$ref": "#/components/schemas/FindingId"
            }
          },
          "findings": {
            "type": "array",
            "description": "Only the checks that fired, in the same order as checked. Empty when all seven are clear in the pasted text.",
            "items": {
              "$ref": "#/components/schemas/Finding"
            }
          },
          "parsed_header_count": {
            "type": "integer",
            "minimum": 0,
            "description": "Header fields parsed from the last block. The HTTP status line is not counted."
          },
          "platform": {
            "type": "string",
            "nullable": true,
            "description": "The platform the caller declared, or null when it was omitted or empty. Not inferred."
          },
          "config_text": {
            "type": "string",
            "enum": [
              "absent",
              "not_checked"
            ],
            "description": "absent when no config text was sent. not_checked when non-empty text was sent. The text itself is never returned."
          },
          "paths_tested_count": {
            "type": "integer",
            "minimum": 0,
            "maximum": 40,
            "description": "How many path strings were listed. The paths themselves are never returned."
          },
          "notes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "What this response did and did not do. The current three notes say the server did not contact any host, that config text and paths are not checked, and that a source URL is not fetched."
          }
        }
      },
      "Finding": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "severity",
          "why",
          "evidence",
          "fix"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/FindingId"
          },
          "severity": {
            "type": "string",
            "enum": [
              "high",
              "medium",
              "low"
            ]
          },
          "why": {
            "type": "string"
          },
          "evidence": {
            "type": "string",
            "maxLength": 180,
            "description": "One checked header line, or absent. Clipped at 180 characters. config_text is never used as evidence."
          },
          "fix": {
            "nullable": true,
            "description": "Null when the declared platform has no snippet for this finding. Otherwise the registry snippet for that platform.",
            "allOf": [
              {
                "$ref": "#/components/schemas/Fix"
              }
            ]
          }
        }
      },
      "Fix": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "platform",
          "snippet",
          "source_url",
          "last_verified"
        ],
        "properties": {
          "platform": {
            "type": "string",
            "enum": [
              "caddy",
              "nginx",
              "vercel",
              "netlify",
              "nextjs"
            ]
          },
          "snippet": {
            "type": "string",
            "description": "Config text for the declared platform. Read it before use. It is a starter, not a judgment that the app will still work."
          },
          "source_url": {
            "type": "string",
            "format": "uri",
            "description": "Documentation page the snippet was taken from. This server does not fetch it."
          },
          "last_verified": {
            "type": "string",
            "format": "date",
            "description": "The date that documentation page was checked. Currently 2026-10-17."
          },
          "note": {
            "type": "string",
            "description": "Optional. Present only when that registry file has a note, and then it is a string, not null. Three Vercel snippets include one: GLC-HSTS-MISSING, GLC-CSP-MISSING, and GLC-CORS-WILDCARD-CREDENTIALS. Other fixes omit this field."
          }
        }
      },
      "FindingId": {
        "type": "string",
        "enum": [
          "GLC-HSTS-MISSING",
          "GLC-CSP-MISSING",
          "GLC-NOSNIFF-MISSING",
          "GLC-FRAME-MISSING",
          "GLC-REFERRER-MISSING",
          "GLC-SERVER-VERSION-LEAK",
          "GLC-CORS-WILDCARD-CREDENTIALS"
        ]
      },
      "Error": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "error"
        ],
        "description": "One error code. Status mapping: 400 invalid_json (the body is not JSON), invalid_body (JSON is not an object), headers_raw_required (missing, not a string, or empty after trim), invalid_platform (not 1–32 characters from a-z, 0-9, _, -; an empty string is allowed and means omitted), invalid_config_text (present and not a string), invalid_paths_tested (not an array of at most 40 strings, or an item longer than 200 characters), bad_request (the request could not be read, including a bad Content-Length). 413 body_too_large (HTTP body over 65536 bytes, headers_raw over 65536 bytes, or config_text over 32768 bytes). 405 method_not_allowed, with Allow: POST. 429 rate_limited, with Retry-After: 60. 500 analyze_failed.",
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "invalid_json",
              "invalid_body",
              "headers_raw_required",
              "invalid_platform",
              "invalid_config_text",
              "invalid_paths_tested",
              "bad_request",
              "body_too_large",
              "method_not_allowed",
              "rate_limited",
              "analyze_failed"
            ]
          }
        }
      }
    }
  }
}
