{
 "openapi": "3.1.0",
 "info": {
  "title": "Appfront inhoud-API",
  "version": "1.0.0",
  "description": "Openbare, alleen-lezen API om appfront.nl te doorzoeken en pagina's als Markdown op te halen. Geen authenticatie. Limiet 120 verzoeken per minuut per IP (de MCP-server: 1.200). Dezelfde functies zijn beschikbaar als MCP-server op https://appfront.nl/mcp.",
  "contact": {
   "name": "Appfront",
   "email": "info@appfront.nl",
   "url": "https://appfront.nl/contact"
  }
 },
 "servers": [
  {
   "url": "https://appfront.nl/api/v1"
  }
 ],
 "security": [],
 "paths": {
  "/search": {
   "get": {
    "operationId": "searchPages",
    "summary": "Zoek pagina's op appfront.nl",
    "description": "Volledige-tekstzoeken over alle pagina's (titel weegt het zwaarst). Geeft titel, URL, beschrijving, sectie en een fragment.",
    "parameters": [
     {
      "name": "q",
      "in": "query",
      "required": true,
      "description": "Zoekvraag, bij voorkeur Nederlands.",
      "schema": {
       "type": "string",
       "minLength": 2,
       "maxLength": 200
      }
     },
     {
      "name": "limit",
      "in": "query",
      "required": false,
      "description": "Aantal resultaten (1-25).",
      "schema": {
       "type": "integer",
       "minimum": 1,
       "maximum": 25,
       "default": 10
      }
     },
     {
      "name": "section",
      "in": "query",
      "required": false,
      "description": "Beperk tot een sectie.",
      "schema": {
       "type": "string",
       "enum": [
        "software",
        "app",
        "koppeling",
        "web",
        "ai",
        "branche",
        "kennisbank",
        "vergelijking",
        "blog",
        "case",
        "tool",
        "bedrijf",
        "overig"
       ]
      }
     },
     {
      "name": "lang",
      "in": "query",
      "required": false,
      "description": "Taal van de resultaten: auto (standaard), nl of en.",
      "schema": {
       "type": "string",
       "enum": [
        "auto",
        "nl",
        "en"
       ],
       "default": "auto"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "OK",
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimit-Limit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimit-Remaining"
       },
       "RateLimit-Reset": {
        "$ref": "#/components/headers/RateLimit-Reset"
       }
      },
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/SearchResults"
        }
       }
      }
     },
     "429": {
      "description": "Te veel verzoeken; wacht Retry-After seconden.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "500": {
      "description": "Serverfout.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "400": {
      "description": "Ontbrekende of ongeldige parameter.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/page": {
   "get": {
    "operationId": "getPage",
    "summary": "Haal één pagina op als Markdown",
    "description": "Geeft de volledige inhoud van een appfront.nl-pagina als Markdown, met titel, beschrijving, sectie en lastmod.",
    "parameters": [
     {
      "name": "url",
      "in": "query",
      "required": true,
      "description": "Volledige URL of pad van de pagina.",
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "OK",
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimit-Limit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimit-Remaining"
       },
       "RateLimit-Reset": {
        "$ref": "#/components/headers/RateLimit-Reset"
       }
      },
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Page"
        }
       }
      }
     },
     "429": {
      "description": "Te veel verzoeken; wacht Retry-After seconden.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "500": {
      "description": "Serverfout.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "400": {
      "description": "Ontbrekende parameter.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "404": {
      "description": "Pagina niet gevonden.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/comparisons": {
   "get": {
    "operationId": "listComparisons",
    "summary": "Vergelijkingen en top-lijsten",
    "description": "Alle vergelijkingspagina's ('De beste ... van Nederland') met titel, URL, aantal partijen en de eerste drie. Optioneel filteren op onderwerp.",
    "parameters": [
     {
      "name": "topic",
      "in": "query",
      "required": false,
      "description": "Onderwerp, bv. crm, transport, uitzendbureau.",
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "OK",
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimit-Limit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimit-Remaining"
       },
       "RateLimit-Reset": {
        "$ref": "#/components/headers/RateLimit-Reset"
       }
      },
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Comparisons"
        }
       }
      }
     },
     "429": {
      "description": "Te veel verzoeken; wacht Retry-After seconden.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "500": {
      "description": "Serverfout.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/comparison": {
   "get": {
    "operationId": "getComparison",
    "summary": "Eén vergelijking als gestructureerde lijst",
    "description": "Partijen van één vergelijking op volgorde, met website, omschrijving en kenmerken uit de vergelijkingstabel. Appfront stelde de lijst samen en staat er zelf in.",
    "parameters": [
     {
      "name": "url",
      "in": "query",
      "required": true,
      "description": "URL of pad van de vergelijking.",
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "OK",
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimit-Limit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimit-Remaining"
       },
       "RateLimit-Reset": {
        "$ref": "#/components/headers/RateLimit-Reset"
       }
      },
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Comparison"
        }
       }
      }
     },
     "429": {
      "description": "Te veel verzoeken; wacht Retry-After seconden.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "500": {
      "description": "Serverfout.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "400": {
      "description": "Ontbrekende parameter.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "404": {
      "description": "Vergelijking niet gevonden.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/sections": {
   "get": {
    "operationId": "listSections",
    "summary": "Secties van de site",
    "description": "Secties van appfront.nl met omschrijving en aantal pagina's.",
    "responses": {
     "200": {
      "description": "OK",
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimit-Limit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimit-Remaining"
       },
       "RateLimit-Reset": {
        "$ref": "#/components/headers/RateLimit-Reset"
       }
      },
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Sections"
        }
       }
      }
     },
     "429": {
      "description": "Te veel verzoeken; wacht Retry-After seconden.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "500": {
      "description": "Serverfout.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/contact": {
   "get": {
    "operationId": "getContact",
    "summary": "Contactgegevens van Appfront",
    "description": "Contactgegevens en hoe een gebruiker een aanvraag doet.",
    "responses": {
     "200": {
      "description": "OK",
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimit-Limit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimit-Remaining"
       },
       "RateLimit-Reset": {
        "$ref": "#/components/headers/RateLimit-Reset"
       }
      },
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Contact"
        }
       }
      }
     },
     "429": {
      "description": "Te veel verzoeken; wacht Retry-After seconden.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "500": {
      "description": "Serverfout.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  },
  "/health": {
   "get": {
    "operationId": "getHealth",
    "summary": "Status van de API",
    "description": "Geeft aan of de API werkt, hoeveel pagina's in de index staan en wanneer die is bijgewerkt.",
    "responses": {
     "200": {
      "description": "OK",
      "headers": {
       "RateLimit-Limit": {
        "$ref": "#/components/headers/RateLimit-Limit"
       },
       "RateLimit-Remaining": {
        "$ref": "#/components/headers/RateLimit-Remaining"
       },
       "RateLimit-Reset": {
        "$ref": "#/components/headers/RateLimit-Reset"
       }
      },
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Health"
        }
       }
      }
     },
     "429": {
      "description": "Te veel verzoeken; wacht Retry-After seconden.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     },
     "500": {
      "description": "Serverfout.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Error"
        }
       }
      }
     }
    }
   }
  }
 },
 "components": {
  "headers": {
   "RateLimit-Limit": {
    "description": "Maximum aantal verzoeken per venster.",
    "schema": {
     "type": "integer"
    }
   },
   "RateLimit-Remaining": {
    "description": "Resterende verzoeken in dit venster.",
    "schema": {
     "type": "integer"
    }
   },
   "RateLimit-Reset": {
    "description": "Seconden tot het venster opnieuw begint.",
    "schema": {
     "type": "integer"
    }
   }
  },
  "schemas": {
   "Error": {
    "type": "object",
    "required": [
     "error"
    ],
    "properties": {
     "error": {
      "type": "object",
      "required": [
       "code",
       "message"
      ],
      "properties": {
       "code": {
        "type": "string",
        "description": "Machineleesbare foutcode, bv. not_found, missing_parameter, rate_limited."
       },
       "message": {
        "type": "string"
       },
       "hint": {
        "type": "string",
        "description": "Wat de agent kan doen om het op te lossen."
       },
       "docs": {
        "type": "string",
        "format": "uri"
       }
      }
     }
    }
   },
   "SearchResult": {
    "type": "object",
    "properties": {
     "title": {
      "type": "string"
     },
     "url": {
      "type": "string",
      "format": "uri"
     },
     "description": {
      "type": "string"
     },
     "section": {
      "type": "string"
     },
     "snippet": {
      "type": "string"
     },
     "score": {
      "type": "number"
     },
     "match": {
      "type": "string",
      "enum": [
       "all_terms",
       "some_terms"
      ]
     }
    }
   },
   "SearchResults": {
    "type": "object",
    "properties": {
     "query": {
      "type": "string"
     },
     "count": {
      "type": "integer"
     },
     "results": {
      "type": "array",
      "items": {
       "$ref": "#/components/schemas/SearchResult"
      }
     }
    }
   },
   "Page": {
    "type": "object",
    "properties": {
     "url": {
      "type": "string",
      "format": "uri"
     },
     "title": {
      "type": "string"
     },
     "description": {
      "type": "string"
     },
     "section": {
      "type": "string"
     },
     "lastmod": {
      "type": "string"
     },
     "markdown": {
      "type": "string"
     }
    }
   },
   "Sections": {
    "type": "object",
    "properties": {
     "updated": {
      "type": "string"
     },
     "sections": {
      "type": "array",
      "items": {
       "type": "object",
       "properties": {
        "id": {
         "type": "string"
        },
        "name": {
         "type": "string"
        },
        "description": {
         "type": "string"
        },
        "pages": {
         "type": "integer"
        }
       }
      }
     }
    }
   },
   "Comparisons": {
    "type": "object",
    "properties": {
     "count": {
      "type": "integer"
     },
     "comparisons": {
      "type": "array",
      "items": {
       "type": "object",
       "properties": {
        "title": {
         "type": "string"
        },
        "url": {
         "type": "string",
         "format": "uri"
        },
        "description": {
         "type": "string"
        },
        "count": {
         "type": "integer"
        },
        "top": {
         "type": "array",
         "items": {
          "type": "string"
         }
        }
       }
      }
     }
    }
   },
   "Comparison": {
    "type": "object",
    "properties": {
     "url": {
      "type": "string",
      "format": "uri"
     },
     "title": {
      "type": "string"
     },
     "description": {
      "type": "string"
     },
     "count": {
      "type": "integer"
     },
     "compiled_by": {
      "type": "string"
     },
     "items": {
      "type": "array",
      "items": {
       "type": "object",
       "properties": {
        "rank": {
         "type": "integer"
        },
        "name": {
         "type": "string"
        },
        "url": {
         "type": [
          "string",
          "null"
         ]
        },
        "description": {
         "type": "string"
        },
        "details": {
         "type": "object",
         "additionalProperties": {
          "type": "string"
         }
        }
       }
      }
     }
    }
   },
   "Contact": {
    "type": "object",
    "properties": {
     "name": {
      "type": "string"
     },
     "legalName": {
      "type": "string"
     },
     "description": {
      "type": "string"
     },
     "email": {
      "type": "string",
      "format": "email"
     },
     "contactPage": {
      "type": "string",
      "format": "uri"
     },
     "address": {
      "type": "object",
      "properties": {
       "streetAddress": {
        "type": "string"
       },
       "postalCode": {
        "type": "string"
       },
       "addressLocality": {
        "type": "string"
       },
       "addressCountry": {
        "type": "string"
       }
      }
     },
     "languages": {
      "type": "array",
      "items": {
       "type": "string"
      }
     },
     "people": {
      "type": "array",
      "items": {
       "type": "object",
       "properties": {
        "name": {
         "type": "string"
        },
        "role": {
         "type": "string"
        },
        "email": {
         "type": "string"
        },
        "phone": {
         "type": "string"
        }
       }
      }
     },
     "howToRequest": {
      "type": "string"
     },
     "pricing": {
      "type": "string"
     }
    }
   },
   "Health": {
    "type": "object",
    "properties": {
     "status": {
      "type": "string"
     },
     "pages": {
      "type": "integer"
     },
     "updated": {
      "type": "string"
     }
    }
   }
  }
 }
}