{
  "openapi": "3.1.1",
  "info": {
    "title": "Ma Vie Numérique · API de recherche locale et agenda",
    "version": "1.1.0",
    "description": "Public read-only endpoints for Ma Vie Numérique (ma-vie-numerique): find local businesses, associations and places, or retrieve a commune calendar. No authentication or API key is required. The request hostname selects the commune. Results can be partial when a source is unavailable; an empty result is not proof that no establishment or event exists. Use /api/v1/ for version 1. Existing unversioned paths remain compatible aliases. All documented operations share an enforced quota of 120 requests per 60-second fixed window per client IP across hosts and aliases. Responses are private, no-store; underlying source data remains cached. Respect RateLimit, RateLimit-Policy and Retry-After. Breaking changes require a new major path; retirement is announced at least 90 days in advance with RFC 9745 Deprecation and RFC 8594 Sunset headers. No retirement is currently scheduled. Account, messaging, publication and payment APIs are private and excluded. RateLimit and RateLimit-Policy follow draft-ietf-httpapi-ratelimit-headers-11 (not a published RFC as of October 2026).",
    "contact": {
      "name": "Webdesign29",
      "email": "ben@webdesign29.net",
      "url": "https://ma-vie-numerique.fr/contact"
    }
  },
  "servers": [
    {
      "url": "https://brest.ma-vie-numerique.fr",
      "description": "Brest"
    },
    {
      "url": "https://ma-vie-numerique.fr",
      "description": "Canonical Brest portal"
    },
    {
      "url": "https://{commune}.ma-vie-numerique.fr",
      "description": "An existing commune portal; an unknown commune returns 404.",
      "variables": {
        "commune": {
          "default": "brest",
          "description": "Existing commune slug, for example quimper."
        }
      }
    }
  ],
  "security": [],
  "externalDocs": {
    "description": "Ma Vie Numérique developer and agent documentation",
    "url": "https://brest.ma-vie-numerique.fr/developers"
  },
  "paths": {
    "/api/search/suggest": {
      "get": {
        "operationId": "searchLocalDirectory",
        "summary": "Find local businesses, associations, places and events",
        "description": "Search the selected commune and return grouped public detail links. Use these to open and cite the source page. Query is trimmed for search; fewer than two characters gives an empty list. Avoid repeating identical queries within 30 seconds; source caching is internal and quota-bearing HTTP responses are not cached. This endpoint does not verify business quality, opening hours or availability.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Search text; omitted means empty. Longer input is truncated to its first 80 characters.",
            "schema": {
              "type": "string",
              "default": "",
              "maxLength": 80
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Grouped public search suggestions, possibly empty.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResult"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "Major version serving this operation.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "RateLimit-Policy": {
                "description": "Shared per-client-IP fixed-window quota; IETF draft-ietf-httpapi-ratelimit-headers-11 syntax.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";q=120;w=60"
              },
              "RateLimit": {
                "description": "Remaining requests (r) and seconds until reset (t). Omitted when quota storage is unavailable; never contains a client identifier.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";r=119;t=60"
              }
            }
          },
          "404": {
            "description": "Portal or event not found. Follow the hint to select an existing resource.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "Major version serving this operation.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "RateLimit-Policy": {
                "description": "Shared per-client-IP fixed-window quota; IETF draft-ietf-httpapi-ratelimit-headers-11 syntax.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";q=120;w=60"
              },
              "RateLimit": {
                "description": "Remaining requests (r) and seconds until reset (t). Omitted when quota storage is unavailable; never contains a client identifier.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";r=119;t=60"
              }
            }
          },
          "405": {
            "description": "Unsupported method. Use GET; Allow lists supported methods.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "Major version serving this operation.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "RateLimit-Policy": {
                "description": "Shared per-client-IP fixed-window quota; IETF draft-ietf-httpapi-ratelimit-headers-11 syntax.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";q=120;w=60"
              },
              "RateLimit": {
                "description": "Remaining requests (r) and seconds until reset (t). Omitted when quota storage is unavailable; never contains a client identifier.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";r=119;t=60"
              }
            }
          },
          "503": {
            "description": "Temporary failure. Respect Retry-After, normally 60 seconds.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "Major version serving this operation.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "RateLimit-Policy": {
                "description": "Shared per-client-IP fixed-window quota; IETF draft-ietf-httpapi-ratelimit-headers-11 syntax.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";q=120;w=60"
              },
              "RateLimit": {
                "description": "Remaining requests (r) and seconds until reset (t). Omitted when quota storage is unavailable; never contains a client identifier.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";r=119;t=60"
              },
              "Retry-After": {
                "description": "Minimum seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              }
            }
          },
          "429": {
            "description": "Quota exhausted. Wait the Retry-After delay in seconds; do not retry immediately.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "Major version serving this operation.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "RateLimit-Policy": {
                "description": "Shared per-client-IP fixed-window quota; IETF draft-ietf-httpapi-ratelimit-headers-11 syntax.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";q=120;w=60"
              },
              "RateLimit": {
                "description": "Remaining requests (r) and seconds until reset (t). Omitted when quota storage is unavailable; never contains a client identifier.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";r=119;t=60"
              },
              "Retry-After": {
                "description": "Minimum seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              }
            }
          }
        }
      }
    },
    "/api/agenda-ics": {
      "get": {
        "operationId": "getCommuneCalendar",
        "summary": "Read the local calendar or export one event",
        "description": "Returns RFC 5545 iCalendar text for the selected commune. No booking is made. Omit id for the upcoming feed (which may be partial); obtain event ids from agenda export links. Source data uses an internal cache; quota-bearing HTTP responses are not cached. Request application/json for typed event data; text/calendar or no explicit JSON preference retains the existing iCalendar export. Check event details with the organizer before recommending them.",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": false,
            "description": "Optional identifier from an agenda export link. Omitted or empty selects the full feed. Input is truncated to 64 characters.",
            "schema": {
              "type": "string",
              "maxLength": 64
            }
          },
          {
            "name": "dl",
            "in": "query",
            "required": false,
            "description": "Use 1 to request an attachment; omitted returns the inline feed. Single events are always attachments.",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "iCalendar text; Content-Disposition is attachment when requested.",
            "content": {
              "text/calendar": {
                "schema": {
                  "type": "string",
                  "description": "RFC 5545 calendar beginning BEGIN:VCALENDAR and ending END:VCALENDAR."
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalendarResult"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "Major version serving this operation.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "RateLimit-Policy": {
                "description": "Shared per-client-IP fixed-window quota; IETF draft-ietf-httpapi-ratelimit-headers-11 syntax.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";q=120;w=60"
              },
              "RateLimit": {
                "description": "Remaining requests (r) and seconds until reset (t). Omitted when quota storage is unavailable; never contains a client identifier.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";r=119;t=60"
              }
            }
          },
          "404": {
            "description": "Portal or event not found. Follow the hint to select an existing resource.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "Major version serving this operation.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "RateLimit-Policy": {
                "description": "Shared per-client-IP fixed-window quota; IETF draft-ietf-httpapi-ratelimit-headers-11 syntax.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";q=120;w=60"
              },
              "RateLimit": {
                "description": "Remaining requests (r) and seconds until reset (t). Omitted when quota storage is unavailable; never contains a client identifier.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";r=119;t=60"
              }
            }
          },
          "405": {
            "description": "Unsupported method. Use GET; Allow lists supported methods.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "Major version serving this operation.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "RateLimit-Policy": {
                "description": "Shared per-client-IP fixed-window quota; IETF draft-ietf-httpapi-ratelimit-headers-11 syntax.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";q=120;w=60"
              },
              "RateLimit": {
                "description": "Remaining requests (r) and seconds until reset (t). Omitted when quota storage is unavailable; never contains a client identifier.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";r=119;t=60"
              }
            }
          },
          "503": {
            "description": "Temporary failure. Respect Retry-After, normally 60 seconds.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "Major version serving this operation.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "RateLimit-Policy": {
                "description": "Shared per-client-IP fixed-window quota; IETF draft-ietf-httpapi-ratelimit-headers-11 syntax.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";q=120;w=60"
              },
              "RateLimit": {
                "description": "Remaining requests (r) and seconds until reset (t). Omitted when quota storage is unavailable; never contains a client identifier.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";r=119;t=60"
              },
              "Retry-After": {
                "description": "Minimum seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              }
            }
          },
          "429": {
            "description": "Quota exhausted. Wait the Retry-After delay in seconds; do not retry immediately.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "Major version serving this operation.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "RateLimit-Policy": {
                "description": "Shared per-client-IP fixed-window quota; IETF draft-ietf-httpapi-ratelimit-headers-11 syntax.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";q=120;w=60"
              },
              "RateLimit": {
                "description": "Remaining requests (r) and seconds until reset (t). Omitted when quota storage is unavailable; never contains a client identifier.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";r=119;t=60"
              },
              "Retry-After": {
                "description": "Minimum seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              }
            }
          },
          "406": {
            "description": "No acceptable representation. Request application/json or text/calendar.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "Major version serving this operation.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "RateLimit-Policy": {
                "description": "Shared per-client-IP fixed-window quota; IETF draft-ietf-httpapi-ratelimit-headers-11 syntax.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";q=120;w=60"
              },
              "RateLimit": {
                "description": "Remaining requests (r) and seconds until reset (t). Omitted when quota storage is unavailable; never contains a client identifier.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";r=119;t=60"
              }
            }
          }
        }
      }
    },
    "/api/v1/search": {
      "get": {
        "operationId": "searchLocalDirectoryV1",
        "summary": "Find local businesses, associations, places and events",
        "description": "Search the selected commune and return grouped public detail links. Use these to open and cite the source page. Query is trimmed for search; fewer than two characters gives an empty list. Avoid repeating identical queries within 30 seconds; source caching is internal and quota-bearing HTTP responses are not cached. This endpoint does not verify business quality, opening hours or availability.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Search text; omitted means empty. Longer input is truncated to its first 80 characters.",
            "schema": {
              "type": "string",
              "default": "",
              "maxLength": 80
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Grouped public search suggestions, possibly empty.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResult"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "Major version serving this operation.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "RateLimit-Policy": {
                "description": "Shared per-client-IP fixed-window quota; IETF draft-ietf-httpapi-ratelimit-headers-11 syntax.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";q=120;w=60"
              },
              "RateLimit": {
                "description": "Remaining requests (r) and seconds until reset (t). Omitted when quota storage is unavailable; never contains a client identifier.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";r=119;t=60"
              }
            }
          },
          "404": {
            "description": "Portal or event not found. Follow the hint to select an existing resource.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "Major version serving this operation.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "RateLimit-Policy": {
                "description": "Shared per-client-IP fixed-window quota; IETF draft-ietf-httpapi-ratelimit-headers-11 syntax.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";q=120;w=60"
              },
              "RateLimit": {
                "description": "Remaining requests (r) and seconds until reset (t). Omitted when quota storage is unavailable; never contains a client identifier.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";r=119;t=60"
              }
            }
          },
          "405": {
            "description": "Unsupported method. Use GET; Allow lists supported methods.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "Major version serving this operation.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "RateLimit-Policy": {
                "description": "Shared per-client-IP fixed-window quota; IETF draft-ietf-httpapi-ratelimit-headers-11 syntax.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";q=120;w=60"
              },
              "RateLimit": {
                "description": "Remaining requests (r) and seconds until reset (t). Omitted when quota storage is unavailable; never contains a client identifier.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";r=119;t=60"
              }
            }
          },
          "503": {
            "description": "Temporary failure. Respect Retry-After, normally 60 seconds.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "Major version serving this operation.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "RateLimit-Policy": {
                "description": "Shared per-client-IP fixed-window quota; IETF draft-ietf-httpapi-ratelimit-headers-11 syntax.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";q=120;w=60"
              },
              "RateLimit": {
                "description": "Remaining requests (r) and seconds until reset (t). Omitted when quota storage is unavailable; never contains a client identifier.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";r=119;t=60"
              },
              "Retry-After": {
                "description": "Minimum seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              }
            }
          },
          "429": {
            "description": "Quota exhausted. Wait the Retry-After delay in seconds; do not retry immediately.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "Major version serving this operation.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "RateLimit-Policy": {
                "description": "Shared per-client-IP fixed-window quota; IETF draft-ietf-httpapi-ratelimit-headers-11 syntax.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";q=120;w=60"
              },
              "RateLimit": {
                "description": "Remaining requests (r) and seconds until reset (t). Omitted when quota storage is unavailable; never contains a client identifier.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";r=119;t=60"
              },
              "Retry-After": {
                "description": "Minimum seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/agenda": {
      "get": {
        "operationId": "getCommuneAgendaV1",
        "summary": "Read the local calendar or export one event",
        "description": "Version 1 agenda: JSON by default for an absent Accept header or */*. Explicit text/calendar returns the existing iCalendar export. Returns RFC 5545 iCalendar text for the selected commune. No booking is made. Omit id for the upcoming feed (which may be partial); obtain event ids from agenda export links. Source data uses an internal cache; quota-bearing HTTP responses are not cached. Request application/json for typed event data; text/calendar or no explicit JSON preference retains the existing iCalendar export. Check event details with the organizer before recommending them.",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": false,
            "description": "Optional identifier from an agenda export link. Omitted or empty selects the full feed. Input is truncated to 64 characters.",
            "schema": {
              "type": "string",
              "maxLength": 64
            }
          },
          {
            "name": "dl",
            "in": "query",
            "required": false,
            "description": "Use 1 to request an attachment; omitted returns the inline feed. Single events are always attachments.",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "iCalendar text; Content-Disposition is attachment when requested.",
            "content": {
              "text/calendar": {
                "schema": {
                  "type": "string",
                  "description": "RFC 5545 calendar beginning BEGIN:VCALENDAR and ending END:VCALENDAR."
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalendarResult"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "Major version serving this operation.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "RateLimit-Policy": {
                "description": "Shared per-client-IP fixed-window quota; IETF draft-ietf-httpapi-ratelimit-headers-11 syntax.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";q=120;w=60"
              },
              "RateLimit": {
                "description": "Remaining requests (r) and seconds until reset (t). Omitted when quota storage is unavailable; never contains a client identifier.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";r=119;t=60"
              }
            }
          },
          "404": {
            "description": "Portal or event not found. Follow the hint to select an existing resource.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "Major version serving this operation.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "RateLimit-Policy": {
                "description": "Shared per-client-IP fixed-window quota; IETF draft-ietf-httpapi-ratelimit-headers-11 syntax.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";q=120;w=60"
              },
              "RateLimit": {
                "description": "Remaining requests (r) and seconds until reset (t). Omitted when quota storage is unavailable; never contains a client identifier.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";r=119;t=60"
              }
            }
          },
          "405": {
            "description": "Unsupported method. Use GET; Allow lists supported methods.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "Major version serving this operation.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "RateLimit-Policy": {
                "description": "Shared per-client-IP fixed-window quota; IETF draft-ietf-httpapi-ratelimit-headers-11 syntax.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";q=120;w=60"
              },
              "RateLimit": {
                "description": "Remaining requests (r) and seconds until reset (t). Omitted when quota storage is unavailable; never contains a client identifier.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";r=119;t=60"
              }
            }
          },
          "503": {
            "description": "Temporary failure. Respect Retry-After, normally 60 seconds.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "Major version serving this operation.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "RateLimit-Policy": {
                "description": "Shared per-client-IP fixed-window quota; IETF draft-ietf-httpapi-ratelimit-headers-11 syntax.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";q=120;w=60"
              },
              "RateLimit": {
                "description": "Remaining requests (r) and seconds until reset (t). Omitted when quota storage is unavailable; never contains a client identifier.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";r=119;t=60"
              },
              "Retry-After": {
                "description": "Minimum seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              }
            }
          },
          "429": {
            "description": "Quota exhausted. Wait the Retry-After delay in seconds; do not retry immediately.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "Major version serving this operation.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "RateLimit-Policy": {
                "description": "Shared per-client-IP fixed-window quota; IETF draft-ietf-httpapi-ratelimit-headers-11 syntax.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";q=120;w=60"
              },
              "RateLimit": {
                "description": "Remaining requests (r) and seconds until reset (t). Omitted when quota storage is unavailable; never contains a client identifier.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";r=119;t=60"
              },
              "Retry-After": {
                "description": "Minimum seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              }
            }
          },
          "406": {
            "description": "No acceptable representation. Request application/json or text/calendar.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "headers": {
              "API-Version": {
                "description": "Major version serving this operation.",
                "schema": {
                  "type": "string",
                  "const": "1"
                }
              },
              "RateLimit-Policy": {
                "description": "Shared per-client-IP fixed-window quota; IETF draft-ietf-httpapi-ratelimit-headers-11 syntax.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";q=120;w=60"
              },
              "RateLimit": {
                "description": "Remaining requests (r) and seconds until reset (t). Omitted when quota storage is unavailable; never contains a client identifier.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api\";r=119;t=60"
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "RFC 9457 Problem Details with a stable code and actionable resolution hint.",
        "required": [
          "type",
          "title",
          "status",
          "detail",
          "code",
          "hint"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "const": "about:blank"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer",
            "minimum": 400,
            "maximum": 599
          },
          "detail": {
            "type": "string"
          },
          "code": {
            "type": "string",
            "enum": [
              "PORTAL_NOT_FOUND",
              "EVENT_NOT_FOUND",
              "API_NOT_FOUND",
              "METHOD_NOT_ALLOWED",
              "SERVICE_UNAVAILABLE",
              "RATE_LIMITED",
              "NOT_ACCEPTABLE"
            ]
          },
          "hint": {
            "type": "string"
          }
        }
      },
      "Suggestion": {
        "type": "object",
        "required": [
          "title",
          "sub",
          "href"
        ],
        "properties": {
          "title": {
            "type": "string",
            "description": "Display name."
          },
          "sub": {
            "type": "string",
            "description": "Locality or activity summary; may be empty."
          },
          "href": {
            "type": "string",
            "description": "Relative path of the public detail page. Resolve against the server origin."
          }
        }
      },
      "SuggestionGroup": {
        "type": "object",
        "required": [
          "type",
          "label",
          "items"
        ],
        "properties": {
          "type": {
            "type": "string",
            "description": "Category key, such as entreprises, associations, lieux, evenements or actualites."
          },
          "label": {
            "type": "string"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Suggestion"
            }
          }
        }
      },
      "SearchResult": {
        "type": "object",
        "required": [
          "q",
          "groups"
        ],
        "properties": {
          "q": {
            "type": "string",
            "description": "Query as received, truncated to 80 characters."
          },
          "groups": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SuggestionGroup"
            },
            "description": "Up to five suggestions per category (three for indexed news). Empty or one-character queries return no groups."
          }
        }
      },
      "CalendarEvent": {
        "type": "object",
        "required": [
          "id",
          "title",
          "startDate",
          "endDate",
          "venue",
          "address",
          "city"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Event identifier from the source."
          },
          "title": {
            "type": "string",
            "description": "Published event title."
          },
          "startDate": {
            "type": "string",
            "description": "Source date or date-time string; may be empty if missing."
          },
          "endDate": {
            "type": "string",
            "description": "Source date or date-time string, or empty if no end is published."
          },
          "venue": {
            "type": "string",
            "description": "Published venue name, or empty."
          },
          "address": {
            "type": "string",
            "description": "Published venue address, or empty."
          },
          "city": {
            "type": "string",
            "description": "Published city name, or empty if unavailable."
          }
        }
      },
      "CalendarResult": {
        "type": "object",
        "required": [
          "city",
          "events"
        ],
        "properties": {
          "city": {
            "type": "string",
            "description": "Commune name, or empty for a single event lacking a city."
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CalendarEvent"
            },
            "description": "Public agenda events; may be partial if a source is unavailable."
          }
        }
      }
    }
  }
}
