{
  "openapi": "3.1.0",
  "info": {
    "title": "Digizap API",
    "version": "0.2.0",
    "summary": "The public measurement endpoints behind digizap.nl.",
    "description": "All rules from https://digizap.nl/api-docs apply to every endpoint: GET and HEAD only, JSON answers, no key and no account, an exact per-endpoint request limit per visitor address (x-rate-limit names the number; 429 with retry-after beyond it), a fifteen-minute scan cache and a five-minute header cache, refusal of private and reserved target addresses before any measuring, no CORS headers, and no target URLs in our logs. Endpoints that reach a target themselves (over HTTP or a mail-port probe) additionally share an egress gate of four concurrent measurements, a two-questions-per-twenty-seconds cooldown per target, and a fourteen-second work budget.",
    "contact": {
      "name": "Altf3",
      "url": "https://altf3.nl"
    },
    "externalDocs": {
      "url": "https://digizap.nl/api-docs",
      "description": "Human documentation (Dutch; ?lang=en for English)"
    }
  },
  "servers": [
    {
      "url": "https://digizap.nl",
      "description": "Digizap live"
    }
  ],
  "paths": {
    "/api": {
      "get": {
        "operationId": "apiIndex",
        "summary": "Index: application name, version and the full endpoint list.",
        "description": "Index: application name, version and the full endpoint list.",
        "x-rate-limit": {
          "requests": 60,
          "windowSeconds": 60,
          "sharedWith": []
        },
        "parameters": [
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Answer language: en for English, Dutch otherwise.",
            "schema": {
              "type": "string",
              "enum": [
                "nl",
                "en"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The measured result as a JSON object; /api-docs describes what this endpoint measures.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/scan": {
      "get": {
        "operationId": "scan",
        "summary": "The full website scan: HTML, response headers, redirect chain, robots and sitemap findings, network measurements (DNS, connect, TLS, TTFB, total) and asset weight.",
        "description": "The full website scan: HTML, response headers, redirect chain, robots and sitemap findings, network measurements (DNS, connect, TLS, TTFB, total) and asset weight.",
        "x-rate-limit": {
          "requests": 8,
          "windowSeconds": 60,
          "sharedWith": [
            "/api/fetch"
          ]
        },
        "parameters": [
          {
            "name": "url",
            "in": "query",
            "required": true,
            "description": "Target address; bare domain names are accepted too.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Answer language: en for English, Dutch otherwise.",
            "schema": {
              "type": "string",
              "enum": [
                "nl",
                "en"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The measured result as a JSON object; /api-docs describes what this endpoint measures.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/fetch": {
      "get": {
        "operationId": "fetchLegacy",
        "summary": "The legacy entry point to the exact same scan as /api/scan — for your own scripts and old bookmarks. Shares its request budget with /api/scan.",
        "description": "The legacy entry point to the exact same scan as /api/scan — for your own scripts and old bookmarks. Shares its request budget with /api/scan.",
        "x-rate-limit": {
          "requests": 8,
          "windowSeconds": 60,
          "sharedWith": [
            "/api/scan"
          ]
        },
        "parameters": [
          {
            "name": "url",
            "in": "query",
            "required": true,
            "description": "Target address; bare domain names are accepted too.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Answer language: en for English, Dutch otherwise.",
            "schema": {
              "type": "string",
              "enum": [
                "nl",
                "en"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The measured result as a JSON object; /api-docs describes what this endpoint measures.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/meta": {
      "get": {
        "operationId": "meta",
        "summary": "Page HTML with a weight ceiling plus what our server found per requested asset: status, content type, bytes and manifest fields.",
        "description": "Page HTML with a weight ceiling plus what our server found per requested asset: status, content type, bytes and manifest fields.",
        "x-rate-limit": {
          "requests": 8,
          "windowSeconds": 60,
          "sharedWith": []
        },
        "parameters": [
          {
            "name": "url",
            "in": "query",
            "required": true,
            "description": "Target address; bare domain names are accepted too.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "assets",
            "in": "query",
            "required": false,
            "description": "Comma-separated asset paths or URLs to measure along with the page.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Answer language: en for English, Dutch otherwise.",
            "schema": {
              "type": "string",
              "enum": [
                "nl",
                "en"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The measured result as a JSON object; /api-docs describes what this endpoint measures.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/redirects": {
      "get": {
        "operationId": "redirects",
        "summary": "The redirect chain hop by hop, including status and headers per hop; by default also the chains of the bare and www variants alongside.",
        "description": "The redirect chain hop by hop, including status and headers per hop; by default also the chains of the bare and www variants alongside.",
        "x-rate-limit": {
          "requests": 8,
          "windowSeconds": 60,
          "sharedWith": []
        },
        "parameters": [
          {
            "name": "url",
            "in": "query",
            "required": true,
            "description": "Target address; bare domain names are accepted too.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "variants",
            "in": "query",
            "required": false,
            "description": "variants=0 drops the side variants and measures only the given URL.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Answer language: en for English, Dutch otherwise.",
            "schema": {
              "type": "string",
              "enum": [
                "nl",
                "en"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The measured result as a JSON object; /api-docs describes what this endpoint measures.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/robots": {
      "get": {
        "operationId": "robots",
        "summary": "A host's crawl rules: robots.txt parsed group by group (RFC 9307), its sitemap(s) entry by entry, and the cross-check whether sitemap URLs stand in paths that robots forbids.",
        "description": "A host's crawl rules: robots.txt parsed group by group (RFC 9307), its sitemap(s) entry by entry, and the cross-check whether sitemap URLs stand in paths that robots forbids.",
        "x-rate-limit": {
          "requests": 8,
          "windowSeconds": 60,
          "sharedWith": []
        },
        "parameters": [
          {
            "name": "url",
            "in": "query",
            "required": true,
            "description": "Target address; bare domain names are accepted too.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Answer language: en for English, Dutch otherwise.",
            "schema": {
              "type": "string",
              "enum": [
                "nl",
                "en"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The measured result as a JSON object; /api-docs describes what this endpoint measures.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/dns": {
      "get": {
        "operationId": "dns",
        "summary": "DNS records of a domain (A, AAAA, NS, MX, TXT, CNAME, SOA, CAA, SRV), reverse lookup on an IP address, plus SPF and DMARC interpretation with a grade.",
        "description": "DNS records of a domain (A, AAAA, NS, MX, TXT, CNAME, SOA, CAA, SRV), reverse lookup on an IP address, plus SPF and DMARC interpretation with a grade.",
        "x-rate-limit": {
          "requests": 20,
          "windowSeconds": 60,
          "sharedWith": []
        },
        "parameters": [
          {
            "name": "domain",
            "in": "query",
            "required": false,
            "description": "Domain to look up — pass domain or ip.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ip",
            "in": "query",
            "required": false,
            "description": "IPv4 or IPv6 address for the reverse lookup.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "types",
            "in": "query",
            "required": false,
            "description": "Record types to query separately, comma-separated (A, AAAA, MX, TXT, …).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Answer language: en for English, Dutch otherwise.",
            "schema": {
              "type": "string",
              "enum": [
                "nl",
                "en"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The measured result as a JSON object; /api-docs describes what this endpoint measures.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/dns-compare": {
      "get": {
        "operationId": "dnsCompare",
        "summary": "The same domain against four public resolvers, with the differences in answer, TTL and outcome alongside.",
        "description": "The same domain against four public resolvers, with the differences in answer, TTL and outcome alongside.",
        "x-rate-limit": {
          "requests": 10,
          "windowSeconds": 60,
          "sharedWith": []
        },
        "parameters": [
          {
            "name": "domain",
            "in": "query",
            "required": true,
            "description": "Domain queried against all four resolvers.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "types",
            "in": "query",
            "required": false,
            "description": "Record types to query separately, comma-separated (A, AAAA, MX, TXT, …).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Answer language: en for English, Dutch otherwise.",
            "schema": {
              "type": "string",
              "enum": [
                "nl",
                "en"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The measured result as a JSON object; /api-docs describes what this endpoint measures.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/email": {
      "get": {
        "operationId": "email",
        "summary": "What an email address says: structure, mailbox domain, MX presence and how that domain protects its sender (SPF, DMARC).",
        "description": "What an email address says: structure, mailbox domain, MX presence and how that domain protects its sender (SPF, DMARC).",
        "x-rate-limit": {
          "requests": 15,
          "windowSeconds": 60,
          "sharedWith": []
        },
        "parameters": [
          {
            "name": "address",
            "in": "query",
            "required": true,
            "description": "The email address; ?email= is accepted as the older spelling.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Answer language: en for English, Dutch otherwise.",
            "schema": {
              "type": "string",
              "enum": [
                "nl",
                "en"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The measured result as a JSON object; /api-docs describes what this endpoint measures.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/mail-security": {
      "get": {
        "operationId": "mailSecurity",
        "summary": "A domain's mail defense in one pass: SPF, DKIM selectors, DMARC policy and MX, with findings and a grade.",
        "description": "A domain's mail defense in one pass: SPF, DKIM selectors, DMARC policy and MX, with findings and a grade.",
        "x-rate-limit": {
          "requests": 8,
          "windowSeconds": 60,
          "sharedWith": []
        },
        "parameters": [
          {
            "name": "domain",
            "in": "query",
            "required": true,
            "description": "Domain whose mail security is measured.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "selectors",
            "in": "query",
            "required": false,
            "description": "Extra DKIM selectors to test along, comma-separated.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Answer language: en for English, Dutch otherwise.",
            "schema": {
              "type": "string",
              "enum": [
                "nl",
                "en"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The measured result as a JSON object; /api-docs describes what this endpoint measures.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/spf": {
      "get": {
        "operationId": "spf",
        "summary": "SPF evaluation of a domain against a sending IP address (with optional HELO): match, refusal or soft fail, including the include chain.",
        "description": "SPF evaluation of a domain against a sending IP address (with optional HELO): match, refusal or soft fail, including the include chain.",
        "x-rate-limit": {
          "requests": 15,
          "windowSeconds": 60,
          "sharedWith": []
        },
        "parameters": [
          {
            "name": "domain",
            "in": "query",
            "required": true,
            "description": "Sender domain.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ip",
            "in": "query",
            "required": true,
            "description": "Sending IP address.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "helo",
            "in": "query",
            "required": false,
            "description": "HELO/EHLO of the mail server, included in the evaluation.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Answer language: en for English, Dutch otherwise.",
            "schema": {
              "type": "string",
              "enum": [
                "nl",
                "en"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The measured result as a JSON object; /api-docs describes what this endpoint measures.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/mail-ports": {
      "get": {
        "operationId": "mailPorts",
        "summary": "Which mail ports (25, 465, 587) are open on a domain or mail server, with the greeting each server gives; MX servers are tested first by default.",
        "description": "Which mail ports (25, 465, 587) are open on a domain or mail server, with the greeting each server gives; MX servers are tested first by default.",
        "x-rate-limit": {
          "requests": 6,
          "windowSeconds": 60,
          "sharedWith": []
        },
        "parameters": [
          {
            "name": "host",
            "in": "query",
            "required": true,
            "description": "Domain or mail server; ?domain= is accepted as the older spelling.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ports",
            "in": "query",
            "required": false,
            "description": "Port numbers to test separately, comma-separated.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "mx",
            "in": "query",
            "required": false,
            "description": "mx=0 skips the MX lookup and tests only the given host.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Answer language: en for English, Dutch otherwise.",
            "schema": {
              "type": "string",
              "enum": [
                "nl",
                "en"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The measured result as a JSON object; /api-docs describes what this endpoint measures.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/headers": {
      "get": {
        "operationId": "headers",
        "summary": "The response headers of a URL: status, security headers, cache policy and content length.",
        "description": "The response headers of a URL: status, security headers, cache policy and content length.",
        "x-rate-limit": {
          "requests": 12,
          "windowSeconds": 60,
          "sharedWith": []
        },
        "parameters": [
          {
            "name": "url",
            "in": "query",
            "required": true,
            "description": "Target address; bare domain names are accepted too.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Answer language: en for English, Dutch otherwise.",
            "schema": {
              "type": "string",
              "enum": [
                "nl",
                "en"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The measured result as a JSON object; /api-docs describes what this endpoint measures.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/cert": {
      "get": {
        "operationId": "cert",
        "summary": "The certificate of an https target: issuer, validity, domain names (SAN), protocol and key strength, plus which TLS versions the server really accepts — measured version by version. Untrusted and expired certificates are reported as measured.",
        "description": "The certificate of an https target: issuer, validity, domain names (SAN), protocol and key strength, plus which TLS versions the server really accepts — measured version by version. Untrusted and expired certificates are reported as measured.",
        "x-rate-limit": {
          "requests": 12,
          "windowSeconds": 60,
          "sharedWith": []
        },
        "parameters": [
          {
            "name": "url",
            "in": "query",
            "required": true,
            "description": "https addresses only; other protocols answer with 400.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Answer language: en for English, Dutch otherwise.",
            "schema": {
              "type": "string",
              "enum": [
                "nl",
                "en"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The measured result as a JSON object; /api-docs describes what this endpoint measures.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/reach": {
      "get": {
        "operationId": "reach",
        "summary": "Whether a URL or domain is reachable: status, timing and repeated sample measurements, with separate measurements per address family (IPv4 and IPv6) and a parity verdict.",
        "description": "Whether a URL or domain is reachable: status, timing and repeated sample measurements, with separate measurements per address family (IPv4 and IPv6) and a parity verdict.",
        "x-rate-limit": {
          "requests": 8,
          "windowSeconds": 60,
          "sharedWith": []
        },
        "parameters": [
          {
            "name": "url",
            "in": "query",
            "required": true,
            "description": "Target address; bare domain names are accepted too.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "samples",
            "in": "query",
            "required": false,
            "description": "Number of repeated measurements, 1 to 5; default 3.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Answer language: en for English, Dutch otherwise.",
            "schema": {
              "type": "string",
              "enum": [
                "nl",
                "en"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The measured result as a JSON object; /api-docs describes what this endpoint measures.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/ports": {
      "get": {
        "operationId": "ports",
        "summary": "Which service ports (22, 53, 80, 443, 3389, …) on a domain or host name are reachable: open, closed or silent, per port with time and what the service itself said first. No grade — the measurement speaks.",
        "description": "Which service ports (22, 53, 80, 443, 3389, …) on a domain or host name are reachable: open, closed or silent, per port with time and what the service itself said first. No grade — the measurement speaks.",
        "x-rate-limit": {
          "requests": 6,
          "windowSeconds": 60,
          "sharedWith": []
        },
        "parameters": [
          {
            "name": "host",
            "in": "query",
            "required": true,
            "description": "Domain or host name; ?domain= is accepted as the older spelling.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ports",
            "in": "query",
            "required": false,
            "description": "Port numbers from the fixed list (21, 22, 53, 80, 443, 3389, 8080, 8443), comma-separated; default 22, 80 and 443. Ports off the list are not investigated and reported as portsSkipped.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Answer language: en for English, Dutch otherwise.",
            "schema": {
              "type": "string",
              "enum": [
                "nl",
                "en"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The measured result as a JSON object; /api-docs describes what this endpoint measures.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    }
  },
  "components": {
    "responses": {
      "BadRequest": {
        "description": "A required parameter is missing or the target is invalid.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            }
          }
        }
      },
      "MethodNotAllowed": {
        "description": "Only GET and HEAD are supported.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Per-endpoint limit (per visitor address and minute) or the shared cooldown on the same target exceeded; the retry-after header names the wait in seconds.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            }
          }
        }
      }
    },
    "schemas": {
      "ApiError": {
        "type": "object",
        "description": "The one error shape every endpoint shares; failure is present when our side can name the cause.",
        "properties": {
          "ok": {
            "type": "boolean",
            "const": false
          },
          "error": {
            "type": "string",
            "description": "A sentence the caller can act on."
          },
          "failure": {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "description": "Failure class, for example dns, timeout or refused."
              },
              "label": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          },
          "retryAfter": {
            "type": "integer",
            "description": "Only on 429: seconds to wait."
          }
        },
        "required": [
          "ok",
          "error"
        ]
      }
    }
  }
}
