{
  "openapi": "3.1.0",
  "info": {
    "title": "GeoArm Taxi booking API",
    "version": "1.0.0",
    "summary": "Prices, timetable and booking for daily rides between Tbilisi (Georgia) and Yerevan (Armenia).",
    "description": "Open API for people and for AI agents acting for a person. No key is needed. A booking made here is a request: a GeoArm Taxi dispatcher confirms it with the passenger by phone or Telegram. Payment is in cash to the driver. Read GET /api/config first: it lists the valid values for a booking. Human-readable guide: https://geoarmtaxi.com/agents/",
    "contact": {
      "name": "GeoArm Taxi",
      "url": "https://geoarmtaxi.com/agents/",
      "email": "info@geoarmtaxi.com"
    }
  },
  "servers": [
    {
      "url": "https://geoarmtaxi.com"
    }
  ],
  "paths": {
    "/api/config": {
      "get": {
        "operationId": "getConfig",
        "summary": "Current prices, seats, timetable and departure points",
        "description": "Everything a booking needs: currencies, car types (seats, price per passenger, optional whole-vehicle price), departure points per direction (id, names in 7 languages, coordinates) and departure times per direction. Values change; read them before each booking.",
        "responses": {
          "200": {
            "description": "The live configuration",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Config"
                }
              }
            }
          }
        }
      }
    },
    "/api/bookings": {
      "post": {
        "operationId": "createBooking",
        "summary": "Request a ride",
        "description": "Creates a booking request. It is NOT confirmed until a dispatcher contacts the passenger. Use the passenger's real name and phone number, and book only when a person asked you to. One booking per trip. Do not send the field `website` (anti-spam).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BookingRequest"
              },
              "example": {
                "dir": "tbs-evn",
                "point": "avl",
                "date": "2026-11-05",
                "time": "11:00",
                "car": "sprinter",
                "pax": 2,
                "name": "Anna Smith",
                "phone": "+44 7700 900123",
                "currency": "GEL",
                "lang": "en",
                "notes": "Two suitcases",
                "agent": "ChatGPT"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Received (not confirmed yet)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BookingAccepted"
                }
              }
            }
          },
          "400": {
            "description": "The body is not valid JSON",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field is missing or not valid; `field` names it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests from this address — try again later; or (`field: \"phone\"`) this phone already has 3 bookings in 24 hours — the passenger should call or write to us instead",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "The request body is larger than 8 KB",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Temporarily unavailable; try again shortly",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/bookings/status": {
      "get": {
        "operationId": "getBookingStatus",
        "summary": "Has the booking been confirmed?",
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The `status_token` returned when the booking was created."
          }
        ],
        "responses": {
          "200": {
            "description": "The booking's state (no personal data)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BookingStatus"
                }
              }
            }
          },
          "404": {
            "description": "No booking for this token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many status requests; try again later",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Price": {
        "type": "object",
        "description": "Amount per currency code; a currency that is not offered is absent.",
        "additionalProperties": {
          "type": "number"
        },
        "example": {
          "GEL": 60,
          "USD": 25,
          "AMD": 8500
        }
      },
      "Config": {
        "type": "object",
        "properties": {
          "currencies": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "GEL",
                "USD",
                "AMD"
              ]
            }
          },
          "types": {
            "type": "object",
            "description": "Car types by id: `sprinter` (minibus), `minivan`, `sedan`.",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "max": {
                  "type": "integer",
                  "description": "Seats"
                },
                "price": {
                  "$ref": "#/components/schemas/Price",
                  "description": "Per passenger, one way"
                },
                "whole": {
                  "$ref": "#/components/schemas/Price",
                  "description": "Whole vehicle, one way (optional)"
                }
              }
            }
          },
          "points": {
            "type": "object",
            "description": "Departure points by direction (`tbs-evn`, `evn-tbs`).",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "name": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    }
                  },
                  "lat": {
                    "type": "number"
                  },
                  "lng": {
                    "type": "number"
                  },
                  "price": {
                    "anyOf": [
                      {
                        "$ref": "#/components/schemas/Price"
                      },
                      {
                        "type": "null"
                      }
                    ],
                    "description": "The point's own price per passenger, if it has one"
                  }
                }
              }
            }
          },
          "addressPickup": {
            "type": "boolean",
            "description": "Whether pick-up from an address (`point: \"@addr\"`) is offered"
          },
          "schedule": {
            "type": "object",
            "description": "Departure times (HH:MM, Tbilisi/Yerevan time) by direction.",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          }
        }
      },
      "BookingRequest": {
        "type": "object",
        "required": [
          "dir",
          "point",
          "date",
          "time",
          "car",
          "pax",
          "name",
          "phone",
          "currency"
        ],
        "properties": {
          "dir": {
            "type": "string",
            "enum": [
              "tbs-evn",
              "evn-tbs"
            ],
            "description": "`tbs-evn` = Tbilisi → Yerevan, `evn-tbs` = Yerevan → Tbilisi"
          },
          "point": {
            "type": "string",
            "description": "A departure point id from `points[dir]` in /api/config, or `@addr` for pick-up from an address (extra fee, confirmed by the dispatcher)"
          },
          "address": {
            "type": "string",
            "maxLength": 200,
            "description": "Required with `point: \"@addr\"` unless `lat`/`lng` are given"
          },
          "lat": {
            "type": [
              "number",
              "null"
            ]
          },
          "lng": {
            "type": [
              "number",
              "null"
            ]
          },
          "date": {
            "type": "string",
            "format": "date",
            "description": "YYYY-MM-DD, today or later (Tbilisi date), at most a year ahead"
          },
          "time": {
            "type": "string",
            "description": "One of `schedule[dir]` from /api/config, or `other` for a time outside the timetable (whole vehicle; the dispatcher sets the price)"
          },
          "car": {
            "type": "string",
            "enum": [
              "sprinter",
              "minivan",
              "sedan"
            ],
            "description": "`sprinter` = a seat on the scheduled minibus (the default choice)"
          },
          "pax": {
            "type": "integer",
            "minimum": 1,
            "description": "Passengers; at most `types[car].max`"
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80,
            "description": "The passenger's name"
          },
          "phone": {
            "type": "string",
            "description": "The passenger's phone in international format, e.g. +995 5xx xx xx xx"
          },
          "currency": {
            "type": "string",
            "enum": [
              "GEL",
              "USD",
              "AMD"
            ],
            "description": "One of `currencies` from /api/config; the price is taken from the server"
          },
          "lang": {
            "type": "string",
            "enum": [
              "en",
              "ka",
              "ru",
              "hy",
              "tr",
              "ar",
              "fa"
            ],
            "default": "en",
            "description": "The language we should use with the passenger (an unknown value is treated as `en`)"
          },
          "notes": {
            "type": "string",
            "maxLength": 1000,
            "description": "Flight number, luggage, a child seat request and the like (longer text is cut to 1000 characters)"
          },
          "agent": {
            "type": "string",
            "maxLength": 80,
            "description": "If an AI agent makes the booking: its name (e.g. `ChatGPT`, `Claude`). Shown to the dispatcher."
          }
        }
      },
      "BookingAccepted": {
        "type": "object",
        "properties": {
          "number": {
            "type": "integer",
            "description": "The booking number"
          },
          "status": {
            "type": "string",
            "enum": [
              "received"
            ]
          },
          "confirmed": {
            "type": "boolean",
            "description": "Always false here: a dispatcher confirms later"
          },
          "message": {
            "type": "string",
            "description": "A sentence for the passenger, in `lang`"
          },
          "booking": {
            "type": "object",
            "description": "What was booked, as the server understood it",
            "properties": {
              "dir": {
                "type": "string"
              },
              "date": {
                "type": "string"
              },
              "time": {
                "type": "string"
              },
              "point": {
                "type": "string",
                "description": "The departure point's name in `lang`"
              },
              "car": {
                "type": "string"
              },
              "pax": {
                "type": "integer"
              },
              "total": {
                "type": "number"
              },
              "currency": {
                "type": "string"
              },
              "price_confirmed_by_dispatcher": {
                "type": "boolean",
                "description": "True when the final price is set by the dispatcher (address pick-up or a time outside the timetable)"
              }
            }
          },
          "status_token": {
            "type": "string"
          },
          "status_url": {
            "type": "string",
            "format": "uri"
          },
          "telegram": {
            "type": "string",
            "format": "uri",
            "description": "A link the passenger can open to get the confirmation in Telegram"
          }
        }
      },
      "BookingStatus": {
        "type": "object",
        "properties": {
          "number": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "enum": [
              "received",
              "confirmed",
              "completed",
              "cancelled"
            ]
          },
          "confirmed": {
            "type": "boolean"
          },
          "dir": {
            "type": "string"
          },
          "date": {
            "type": "string"
          },
          "time": {
            "type": "string"
          },
          "pax": {
            "type": "integer"
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "What is wrong, in the request's language"
          },
          "field": {
            "type": "string",
            "description": "The field to correct, when one is at fault"
          }
        }
      }
    }
  }
}