{
  "openapi": "3.1.0",
  "info": {
    "title": "EU Whistleblower Tool Directory — read-only catalog",
    "summary": "Static JSON catalog of whistleblower reporting platforms reviewed for EU Directive 2019/1937 compliance.",
    "description": "A read-only, static JSON catalog served from the whistleblowertools.eu Hugo site. No authentication, no rate limits, no state — each endpoint is a CDN-cached JSON file regenerated on every deploy. Suitable for AI agents that need structured comparison data for whistleblower platforms.",
    "version": "1.0.0",
    "contact": {
      "name": "EthicsPortal",
      "url": "https://ethicsportal.eu",
      "email": "support@ethicsportal.eu"
    },
    "license": {
      "name": "CC BY 4.0",
      "url": "https://creativecommons.org/licenses/by/4.0/"
    }
  },
  "servers": [
    {
      "url": "https://whistleblowertools.eu",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "catalog",
      "description": "Directory listing and per-tool profiles"
    }
  ],
  "paths": {
    "/tools/index.json": {
      "get": {
        "operationId": "listTools",
        "summary": "List all reviewed whistleblower tools",
        "description": "Returns a summary record for every tool in the directory, sorted alphabetically by name.",
        "tags": ["catalog"],
        "responses": {
          "200": {
            "description": "Tool list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolList"
                }
              }
            }
          }
        }
      }
    },
    "/tools/{slug}/index.json": {
      "get": {
        "operationId": "getTool",
        "summary": "Get full profile for a single tool",
        "description": "Returns the complete structured profile for one tool, including pricing, hosting, certifications, country coverage, and notes.",
        "tags": ["catalog"],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "Tool identifier, lower-case-hyphenated.",
            "schema": {
              "type": "string",
              "enum": [
                "2secure",
                "alertcys",
                "amodit",
                "ashio",
                "avertizori-integritate",
                "canaletico",
                "cluezo",
                "clym",
                "complylaw",
                "comunica-whistleblowing",
                "confide",
                "confidential-reporting-system",
                "confidly",
                "cortina",
                "digitalpa",
                "digitech",
                "disclosurely",
                "doublevoice",
                "e-cas",
                "e-nform",
                "e-zaupnik",
                "easywhistle",
                "elker",
                "ethiclink",
                "ethicontrol",
                "ethicorp",
                "ethicsportal",
                "eticalert",
                "faceup",
                "falcony",
                "firmsys",
                "flustron",
                "formalize",
                "fraudline",
                "globalsuite",
                "gocomply",
                "gofox",
                "heimdal",
                "hinschg-meldungen",
                "hintbird",
                "hintbox",
                "hintcatcher",
                "hinweis-de",
                "hitrust",
                "iblow",
                "ilmoituskanava",
                "integritycounts",
                "integrityline",
                "integritylog",
                "interaktiv-sakerhet",
                "isweb",
                "ithikios",
                "lantero",
                "legalsending",
                "legaltegrity",
                "lumgo",
                "maistransparente",
                "mittvarsel",
                "myethos",
                "mygovernance",
                "mysecway",
                "navex",
                "northwhistle",
                "ohlasto",
                "openblow",
                "opensource-hinweisgeberportal",
                "openwhistle",
                "osapiens",
                "otris",
                "phoenix",
                "portal-das-denuncias",
                "preeco",
                "prima-whistleblower",
                "pro-vastuullisuus",
                "qnister",
                "raportare-avertizori",
                "reler",
                "safecall",
                "secureblowing",
                "segnala-sicuro",
                "signalement-net",
                "signalrh",
                "soterna",
                "speakup",
                "sygnaapp",
                "sygnali",
                "sygnalista",
                "sygnalista365",
                "sygnanet",
                "tell-it",
                "tell-us",
                "tilkynna",
                "tissla",
                "truespeak",
                "trustbox",
                "trustif",
                "trusty",
                "tucanaldedenuncias",
                "uptalkly",
                "vispato",
                "visselblasaren",
                "visslan",
                "voxwel",
                "walor",
                "wemoral",
                "whiblo",
                "whisly",
                "whisper",
                "whispero",
                "whispli",
                "whistboard",
                "whistleblow-ro",
                "whistleblowing24",
                "whistlebox",
                "whistlechannel",
                "whistleflow",
                "whistlefox",
                "whistlelaw",
                "whistlelink",
                "whistleon",
                "whistleport",
                "whistlesecure",
                "whistlesystem",
                "whistleup",
                "whistly",
                "whizzla",
                "wibso",
                "witik",
                "workinconfidence",
                "zateo"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tool profile",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Tool"
                }
              }
            }
          },
          "404": {
            "description": "Unknown slug"
          }
        }
      }
    },
    "/llms.txt": {
      "get": {
        "operationId": "llmsSummary",
        "summary": "LLM-oriented site summary (plain text)",
        "tags": ["catalog"],
        "responses": {
          "200": {
            "description": "Plain-text summary",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/llms-full.txt": {
      "get": {
        "operationId": "llmsFull",
        "summary": "Complete site content dump (plain text)",
        "tags": ["catalog"],
        "responses": {
          "200": {
            "description": "Plain-text full content",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ToolList": {
        "type": "object",
        "required": ["count", "tools"],
        "properties": {
          "source": {
            "type": "string",
            "format": "uri"
          },
          "generated_at": {
            "type": "string",
            "format": "date-time"
          },
          "count": {
            "type": "integer",
            "minimum": 0
          },
          "tools": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ToolSummary"
            }
          }
        }
      },
      "ToolSummary": {
        "type": "object",
        "required": ["slug", "name", "website", "profile_url"],
        "properties": {
          "slug": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "tagline": {
            "type": "string"
          },
          "website": {
            "type": "string",
            "format": "uri"
          },
          "profile_url": {
            "type": "string",
            "format": "uri"
          },
          "data_url": {
            "type": "string",
            "format": "uri",
            "description": "URL to the full Tool JSON for this entry"
          },
          "headquarters": {
            "type": ["string", "null"]
          },
          "founded": {
            "type": ["integer", "null"]
          },
          "domain_registered": {
            "type": ["string", "null"],
            "format": "date",
            "description": "When this domain was first registered, per the registry. Not a founding date: where a product sits on its parent company's domain the date describes the parent. Null where the registry withholds it, which includes every .de and .eu domain."
          },
          "dnssec": {
            "type": ["boolean", "null"],
            "description": "Whether the domain is DNSSEC-signed."
          },
          "dmarc_policy": {
            "type": ["string", "null"],
            "description": "DMARC policy published for the domain: reject, quarantine, none, or absent."
          },
          "portal_url": {
            "type": ["string", "null"],
            "description": "Reporting surface confirmed to carry an intake form. Absent where none is publicly reachable."
          },
          "portal_third_party": {
            "type": ["array", "null"],
            "description": "Third-party domains requested by the reporting page; content-delivery hosts excluded.",
            "items": {
              "type": "string"
            }
          },
          "portal_security_headers": {
            "type": ["integer", "null"],
            "description": "How many of HSTS, CSP, X-Frame-Options and Referrer-Policy the reporting page returns (0-4)."
          },
          "demo_portal": {
            "type": ["string", "null"],
            "description": "Publicly reachable demo portal, where the vendor runs one. Absent where the vendor offers only a demo request form."
          },
          "vendor_reviewed": {
            "type": ["string", "null"],
            "description": "Date the vendor read this entry and gave feedback on it. Not an endorsement of what the entry says.",
            "format": "date"
          },
          "ownership": {
            "type": ["string", "null"]
          },
          "employees": {
            "type": ["string", "null"]
          },
          "hosting": {
            "type": ["string", "null"]
          },
          "eu_hosting": {
            "type": ["string", "null"],
            "enum": ["eu", "selectable", "undisclosed", null]
          },
          "uses_ai": {
            "type": ["boolean", "string", "null"],
            "enum": [true, false, "undisclosed", null],
            "description": "Whether vendor-published material indicates AI processing of report content."
          },
          "ai_note": {
            "type": ["string", "null"],
            "description": "Short evidence note for AI processing, when applicable."
          },
          "pricing": {
            "type": ["string", "null"]
          },
          "pricing_model": {
            "type": ["string", "null"],
            "enum": ["credits", "flat", "mixed", "per_employee", "quote", "tiered", null]
          },
          "procurement_model": {
            "type": ["string", "null"],
            "enum": ["demo", "mixed", "sales", "self_serve", null]
          },
          "free_trial": {
            "type": ["boolean", "null"]
          },
          "api_access": {
            "type": ["boolean", "string", "null"],
            "enum": [true, false, "undisclosed", null]
          },
          "anonymous_reporting": {
            "type": ["boolean", "null"]
          },
          "case_management": {
            "type": ["boolean", "null"]
          },
          "multi_channel": {
            "type": ["boolean", "string", "null"],
            "enum": [true, false, "undisclosed", null]
          },
          "gdpr_compliant": {
            "type": ["boolean", "null"]
          },
          "eu_directive_compliant": {
            "type": ["boolean", "null"]
          },
          "cert_tier": {
            "type": ["string", "null"]
          },
          "certifications": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "geographic_reach": {
            "type": ["string", "null"]
          },
          "country_codes": {
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "^[A-Z]{2}$"
            }
          },
          "languages": {
            "type": ["integer", "null"],
            "minimum": 0
          },
          "eu_language_count": {
            "type": ["integer", "null"],
            "minimum": 0
          },
          "last_verified": {
            "type": ["string", "null"],
            "format": "date"
          },
          "has_scoring": {
            "type": "boolean",
            "description": "Whether the profile includes a rubric scoring block."
          },
          "scoring_tier": {
            "type": ["string", "null"],
            "description": "Access tier used for scoring, such as P, P+R, or P+R+H."
          },
          "base_total": {
            "type": ["integer", "null"],
            "description": "Base score under the published 50-point rubric."
          },
          "base_max": {
            "type": ["integer", "null"],
            "description": "Maximum base score for the rubric version."
          },
          "rubric_version": {
            "type": ["string", "null"]
          }
        }
      },
      "Tool": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ToolSummary"
          },
          {
            "type": "object",
            "properties": {
              "founded": {
                "type": ["integer", "null"]
              },
              "ownership": {
                "type": ["string", "null"]
              },
              "employees": {
                "type": ["string", "null"]
              },
              "hosting": {
                "type": ["string", "null"]
              },
              "uses_ai": {
                "type": ["boolean", "string", "null"],
                "enum": [true, false, "undisclosed", null]
              },
              "ai_note": {
                "type": ["string", "null"]
              },
              "two_factor_auth": {
                "type": ["boolean", "string", "null"],
                "enum": [true, false, "undisclosed", null]
              },
              "audit_log": {
                "type": ["boolean", "string", "null"],
                "enum": [true, false, "undisclosed", null]
              },
              "encryption": {
                "type": ["string", "null"],
                "enum": ["at_rest", "customer_key", "e2e", "undisclosed", null]
              },
              "pricing_note": {
                "type": ["string", "null"]
              },
              "pricing_surprises": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "eu_languages": {
                "type": ["string", "null"],
                "enum": ["all_24", "partial", "unpublished", null]
              },
              "entry_tier_band": {
                "type": ["string", "null"],
                "enum": ["under_50", "50_to_100", "100_to_200", "over_200", "unpublished", null]
              },
              "ideal_for": {
                "type": ["string", "null"]
              },
              "strengths": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "national_laws_supported": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "sources": {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "uri"
                }
              },
              "scoring": {
                "$ref": "#/components/schemas/Scoring"
              },
              "notes_markdown": {
                "type": "string"
              },
              "spf": {
                "type": ["boolean", "null"],
                "description": "Whether an SPF record is published for the domain."
              },
              "caa": {
                "type": ["boolean", "null"],
                "description": "Whether CAA records restrict which authorities may issue certificates."
              },
              "legal_entity": {
                "type": ["string", "null"],
                "description": "Company named on the vendor's own imprint, where one could be read reliably."
              },
              "company_id": {
                "type": ["string", "null"],
                "description": "Public company-register or VAT identifier, prefixed with its type."
              },
              "company_id_source": {
                "type": ["string", "null"],
                "description": "Imprint URL the identifier was read from."
              },
              "portal_cookies": {
                "type": ["integer", "null"],
                "description": "Cookies set by the reporting page on load."
              },
              "portal_checked": {
                "type": ["string", "null"],
                "description": "Date the reporting surface was last audited. Absent until a human has confirmed the URL is a reporter-facing form.",
                "format": "date"
              },
              "demo_portal": {
                "type": ["string", "null"],
                "description": "Publicly reachable demo portal, where the vendor runs one. Absent where the vendor offers only a demo request form."
              },
              "demo_portal_checked": {
                "type": ["string", "null"],
                "description": "Date the demo portal was last confirmed to respond.",
                "format": "date"
              },
              "vendor_reviewed": {
                "type": ["string", "null"],
                "description": "Date the vendor read this entry and gave feedback on it. Not an endorsement of what the entry says.",
                "format": "date"
              }
            }
          }
        ]
      },
      "Scoring": {
        "type": ["object", "null"],
        "additionalProperties": true,
        "properties": {
          "last_reviewed": {
            "type": ["string", "null"],
            "format": "date"
          },
          "tier": {
            "type": ["string", "null"],
            "description": "Access tier used for the review: P, P+R, or P+R+H."
          },
          "tier_ceiling_note": {
            "type": ["string", "null"]
          },
          "rubric_version": {
            "type": ["string", "null"]
          },
          "base_total": {
            "type": ["integer", "null"]
          },
          "base_max": {
            "type": ["integer", "null"]
          },
          "strengths": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "weaknesses": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "standout": {
            "type": ["string", "null"]
          },
          "criteria": {
            "type": "object",
            "description": "Criterion id to score/evidence object. Criterion ids match /data/rankings/criteria.yaml in the source repository.",
            "additionalProperties": {
              "$ref": "#/components/schemas/ScoreCriterion"
            }
          }
        }
      },
      "ScoreCriterion": {
        "type": "object",
        "required": ["score", "evidence"],
        "properties": {
          "score": {
            "type": "integer",
            "minimum": 0,
            "maximum": 2
          },
          "evidence": {
            "type": "string"
          }
        }
      }
    }
  }
}
