{
  "components": {
    "schemas": {
      "AgentBatchSubmission": {
        "description": "A batch of bookings submitted on one traveller's behalf by an agent.\n\nOne email address per batch by design. The confirmation is a single message\nto one person listing everything an assistant asked us to watch for them,\nwhich is both a better inbox experience than seven separate emails and the\nthing that makes one confirmation link sufficient.",
        "properties": {
          "agent": {
            "anyOf": [
              {
                "maxLength": 80,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Agent"
          },
          "bookings": {
            "items": {
              "$ref": "#/components/schemas/AgentBookingItem"
            },
            "maxItems": 10,
            "minItems": 1,
            "title": "Bookings",
            "type": "array"
          },
          "email": {
            "maxLength": 254,
            "minLength": 3,
            "title": "Email",
            "type": "string"
          }
        },
        "required": [
          "email",
          "bookings"
        ],
        "title": "AgentBatchSubmission",
        "type": "object"
      },
      "AgentBookingItem": {
        "description": "One booking inside an agent batch submission.\n\nDeliberately stricter than `ManualBookingSubmission`, because the callers\nare different. A browser form is forgiving by necessity: a human who mistypes\nshould not lose their booking, so bad attribution is dropped rather than\nraised. An agent is a program that can read an error and correct itself, so\ntelling it exactly what was wrong is more useful than silently coercing.\n\n`country` is REQUIRED here and optional on the form. That is the single\nhighest-leverage field in the payload: a missing country routes a booking\npast DataForSEO to SerpAPI, and in August 2026 that stranded 60 bookings on\nan exhausted free tier. An LLM-driven caller always knows which country a\nhotel is in, so requiring it costs nothing and closes the hole.\n\nPrices may carry decimals. The web form strips them because its JavaScript\nturns \"687.50\" into \"68750\", which is a browser problem an API does not\nhave. Rahul's assistant had to pre-round SGD 467.79 to 468; it no longer\nneeds to.",
        "properties": {
          "booking_platform": {
            "default": "other",
            "maxLength": 40,
            "title": "Booking Platform",
            "type": "string"
          },
          "check_in_date": {
            "format": "date",
            "title": "Check In Date",
            "type": "string"
          },
          "check_out_date": {
            "format": "date",
            "title": "Check Out Date",
            "type": "string"
          },
          "city": {
            "maxLength": 120,
            "minLength": 1,
            "title": "City",
            "type": "string"
          },
          "country": {
            "maxLength": 2,
            "minLength": 2,
            "title": "Country",
            "type": "string"
          },
          "currency": {
            "default": "USD",
            "maxLength": 3,
            "minLength": 3,
            "title": "Currency",
            "type": "string"
          },
          "hotel_name": {
            "maxLength": 200,
            "minLength": 1,
            "title": "Hotel Name",
            "type": "string"
          },
          "num_guests": {
            "default": 2,
            "maximum": 9.0,
            "minimum": 1.0,
            "title": "Num Guests",
            "type": "integer"
          },
          "num_rooms": {
            "default": 1,
            "maximum": 10.0,
            "minimum": 1.0,
            "title": "Num Rooms",
            "type": "integer"
          },
          "room_type": {
            "default": "Standard Room",
            "maxLength": 120,
            "title": "Room Type",
            "type": "string"
          },
          "total_price": {
            "exclusiveMinimum": 0.0,
            "maximum": 10000000.0,
            "title": "Total Price",
            "type": "number"
          }
        },
        "required": [
          "hotel_name",
          "city",
          "country",
          "check_in_date",
          "check_out_date",
          "total_price"
        ],
        "title": "AgentBookingItem",
        "type": "object"
      },
      "HTTPValidationError": {
        "properties": {
          "detail": {
            "items": {
              "$ref": "#/components/schemas/ValidationError"
            },
            "title": "Detail",
            "type": "array"
          }
        },
        "title": "HTTPValidationError",
        "type": "object"
      },
      "ValidationError": {
        "properties": {
          "ctx": {
            "title": "Context",
            "type": "object"
          },
          "input": {
            "title": "Input"
          },
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "title": "Location",
            "type": "array"
          },
          "msg": {
            "title": "Message",
            "type": "string"
          },
          "type": {
            "title": "Error Type",
            "type": "string"
          }
        },
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError",
        "type": "object"
      }
    }
  },
  "info": {
    "contact": {
      "email": "hello@rateranger.io",
      "name": "Rate Ranger"
    },
    "description": "Submit hotel bookings for price monitoring on a traveller's behalf.\n\nRate Ranger watches a hotel's rate after it has been booked and emails the\ntraveller when it drops far enough to be worth rebooking. This API exists so an\nAI assistant can set that up without driving a browser.\n\nCONSENT IS REQUIRED. A submission records the bookings and monitors nothing.\nRate Ranger emails the traveller one link; until they click it no price is\nchecked and no alert can fire. Unconfirmed bookings are deleted after 7 days.\nTell your user to expect that email, and do not report the booking as active\nuntil the read endpoint says so.\n\nNo key is needed. An anonymous caller may submit 10 bookings per day per IP.\nEmail hello@rateranger.io for a key if you need more.\n\nFree, for the traveller and for you. Rate Ranger earns an affiliate commission\nonly if the traveller chooses to rebook through a link in an alert.\n",
    "summary": "Post-booking hotel price monitoring, for AI assistants.",
    "title": "Rate Ranger Agent API",
    "version": "1.0.0"
  },
  "openapi": "3.1.0",
  "paths": {
    "/api/agent/bookings": {
      "post": {
        "description": "Submit 1 to 10 bookings for one traveller. Nothing is watched until they confirm.\n\nReturns a per-item result rather than one verdict for the batch. Rahul's\nassistant sent seven bookings in one message; rejecting all seven because\none date was malformed would make the caller guess which one, and an\nall-or-nothing contract turns a typo into a total failure.",
        "operationId": "agent_submit_bookings_api_agent_bookings_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentBatchSubmission"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {}
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Agent Submit Bookings"
      }
    },
    "/api/agent/bookings/{token}": {
      "get": {
        "description": "Status of one booking the caller created. Ownership is checked server-side.\n\nThe token binds an email to a booking id, and _resolve_booking_from_token\ncompares that email against the booking's own denormalised address before\nreturning anything. Holding a valid signature is not sufficient.",
        "operationId": "agent_read_booking_api_agent_bookings__token__get",
        "parameters": [
          {
            "in": "path",
            "name": "token",
            "required": true,
            "schema": {
              "title": "Token",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {}
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Agent Read Booking"
      }
    }
  },
  "servers": [
    {
      "url": "https://www.rateranger.io"
    }
  ]
}
