{
  "openapi": "3.1.0",
  "info": {
    "title": "On The Way (בדרך) Public API",
    "summary": "Read-only access to the places catalogue behind בדרך / On The Way.",
    "description": "On The Way (בדרך) is a free Hebrew map of places worth stopping at in Israel — food, springs, parks, viewpoints, beaches, lodging, attractions and workshops — searchable by current location, by an A→B driving route, and by filters such as kosher, vegan, open now and wheelchair access.\n\n## Authentication\n\nNone. No API key, no registration. Please keep to roughly 60 requests per minute and credit\nבדרך (https://on-the-way-two.vercel.app) when you surface the data.\n\n## Versioning and deprecation\n\nThis is version 1 of the API. Every response carries `X-API-Version: 1`,\nand a request may pin it by sending the same header. A breaking change to a documented\nresponse shape ships as a new version, never in place.\n\nBefore an endpoint or a version is withdrawn, its responses carry `Deprecation: true` and a\n`Sunset` header (RFC 8594) with the retirement date, at least 90 days ahead. An unknown or\nunsupported `X-API-Version` is ignored rather than refused, so pinning can never break a client.\n\n## Errors\n\nEvery error is `application/problem+json` (RFC 9457) with a stable `code`, a human-readable\n`detail` and, where there is something to do about it, a `hint`. No endpoint answers an error\nwith an HTML page.\n\n## Rate limits\n\n`GET /api/health` and `GET /api/suggestions` allow 60 requests per minute per IP and return\n`RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` (in seconds), plus `Retry-After`\non a 429. `GET /api/places` is unmetered: it is prerendered and served from the CDN, so no\nfunction runs that could count a request. The write endpoints (session-gated, not documented\nhere) carry the same headers.\n\n## Markdown\n\nEvery public page also has a Markdown representation: send `Accept: text/markdown` to the same\nURL. Those responses carry `Vary: Accept`. See /llms.txt for the agent guide and /sitemap.xml\nfor every page.",
    "version": "1.0.0",
    "contact": {
      "name": "בדרך (On The Way)",
      "email": "outravel456@gmail.com",
      "url": "https://on-the-way-two.vercel.app/contact"
    },
    "termsOfService": "https://on-the-way-two.vercel.app/terms"
  },
  "servers": [
    {
      "url": "https://on-the-way-two.vercel.app",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "תיעוד למפתחים ולסוכני AI",
    "url": "https://on-the-way-two.vercel.app/docs"
  },
  "tags": [
    {
      "name": "places",
      "description": "The places catalogue"
    },
    {
      "name": "meta",
      "description": "Service status and community signals"
    }
  ],
  "paths": {
    "/api/places": {
      "get": {
        "tags": [
          "places"
        ],
        "operationId": "listActivePlaces",
        "summary": "Every active place in the catalogue",
        "description": "The full list the map is built from. Cached without a TTL and invalidated on write, so the response is stable between edits and safe to cache client-side. Being prerendered and CDN-served, this endpoint is unmetered, and a non-GET method gets a bare 405 rather than the JSON error body the other endpoints return.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "All active places",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlaceList"
                }
              }
            }
          },
          "405": {
            "description": "Wrong method. Empty body — this route is served from the CDN."
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "default": {
            "$ref": "#/components/responses/UnexpectedError"
          }
        }
      }
    },
    "/api/health": {
      "get": {
        "tags": [
          "meta"
        ],
        "operationId": "getHealth",
        "summary": "Service and database health",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Healthy",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthStatus"
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "default": {
            "$ref": "#/components/responses/UnexpectedError"
          }
        }
      }
    },
    "/api/suggestions": {
      "get": {
        "tags": [
          "meta"
        ],
        "operationId": "listFeatureSuggestions",
        "summary": "Community feature suggestions and their vote counts",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Suggestions, most-voted first",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuggestionList"
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "default": {
            "$ref": "#/components/responses/UnexpectedError"
          }
        }
      }
    },
    "/place/{slug}": {
      "get": {
        "tags": [
          "places"
        ],
        "operationId": "getPlacePage",
        "summary": "A single place, as HTML or as Markdown",
        "description": "Send `Accept: text/markdown` to get the Markdown representation of the place page instead of HTML. The response varies on Accept.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The place slug, or its UUID (a UUID redirects to the slug).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Accept",
            "in": "header",
            "required": false,
            "description": "Use `text/markdown` for the agent-friendly representation.",
            "schema": {
              "type": "string",
              "examples": [
                "text/markdown",
                "text/html"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The place page",
            "headers": {
              "Vary": {
                "schema": {
                  "type": "string"
                },
                "description": "Includes `Accept`."
              }
            },
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              },
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "No such place. The Markdown body lists where to look instead.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/UnexpectedError"
          }
        }
      }
    },
    "/api/{unknownPath}": {
      "get": {
        "tags": [
          "meta"
        ],
        "operationId": "unknownEndpoint",
        "summary": "Any /api path that does not exist",
        "description": "Documented so a client that guessed a URL knows what it gets: a 404 problem+json naming the public endpoints, never an HTML page. Every method behaves the same way.",
        "parameters": [
          {
            "name": "unknownPath",
            "in": "path",
            "required": true,
            "description": "Any path segment that matches no endpoint.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "default": {
            "$ref": "#/components/responses/UnexpectedError"
          }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "ApiVersion": {
        "name": "X-API-Version",
        "in": "header",
        "required": false,
        "description": "Pin the API version. Currently `1`. An unsupported value is ignored, never refused.",
        "schema": {
          "type": "string",
          "default": "1"
        }
      }
    },
    "headers": {
      "ApiVersion": {
        "description": "The API version that served this response.",
        "schema": {
          "type": "string"
        }
      },
      "RateLimitLimit": {
        "description": "Requests allowed in the current window.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitRemaining": {
        "description": "Requests left in the current window.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitReset": {
        "description": "Seconds until the window resets.",
        "schema": {
          "type": "integer"
        }
      },
      "RetryAfter": {
        "description": "Seconds to wait before retrying.",
        "schema": {
          "type": "integer"
        }
      }
    },
    "responses": {
      "NotFound": {
        "description": "No such endpoint or resource.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "MethodNotAllowed": {
        "description": "The endpoint does not accept this method. `Allow` lists the ones it does.",
        "headers": {
          "Allow": {
            "description": "Accepted methods.",
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Too many requests.",
        "headers": {
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          },
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "ServerError": {
        "description": "Something failed on our side. Transient unless it repeats.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "UnexpectedError": {
        "description": "Any other failure. Always this same shape — branch on `code`.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      }
    },
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem detail. Branch on `code`, show `detail`, act on `hint`.",
        "required": [
          "type",
          "title",
          "status",
          "detail",
          "code"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "description": "Link to the documentation for this code."
          },
          "title": {
            "type": "string",
            "description": "Short, stable, English."
          },
          "status": {
            "type": "integer",
            "description": "Repeats the HTTP status code."
          },
          "detail": {
            "type": "string",
            "description": "What went wrong, in Hebrew — the same text the UI shows."
          },
          "code": {
            "type": "string",
            "description": "Stable machine-readable code. Branch on this, never on the prose.",
            "enum": [
              "not_found",
              "method_not_allowed",
              "rate_limited",
              "invalid_request",
              "unauthorized",
              "forbidden",
              "server_error"
            ]
          },
          "hint": {
            "type": "string",
            "description": "What the caller should do about it."
          },
          "documentation": {
            "type": "string",
            "format": "uri"
          },
          "error": {
            "type": "string",
            "description": "Deprecated alias of `detail`, kept for existing clients."
          }
        }
      },
      "PlaceList": {
        "type": "array",
        "description": "Every active place, in no guaranteed order.",
        "items": {
          "$ref": "#/components/schemas/Place"
        }
      },
      "HealthStatus": {
        "type": "object",
        "required": [
          "status"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok"
            ]
          },
          "message": {
            "type": "string"
          }
        }
      },
      "SuggestionList": {
        "type": "object",
        "required": [
          "suggestions"
        ],
        "properties": {
          "suggestions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FeatureSuggestion"
            }
          }
        }
      },
      "Place": {
        "type": "object",
        "description": "A place in the catalogue. Hebrew is the primary language of the data.",
        "required": [
          "id",
          "name_he",
          "content_type"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "slug": {
            "type": [
              "string",
              "null"
            ],
            "description": "URL segment used by /place/{slug}."
          },
          "name_he": {
            "type": "string"
          },
          "name_en": {
            "type": [
              "string",
              "null"
            ]
          },
          "content_type": {
            "type": "string",
            "enum": [
              "food",
              "spring",
              "park",
              "viewpoint",
              "beach",
              "waterfall",
              "lake",
              "accommodation",
              "attraction",
              "workshop"
            ]
          },
          "type": {
            "type": [
              "string",
              "null"
            ],
            "description": "Sub-type, e.g. cafe, restaurant, food_truck."
          },
          "city": {
            "type": [
              "string",
              "null"
            ]
          },
          "address": {
            "type": [
              "string",
              "null"
            ]
          },
          "region_main": {
            "type": [
              "string",
              "null"
            ]
          },
          "lat": {
            "type": [
              "number",
              "null"
            ]
          },
          "lng": {
            "type": [
              "number",
              "null"
            ]
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "website": {
            "type": [
              "string",
              "null"
            ]
          },
          "cover_image_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "rating_avg": {
            "type": [
              "number",
              "null"
            ],
            "description": "Synced from Google every few months, not live."
          },
          "rating_count": {
            "type": [
              "integer",
              "null"
            ]
          },
          "price_range": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 4
          },
          "is_kosher": {
            "type": "boolean"
          },
          "kosher_status": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "none",
              "regular",
              "mehadrin",
              "badatz",
              "not_kosher",
              "trust_kosher",
              null
            ]
          },
          "kosher_authority": {
            "type": [
              "string",
              "null"
            ]
          },
          "kosher_for_passover": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "vegan_options": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "vegetarian_options": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "gluten_free_options": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "weekly_hours": {
            "type": [
              "object",
              "null"
            ],
            "description": "Keyed by sun…sat, each { open: \"HH:MM\", close: \"HH:MM\", closed: boolean }.",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "open": {
                  "type": "string"
                },
                "close": {
                  "type": "string"
                },
                "closed": {
                  "type": "boolean"
                }
              }
            }
          },
          "features": {
            "type": [
              "object",
              "null"
            ],
            "description": "Boolean feature flags (parking, wheelchair access, outdoor seating…)."
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "FeatureSuggestion": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": [
              "string",
              "null"
            ]
          },
          "vote_count": {
            "type": [
              "integer",
              "null"
            ]
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      }
    }
  }
}