{
  "openapi": "3.1.0",
  "info": {
    "title": "SYMONE Route Search API",
    "description": "Public API for searching motorcycle transport routes in France. No authentication required. All endpoints return JSON errors on failure.\n\n**Rate limits**: No hard rate limits enforced. Response headers: `X-RateLimit-Limit: 0` (unlimited), `X-RateLimit-Remaining: 0`, `X-RateLimit-Reset: 0`. Recommended: max 60 requests/minute per client.\n\n**Retry guidance**: On 5xx errors, retry with exponential backoff starting at 1s, max 3 attempts. On 429 (if returned), wait for `Retry-After` header value.\n\n**Status**: No dedicated status page. Contact contact@symone.fr for outages.\n\n**Error format**: All errors return `{\"error\": \"message\"}` JSON with appropriate HTTP status code.",
    "version": "1.1.0",
    "contact": {
      "email": "contact@symone.fr",
      "url": "https://symone.fr"
    },
    "license": {
      "name": "Proprietary"
    },
    "x-rate-limit": "No rate limit enforced on public endpoints.",
    "x-versioning": "Current version: v1. Breaking changes will be introduced under a new version prefix (e.g. /v2/). Deprecated endpoints will return a Deprecation header before removal."
  },
  "servers": [
    {
      "url": "https://symone.fr/api-proxy/v1",
      "description": "Production server (v1)"
    },
    {
      "url": "https://symone.fr/api-proxy",
      "description": "Production server (unversioned, legacy)"
    }
  ],
  "paths": {
    "/route/ai/search": {
      "get": {
        "operationId": "searchRoutes",
        "summary": "Search motorcycle transport routes (AI-optimized)",
        "description": "Returns filtered, compact route results. Filter by departure city, arrival city, and date range. Main route: Paris↔Marseille from 299€. Book at https://symone.fr/moto. No rate limit.",
        "parameters": [
          {
            "name": "depart",
            "in": "query",
            "required": false,
            "description": "Departure city name (e.g. Paris)",
            "schema": { "type": "string", "example": "Paris" }
          },
          {
            "name": "arrival",
            "in": "query",
            "required": false,
            "description": "Arrival city name (e.g. Marseille)",
            "schema": { "type": "string", "example": "Marseille" }
          },
          {
            "name": "date_from",
            "in": "query",
            "required": false,
            "description": "Start date filter, format YYYY-MM-DD",
            "schema": { "type": "string", "example": "2025-07-01" }
          },
          {
            "name": "date_to",
            "in": "query",
            "required": false,
            "description": "End date filter, format YYYY-MM-DD",
            "schema": { "type": "string", "example": "2025-07-31" }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number (1-based)",
            "schema": { "type": "integer", "default": 1, "minimum": 1 }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Results per page (max 100)",
            "schema": { "type": "integer", "default": 50, "minimum": 1, "maximum": 100 }
          }
        ],
        "responses": {
          "200": {
            "description": "Filtered list of routes",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": { "type": "integer" },
                "description": "Request limit (0 = unlimited)"
              },
              "X-RateLimit-Remaining": {
                "schema": { "type": "integer" },
                "description": "Remaining requests (0 = unlimited)"
              },
              "X-RateLimit-Reset": {
                "schema": { "type": "integer" },
                "description": "Rate limit reset timestamp (0 = no limit)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "routes": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/RouteAI" }
                    },
                    "total": { "type": "integer" },
                    "page": { "type": "integer" },
                    "limit": { "type": "integer" },
                    "total_pages": { "type": "integer" },
                    "has_next": { "type": "boolean" },
                    "book_url": { "type": "string" }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/route/ai/jobs": {
      "post": {
        "operationId": "createSearchJob",
        "summary": "Create async search job",
        "description": "Submit a route search as an async job. Returns immediately with a job_id. Poll GET /route/ai/jobs/{jobId} for results.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "depart": { "type": "string", "example": "Paris" },
                  "arrival": { "type": "string", "example": "Marseille" },
                  "date_from": { "type": "string", "example": "2025-07-01" },
                  "date_to": { "type": "string", "example": "2025-07-31" }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Job accepted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "job_id": { "type": "string" },
                    "status": { "type": "string", "enum": ["pending"] },
                    "poll_url": { "type": "string" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/route/ai/jobs/{jobId}": {
      "get": {
        "operationId": "getSearchJob",
        "summary": "Poll async search job status",
        "description": "Poll for the result of a previously submitted search job.",
        "parameters": [
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Job status and result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "job_id": { "type": "string" },
                    "status": { "type": "string", "enum": ["pending", "completed", "failed"] },
                    "result": { "type": "object" }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Job not found",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/route/ai/batch": {
      "post": {
        "operationId": "batchSearchRoutes",
        "summary": "Batch search motorcycle transport routes",
        "description": "Execute multiple route searches in a single request. Maximum 10 queries per batch. Supports Idempotency-Key header to safely retry requests.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique key to ensure idempotent requests. Same key returns cached result for 5 minutes.",
            "schema": { "type": "string", "example": "my-unique-key-123" }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["queries"],
                "properties": {
                  "queries": {
                    "type": "array",
                    "maxItems": 10,
                    "items": {
                      "type": "object",
                      "properties": {
                        "depart": { "type": "string", "example": "Paris" },
                        "arrival": { "type": "string", "example": "Marseille" },
                        "date_from": { "type": "string", "example": "2025-07-01" },
                        "date_to": { "type": "string", "example": "2025-07-31" }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Batch search results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "index": { "type": "integer" },
                          "query": { "type": "object" },
                          "routes": { "type": "array", "items": { "$ref": "#/components/schemas/RouteAI" } },
                          "total": { "type": "integer" }
                        }
                      }
                    },
                    "book_url": { "type": "string" }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/route/get/results": {
      "get": {
        "operationId": "getAllRoutes",
        "summary": "Get all upcoming motorcycle transport routes (full data)",
        "description": "Returns all upcoming trips — full dataset, large response. Prefer /route/ai/search for AI use cases. No rate limit.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "List of available routes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": { "$ref": "#/components/schemas/Route" }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/route/cities-near": {
      "get": {
        "operationId": "getCitiesNear",
        "summary": "Find SYMONE-served cities near a location",
        "description": "Returns cities served by SYMONE near the given location name. No rate limit.",
        "parameters": [
          {
            "name": "location",
            "in": "query",
            "required": true,
            "description": "City name (e.g. Paris, Lyon, Marseille)",
            "schema": { "type": "string", "example": "Paris" }
          }
        ],
        "responses": {
          "200": {
            "description": "List of nearby served cities",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "cities": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": { "type": "string" },
                          "distance_km": { "type": "number" }
                        }
                      }
                    },
                    "source": { "type": "string" }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing required parameter",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable error message"
          }
        }
      },
      "RouteAI": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "date": { "type": "string", "description": "Departure date (YYYY-MM-DD)" },
          "depart": { "type": "string", "description": "Departure city" },
          "arrival": { "type": "string", "description": "Arrival city" },
          "layovers": { "type": "array", "items": { "type": "string" } },
          "price_min": { "type": "number", "description": "Minimum price in €" },
          "price_max": { "type": "number", "description": "Maximum price in €" },
          "slots": { "type": "array", "items": { "type": "integer" }, "description": "Available motorcycle slots per leg" },
          "times": { "type": "array", "items": { "type": "string" }, "description": "Departure times per stop" }
        }
      },
      "Route": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "description": "Unique route identifier" },
          "depart": { "type": "string", "description": "Departure city name" },
          "arrival": { "type": "string", "description": "Arrival city name" },
          "date": { "type": "string", "description": "Departure date" },
          "prix": { "type": "number", "description": "Price in euros" },
          "places_disponibles": { "type": "integer", "description": "Number of available slots" }
        }
      }
    }
  }
}
