{
  "openapi": "3.1.0",
  "info": {
    "title": "EsameNautica API",
    "version": "1.0.0",
    "summary": "API pubblica in sola lettura sulle guide per l'esame della patente nautica.",
    "description": "Espone i contenuti di esamenautica.app: 108 guide (carteggio, quesiti, iter d'esame, patentino D1), 51 città con la sede d'esame competente e le regole d'esame ufficiali. Nessuna autenticazione. Limite: 60 richieste ogni 60 secondi per IP, applicato per istanza edge (best-effort) e comunicato con gli header IETF RateLimit e RateLimit-Policy. Gli errori sono in application/problem+json (RFC 9457). Lo stesso contenuto è disponibile come server MCP su https://esamenautica.app/mcp.",
    "contact": {
      "name": "Simone Ruggiero",
      "email": "simone.ruggiero97@gmail.com",
      "url": "https://esamenautica.app/contact/"
    }
  },
  "externalDocs": {
    "description": "Documentazione",
    "url": "https://esamenautica.app/docs/"
  },
  "servers": [
    {
      "url": "https://esamenautica.app"
    }
  ],
  "security": [],
  "tags": [
    {
      "name": "Sito"
    },
    {
      "name": "Guide"
    },
    {
      "name": "Città"
    }
  ],
  "paths": {
    "/api/v1": {
      "get": {
        "operationId": "getApiIndex",
        "tags": [
          "Sito"
        ],
        "summary": "Indice dell'API",
        "description": "Elenco degli endpoint disponibili e link a documentazione, OpenAPI e MCP.",
        "responses": {
          "200": {
            "description": "Indice",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiIndex"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/api/v1/info": {
      "get": {
        "operationId": "getSiteInfo",
        "tags": [
          "Sito"
        ],
        "summary": "Informazioni sul sito",
        "description": "Descrizione del sito, app iOS collegata, autore, contatti e numero di contenuti.",
        "responses": {
          "200": {
            "description": "Informazioni",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SiteInfo"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/api/v1/exam": {
      "get": {
        "operationId": "getExamRules",
        "tags": [
          "Sito"
        ],
        "summary": "Regole d'esame",
        "description": "Domande, tempi ed errori ammessi per ogni prova della patente nautica (DM 323/2021) e per il patentino D1.",
        "responses": {
          "200": {
            "description": "Regole d'esame",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExamRules"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/api/v1/guides": {
      "get": {
        "operationId": "listGuides",
        "tags": [
          "Guide"
        ],
        "summary": "Elenca o cerca le guide",
        "description": "Restituisce le guide pubblicate, filtrabili per argomento e ricerca testuale su titolo, descrizione e keyword.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Parole chiave (max 100 caratteri).",
            "schema": {
              "type": "string",
              "maxLength": 100
            }
          },
          {
            "name": "cluster",
            "in": "query",
            "required": false,
            "description": "Argomento.",
            "schema": {
              "type": "string",
              "enum": [
                "Carteggio",
                "Esame",
                "Quesiti",
                "Patentino D1"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Numero massimo di risultati.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Guide trovate",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GuideList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/api/v1/guides/{slug}": {
      "get": {
        "operationId": "getGuide",
        "tags": [
          "Guide"
        ],
        "summary": "Leggi una guida",
        "description": "Metadati e testo completo in markdown di una guida.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "Slug della guida, es. carta-5d-cos-e-come-si-usa.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9-]{1,120}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Guida",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Guide"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/api/v1/cities": {
      "get": {
        "operationId": "listCities",
        "tags": [
          "Città"
        ],
        "summary": "Elenca le città",
        "description": "Città con pagina dedicata: sede d'esame competente e specchio d'acqua per la pratica.",
        "parameters": [
          {
            "name": "region",
            "in": "query",
            "required": false,
            "description": "Filtra per regione, es. Campania.",
            "schema": {
              "type": "string",
              "maxLength": 60
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Città",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CityList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    },
    "/api/v1/cities/{slug}": {
      "get": {
        "operationId": "getCity",
        "tags": [
          "Città"
        ],
        "summary": "Dettaglio città",
        "description": "Sede d'esame e specchio d'acqua per la pratica in una città.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "Slug della città, es. napoli.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9-]{1,120}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Città",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/City"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Problem"
          },
          "404": {
            "$ref": "#/components/responses/Problem"
          },
          "429": {
            "$ref": "#/components/responses/Problem"
          }
        }
      }
    }
  },
  "components": {
    "headers": {
      "RateLimit": {
        "description": "Quota residua e secondi al reset (draft-ietf-httpapi-ratelimit-headers).",
        "schema": {
          "type": "string",
          "examples": [
            "\"default\";r=59;t=60"
          ]
        }
      },
      "RateLimitPolicy": {
        "description": "Politica: q richieste per finestra di w secondi.",
        "schema": {
          "type": "string",
          "examples": [
            "\"default\";q=60;w=60"
          ]
        }
      }
    },
    "responses": {
      "Problem": {
        "description": "Errore (RFC 9457)",
        "headers": {
          "RateLimit": {
            "$ref": "#/components/headers/RateLimit"
          },
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimitPolicy"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      }
    },
    "schemas": {
      "Problem": {
        "type": "object",
        "required": [
          "type",
          "title",
          "status",
          "detail",
          "code",
          "resolution"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "code": {
            "type": "string",
            "enum": [
              "invalid_query",
              "invalid_cluster",
              "invalid_limit",
              "invalid_slug",
              "invalid_region",
              "guide_not_found",
              "city_not_found",
              "not_found",
              "method_not_allowed",
              "rate_limited"
            ],
            "description": "Codice macchina, es. guide_not_found, rate_limited."
          },
          "resolution": {
            "type": "string",
            "description": "Cosa fare per risolvere."
          }
        }
      },
      "ApiIndex": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "documentation": {
            "type": "string",
            "format": "uri"
          },
          "openapi": {
            "type": "string",
            "format": "uri"
          },
          "mcp": {
            "type": "string",
            "format": "uri"
          },
          "endpoints": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "SiteInfo": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "apiVersion": {
            "type": "string"
          },
          "language": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "app": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "platform": {
                "type": "string"
              },
              "appStoreUrl": {
                "type": "string",
                "format": "uri"
              }
            }
          },
          "author": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "url": {
                "type": "string",
                "format": "uri"
              }
            }
          },
          "contact": {
            "type": "object",
            "properties": {
              "email": {
                "type": "string",
                "format": "email"
              },
              "page": {
                "type": "string",
                "format": "uri"
              }
            }
          },
          "location": {
            "type": "object",
            "properties": {
              "city": {
                "type": "string"
              },
              "country": {
                "type": "string"
              }
            }
          },
          "disclaimer": {
            "type": "string"
          },
          "links": {
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "format": "uri"
            }
          },
          "guides": {
            "type": "integer"
          },
          "cities": {
            "type": "integer"
          }
        }
      },
      "ExamTest": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "questions": {
            "type": "integer"
          },
          "minutes": {
            "type": "integer"
          },
          "maxErrors": {
            "type": "integer"
          },
          "appliesTo": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "note": {
            "type": "string"
          }
        }
      },
      "ExamRules": {
        "type": "object",
        "properties": {
          "source": {
            "type": "string"
          },
          "patenteNautica": {
            "type": "object",
            "properties": {
              "order": {
                "type": "string"
              },
              "tests": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ExamTest"
                }
              }
            }
          },
          "patentinoD1": {
            "type": "object",
            "properties": {
              "questions": {
                "type": "integer"
              },
              "format": {
                "type": "string"
              },
              "minutes": {
                "type": "integer"
              },
              "minCorrect": {
                "type": "integer"
              },
              "practicalExam": {
                "type": "boolean"
              },
              "mandatoryCourse": {
                "type": "string"
              },
              "minAge": {
                "type": "integer"
              },
              "maxPowerKw": {
                "type": "integer"
              },
              "maxDistanceMiles": {
                "type": "integer"
              },
              "daytimeOnly": {
                "type": "boolean"
              },
              "personalWatercraftMaxMiles": {
                "type": "integer"
              }
            }
          },
          "guide": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "GuideSummary": {
        "type": "object",
        "required": [
          "slug",
          "title",
          "url"
        ],
        "properties": {
          "slug": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "cluster": {
            "type": "string",
            "enum": [
              "Carteggio",
              "Esame",
              "Quesiti",
              "Patentino D1"
            ]
          },
          "keywords": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "updatedAt": {
            "type": "string",
            "format": "date"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "markdownUrl": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "Guide": {
        "allOf": [
          {
            "$ref": "#/components/schemas/GuideSummary"
          },
          {
            "type": "object",
            "properties": {
              "markdown": {
                "type": "string",
                "description": "Testo completo in markdown."
              }
            }
          }
        ]
      },
      "GuideList": {
        "type": "object",
        "properties": {
          "total": {
            "type": "integer"
          },
          "guides": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GuideSummary"
            }
          }
        }
      },
      "City": {
        "type": "object",
        "required": [
          "slug",
          "name"
        ],
        "properties": {
          "slug": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "region": {
            "type": "string"
          },
          "coastal": {
            "type": "boolean"
          },
          "examOffice": {
            "type": "string",
            "description": "Ufficio presso cui si sostiene l'esame."
          },
          "practiceWaters": {
            "type": "string"
          },
          "note": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "CityList": {
        "type": "object",
        "properties": {
          "total": {
            "type": "integer"
          },
          "cities": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/City"
            }
          }
        }
      }
    }
  }
}