{
    "openapi": "3.1.0",
    "info": {
        "title": "Citeerbaar open data en agent-laag",
        "version": "1.1.3",
        "description": "De machineleesbare laag van citeerbaar.nl: JSON-endpoints met feiten, prijzen, antwoorden, beweringen, begrippen, entiteiten en wijzigingen, plus het MCP- en A2A-endpoint. Alle GET-endpoints zijn openbaar, zonder sleutel, met ETag en CORS. Wijzigingen staan in /api/changes.json onder tool_changes.",
        "contact": {
            "name": "Citeerbaar",
            "url": "https://www.citeerbaar.nl/contact/",
            "email": "hello@citeerbaar.nl"
        },
        "license": {
            "name": "CC BY 4.0",
            "identifier": "CC-BY-4.0"
        }
    },
    "servers": [
        {
            "url": "https://www.citeerbaar.nl/"
        }
    ],
    "tags": [
        {
            "name": "Open data",
            "description": "Alleen-lezen JSON, CC BY 4.0, bronvermelding naar source_page."
        },
        {
            "name": "Agents",
            "description": "JSON-RPC-endpoints voor MCP-clients en A2A-agents."
        },
        {
            "name": "Status",
            "description": "Statuscontrole en deze beschrijving."
        }
    ],
    "paths": {
        "/api/facts.json": {
            "get": {
                "summary": "Feiten-API",
                "description": "De harde feiten over Citeerbaar: diensten, prijzen, doorlooptijden, btw-status en bedrijfsgegevens.",
                "tags": [
                    "Open data"
                ],
                "responses": {
                    "200": {
                        "description": "OK. Zwakke ETag; met If-None-Match volgt 304. Cache-Control: max-age=300, stale-while-revalidate=600.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "allOf": [
                                        {
                                            "$ref": "#/components/schemas/Envelope"
                                        },
                                        {
                                            "type": "object",
                                            "properties": {
                                                "organization": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                },
                                                "packages": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                },
                                                "discount_rules": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                },
                                                "guarantee": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                },
                                                "measurement_platforms": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                },
                                                "payment_terms": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                },
                                                "methodology_9_points": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                },
                                                "methodology_source_page": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                },
                                                "cases": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                },
                                                "claims": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                },
                                                "customer_statements": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                }
                                            },
                                            "additionalProperties": true
                                        }
                                    ]
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/content.json": {
            "get": {
                "summary": "Pagina-index",
                "description": "Elke gepubliceerde pagina met titel, samenvatting, type en wijzigingsdatum.",
                "tags": [
                    "Open data"
                ],
                "responses": {
                    "200": {
                        "description": "OK. Zwakke ETag; met If-None-Match volgt 304. Cache-Control: max-age=300, stale-while-revalidate=600.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "allOf": [
                                        {
                                            "$ref": "#/components/schemas/Envelope"
                                        },
                                        {
                                            "type": "object",
                                            "properties": {
                                                "count": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                },
                                                "items": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                }
                                            },
                                            "additionalProperties": true
                                        }
                                    ]
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/answers.json": {
            "get": {
                "summary": "Vragen en antwoorden",
                "description": "Vragen die zichtbaar op de site staan, met het antwoord, de bron-URL en de wijzigingsdatum van die pagina.",
                "tags": [
                    "Open data"
                ],
                "responses": {
                    "200": {
                        "description": "OK. Zwakke ETag; met If-None-Match volgt 304. Cache-Control: max-age=300, stale-while-revalidate=600.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "allOf": [
                                        {
                                            "$ref": "#/components/schemas/Envelope"
                                        },
                                        {
                                            "type": "object",
                                            "properties": {
                                                "count": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                },
                                                "items": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                }
                                            },
                                            "additionalProperties": true
                                        }
                                    ]
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/pricing.json": {
            "get": {
                "summary": "Prijzen",
                "description": "Alle pakketten met prijs, doorlooptijd en inhoud, plus verrekenregel en betaalvoorwaarden. Exclusief btw.",
                "tags": [
                    "Open data"
                ],
                "responses": {
                    "200": {
                        "description": "OK. Zwakke ETag; met If-None-Match volgt 304. Cache-Control: max-age=300, stale-while-revalidate=600.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "allOf": [
                                        {
                                            "$ref": "#/components/schemas/Envelope"
                                        },
                                        {
                                            "type": "object",
                                            "properties": {
                                                "currency": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                },
                                                "vat_status": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                },
                                                "vat_rate_pct": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                },
                                                "vat_note": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                },
                                                "source_page": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                },
                                                "packages": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                },
                                                "discount_rules": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                },
                                                "guarantee": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                },
                                                "payment_terms": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                },
                                                "measurement_platforms": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                },
                                                "methodology_9_points": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                }
                                            },
                                            "additionalProperties": true
                                        }
                                    ]
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/claims.json": {
            "get": {
                "summary": "Beweringen met bron",
                "description": "Een getoetst register: alleen cijfers waarvan de primaire bron is gecontroleerd en die zichtbaar met bronvermelding op een pagina staan.",
                "tags": [
                    "Open data"
                ],
                "responses": {
                    "200": {
                        "description": "OK. Zwakke ETag; met If-None-Match volgt 304. Cache-Control: max-age=300, stale-while-revalidate=600.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "allOf": [
                                        {
                                            "$ref": "#/components/schemas/Envelope"
                                        },
                                        {
                                            "type": "object",
                                            "properties": {
                                                "method": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                },
                                                "external_claims": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                },
                                                "own_results": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                }
                                            },
                                            "additionalProperties": true
                                        }
                                    ]
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/glossary.json": {
            "get": {
                "summary": "Begrippenlijst",
                "description": "De begrippen rond AI-vindbaarheid en GEO met hun definitie, waar mogelijk gekoppeld aan Wikipedia en Wikidata.",
                "tags": [
                    "Open data"
                ],
                "responses": {
                    "200": {
                        "description": "OK. Zwakke ETag; met If-None-Match volgt 304. Cache-Control: max-age=300, stale-while-revalidate=600.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "allOf": [
                                        {
                                            "$ref": "#/components/schemas/Envelope"
                                        },
                                        {
                                            "type": "object",
                                            "properties": {
                                                "source_page": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                },
                                                "count": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                },
                                                "items": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                }
                                            },
                                            "additionalProperties": true
                                        }
                                    ]
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/entities.json": {
            "get": {
                "summary": "Entiteitenkaart",
                "description": "De entiteiten waar de site over spreekt en hoe ze zich tot elkaar verhouden.",
                "tags": [
                    "Open data"
                ],
                "responses": {
                    "200": {
                        "description": "OK. Zwakke ETag; met If-None-Match volgt 304. Cache-Control: max-age=300, stale-while-revalidate=600.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "allOf": [
                                        {
                                            "$ref": "#/components/schemas/Envelope"
                                        },
                                        {
                                            "type": "object",
                                            "properties": {
                                                "organization": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                },
                                                "founder": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                },
                                                "services": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                },
                                                "clients": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                },
                                                "concepts": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                }
                                            },
                                            "additionalProperties": true
                                        }
                                    ]
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/changes.json": {
            "get": {
                "summary": "Wijzigingen",
                "description": "Alles wat is gepubliceerd of bijgewerkt, nieuwste eerst, zodat een crawler in één verzoek ziet wat er veranderde.",
                "tags": [
                    "Open data"
                ],
                "responses": {
                    "200": {
                        "description": "OK. Zwakke ETag; met If-None-Match volgt 304. Cache-Control: max-age=300, stale-while-revalidate=600.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "allOf": [
                                        {
                                            "$ref": "#/components/schemas/Envelope"
                                        },
                                        {
                                            "type": "object",
                                            "properties": {
                                                "count": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                },
                                                "items": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                },
                                                "schema_changes": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                },
                                                "tool_changes": {
                                                    "description": "Zie de payload; structuur staat in het antwoord zelf."
                                                }
                                            },
                                            "additionalProperties": true
                                        }
                                    ]
                                }
                            }
                        }
                    }
                }
            }
        },
        "/feed.json": {
            "get": {
                "summary": "Artikelfeed",
                "description": "De blogartikelen als JSON Feed, met volledige tekst. Formaat: JSON Feed 1.1.",
                "tags": [
                    "Open data"
                ],
                "responses": {
                    "200": {
                        "description": "OK. Zwakke ETag; met If-None-Match volgt 304. Cache-Control: max-age=300, stale-while-revalidate=600.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "version",
                                        "title",
                                        "items"
                                    ],
                                    "properties": {
                                        "version": {
                                            "type": "string"
                                        },
                                        "title": {
                                            "type": "string"
                                        },
                                        "items": {
                                            "type": "array",
                                            "items": {
                                                "type": "object",
                                                "additionalProperties": true
                                            }
                                        }
                                    },
                                    "additionalProperties": true
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/health.json": {
            "get": {
                "summary": "Statuscontrole",
                "description": "Geeft ok, de versie van de agent-laag en de servertijd. Geen cache, geen limiet. Bedoeld voor uptime-monitors en als eerste aanroep van een agent.",
                "tags": [
                    "Status"
                ],
                "responses": {
                    "200": {
                        "description": "OK. Cache-Control: no-store, geen ETag: elk antwoord is vers.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "ok",
                                        "version",
                                        "time"
                                    ],
                                    "properties": {
                                        "ok": {
                                            "type": "boolean"
                                        },
                                        "version": {
                                            "type": "string"
                                        },
                                        "time": {
                                            "type": "string",
                                            "format": "date-time"
                                        },
                                        "services": {
                                            "type": "object",
                                            "additionalProperties": {
                                                "type": "string",
                                                "format": "uri"
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/openapi.json": {
            "get": {
                "summary": "Deze beschrijving",
                "description": "Dit document.",
                "tags": [
                    "Status"
                ],
                "responses": {
                    "200": {
                        "description": "OK. Zwakke ETag; met If-None-Match volgt 304. Cache-Control: max-age=300, stale-while-revalidate=600.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "additionalProperties": true
                                }
                            }
                        }
                    }
                }
            }
        },
        "/mcp": {
            "post": {
                "summary": "MCP-endpoint",
                "description": "Model Context Protocol over HTTP, één JSON-RPC-bericht per verzoek. Versies tot en met 2025-11-25: initialize, dan notifications/initialized, dan tools/list. Versie 2026-07-28: geen handshake; stuur tools/list of tools/call direct, met de header MCP-Protocol-Version: 2026-07-28 en dezelfde versie in params._meta[\"io.modelcontextprotocol/protocolVersion\"] (optioneel eerst server/discover). Elf tools; alleen request_quick_check schrijft (5 per uur per IP). Protocolversies: 2026-07-28, 2025-11-25, 2025-06-18, 2025-03-26.",
                "tags": [
                    "Agents"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "jsonrpc",
                                    "method"
                                ],
                                "properties": {
                                    "jsonrpc": {
                                        "type": "string",
                                        "const": "2.0"
                                    },
                                    "id": {
                                        "oneOf": [
                                            {
                                                "type": "string"
                                            },
                                            {
                                                "type": "integer"
                                            },
                                            {
                                                "type": "null"
                                            }
                                        ]
                                    },
                                    "method": {
                                        "type": "string",
                                        "enum": [
                                            "initialize",
                                            "server/discover",
                                            "ping",
                                            "tools/list",
                                            "tools/call",
                                            "resources/list",
                                            "resources/read",
                                            "prompts/list"
                                        ]
                                    },
                                    "params": {
                                        "type": "object",
                                        "additionalProperties": true
                                    }
                                }
                            },
                            "examples": {
                                "initialize": {
                                    "summary": "Stap 1: handshake",
                                    "value": {
                                        "jsonrpc": "2.0",
                                        "id": 1,
                                        "method": "initialize",
                                        "params": {
                                            "protocolVersion": "2025-06-18",
                                            "capabilities": {},
                                            "clientInfo": {
                                                "name": "voorbeeld-client",
                                                "version": "1.0"
                                            }
                                        }
                                    }
                                },
                                "tools_list": {
                                    "summary": "Stap 2: welke tools zijn er",
                                    "value": {
                                        "jsonrpc": "2.0",
                                        "id": 2,
                                        "method": "tools/list"
                                    }
                                },
                                "tools_call_search": {
                                    "summary": "Stap 3: zoeken (veilige leesroute, ook om te testen)",
                                    "value": {
                                        "jsonrpc": "2.0",
                                        "id": 3,
                                        "method": "tools/call",
                                        "params": {
                                            "name": "search",
                                            "arguments": {
                                                "query": "pakketten"
                                            }
                                        }
                                    }
                                },
                                "ping": {
                                    "summary": "Bereikbaarheidscontrole, result is {}",
                                    "value": {
                                        "jsonrpc": "2.0",
                                        "id": 4,
                                        "method": "ping"
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "JSON-RPC-antwoord: result bij succes, error bij een fout op protocolniveau (ook bij een onbekende methode). error.message is Engels, error.data.message_nl Nederlands.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/JsonRpcResponse"
                                }
                            }
                        }
                    },
                    "400": {
                        "description": "Geen geldige JSON, een batch, of een ontbrekend verplicht veld."
                    },
                    "405": {
                        "description": "Alleen POST."
                    },
                    "429": {
                        "description": "Limiet: 60 verzoeken per 60 seconden per IP. Retry-After en RateLimit-* zeggen wanneer het weer kan.",
                        "headers": {
                            "Retry-After": {
                                "schema": {
                                    "type": "integer"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/a2a": {
            "post": {
                "summary": "A2A-endpoint",
                "description": "Agent-to-Agent JSON-RPC. De agent-card met skills en voorbeelden staat op /.well-known/agent-card.json.",
                "tags": [
                    "Agents"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "jsonrpc",
                                    "method"
                                ],
                                "properties": {
                                    "jsonrpc": {
                                        "type": "string",
                                        "const": "2.0"
                                    },
                                    "id": {
                                        "oneOf": [
                                            {
                                                "type": "string"
                                            },
                                            {
                                                "type": "integer"
                                            },
                                            {
                                                "type": "null"
                                            }
                                        ]
                                    },
                                    "method": {
                                        "type": "string",
                                        "enum": [
                                            "SendMessage",
                                            "message/send",
                                            "GetTask",
                                            "tasks/get",
                                            "ListTasks",
                                            "tasks/list",
                                            "CancelTask",
                                            "tasks/cancel"
                                        ]
                                    },
                                    "params": {
                                        "type": "object",
                                        "additionalProperties": true
                                    }
                                }
                            },
                            "examples": {
                                "message_send": {
                                    "summary": "Vraag stellen (SendMessage of message/send), antwoord komt synchroon als Message",
                                    "value": {
                                        "jsonrpc": "2.0",
                                        "id": 1,
                                        "method": "message/send",
                                        "params": {
                                            "message": {
                                                "role": "user",
                                                "messageId": "voorbeeld-1",
                                                "parts": [
                                                    {
                                                        "kind": "text",
                                                        "text": "Wat kost de Scan?"
                                                    }
                                                ]
                                            }
                                        }
                                    }
                                },
                                "tasks_get": {
                                    "summary": "Taak opvragen (GetTask of tasks/get); deze agent maakt geen taken aan, dus het antwoord is een fout -32001",
                                    "value": {
                                        "jsonrpc": "2.0",
                                        "id": 2,
                                        "method": "tasks/get",
                                        "params": {
                                            "id": "voorbeeld-taak"
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "JSON-RPC-antwoord: result bij succes, error bij een fout op protocolniveau (ook bij een onbekende methode). error.message is Engels, error.data.message_nl Nederlands.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/JsonRpcResponse"
                                }
                            }
                        }
                    },
                    "400": {
                        "description": "Geen geldige JSON, een batch, of een ontbrekend verplicht veld."
                    },
                    "405": {
                        "description": "Alleen POST."
                    },
                    "429": {
                        "description": "Limiet: 60 verzoeken per 60 seconden per IP. Retry-After en RateLimit-* zeggen wanneer het weer kan.",
                        "headers": {
                            "Retry-After": {
                                "schema": {
                                    "type": "integer"
                                }
                            }
                        }
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "Envelope": {
                "type": "object",
                "description": "Gedeelde omslag van elk JSON-endpoint.",
                "required": [
                    "name",
                    "url",
                    "generated_at",
                    "last_modified",
                    "content_hash"
                ],
                "properties": {
                    "name": {
                        "type": "string"
                    },
                    "description": {
                        "type": "string"
                    },
                    "url": {
                        "type": "string",
                        "format": "uri"
                    },
                    "inLanguage": {
                        "type": "string"
                    },
                    "publisher": {
                        "type": "object",
                        "additionalProperties": true
                    },
                    "license": {
                        "type": "string",
                        "format": "uri"
                    },
                    "usage": {
                        "type": "string"
                    },
                    "generated_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Moment van deze ophaling."
                    },
                    "last_modified": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Schuift alleen op als de inhoud verandert."
                    },
                    "content_hash": {
                        "type": "string"
                    },
                    "related": {
                        "type": "object",
                        "additionalProperties": {
                            "type": "string",
                            "format": "uri"
                        }
                    }
                }
            },
            "JsonRpcResponse": {
                "type": "object",
                "required": [
                    "jsonrpc",
                    "id"
                ],
                "properties": {
                    "jsonrpc": {
                        "type": "string",
                        "const": "2.0"
                    },
                    "id": {
                        "oneOf": [
                            {
                                "type": "string"
                            },
                            {
                                "type": "integer"
                            },
                            {
                                "type": "null"
                            }
                        ]
                    },
                    "result": {
                        "type": "object",
                        "additionalProperties": true
                    },
                    "error": {
                        "type": "object",
                        "required": [
                            "code",
                            "message"
                        ],
                        "properties": {
                            "code": {
                                "type": "integer"
                            },
                            "message": {
                                "type": "string",
                                "description": "Engels."
                            },
                            "data": {
                                "type": "object",
                                "properties": {
                                    "message_nl": {
                                        "type": "string"
                                    }
                                },
                                "additionalProperties": true
                            }
                        }
                    }
                }
            }
        }
    }
}