openapi: 3.1.0
info:
  title: BringMeThat v1 API
  version: 1.0.0
  description: |
    Agent-friendly food-ordering API. Stateless, JSON, idempotency-key
    supported, prices always in integer cents.
  contact:
    email: developers@bringmethat.com
    url: https://bringmethat.com/developers
  license:
    name: Proprietary
servers:
  - url: https://bringmethat.com/api/v1
    description: Production
  - url: https://bmt2-staging-production.up.railway.app/api/v1
    description: Staging
security:
  - bearerAuth: []
paths:
  /restaurants:
    get:
      summary: Search restaurants near a location and/or by cuisine
      operationId: searchRestaurants
      parameters:
        - { in: query, name: lat, schema: { type: number, format: double } }
        - { in: query, name: lng, schema: { type: number, format: double } }
        - { in: query, name: city, schema: { type: string } }
        - { in: query, name: state, schema: { type: string, minLength: 2, maxLength: 2 } }
        - { in: query, name: zip, schema: { type: string } }
        - { in: query, name: cuisine, schema: { type: string, example: pizza } }
        - { in: query, name: open_now, schema: { type: boolean, default: true } }
        - { in: query, name: limit, schema: { type: integer, minimum: 1, maximum: 50, default: 20 } }
        - { in: query, name: offset, schema: { type: integer, minimum: 0, default: 0 } }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RestaurantList" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }
  /restaurants/{id}:
    get:
      summary: Get a restaurant by id
      operationId: getRestaurant
      parameters:
        - { in: path, name: id, required: true, schema: { type: integer } }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Restaurant" }
        "404": { $ref: "#/components/responses/NotFound" }
  /restaurants/{id}/menu:
    get:
      summary: Full structured menu for a restaurant
      operationId: getMenu
      parameters:
        - { in: path, name: id, required: true, schema: { type: integer } }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Menu" }
        "404": { $ref: "#/components/responses/NotFound" }
  /restaurants/{id}/availability:
    get:
      summary: Can this restaurant accept an order right now?
      operationId: checkAvailability
      parameters:
        - { in: path, name: id, required: true, schema: { type: integer } }
        - { in: query, name: at, schema: { type: string, format: date-time } }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  available: { type: boolean }
                  reason: { type: string, nullable: true, enum: [closed_for_day, permanently_closed, temporarily_closed, out_of_delivery_range, null] }
                  reopens_at: { type: string, format: date-time, nullable: true }
  /orders:
    post:
      summary: Create an order (requires Idempotency-Key header)
      operationId: createOrder
      parameters:
        - in: header
          name: Idempotency-Key
          required: true
          schema: { type: string, minLength: 8, maxLength: 128 }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/OrderRequest" }
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema: { $ref: "#/components/schemas/OrderCreated" }
        "409":
          description: Idempotency conflict — same key with different body
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
  /orders/{id}:
    get:
      summary: Order status
      operationId: getOrder
      parameters:
        - { in: path, name: id, required: true, schema: { type: integer } }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Order" }
  /orders/{id}/pay:
    post:
      summary: Pay an order using a pre-tokenized payment method
      operationId: payOrder
      parameters:
        - { in: path, name: id, required: true, schema: { type: integer } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [payment_method]
              properties:
                payment_method:
                  type: string
                  enum: [spreedly_token, paypal_order_id, apple_pay_token, google_pay_token]
                token: { type: string }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, enum: [paid, failed] }
                  receipt_url: { type: string }
  /menus/search:
    get:
      summary: Full-text search across menu items
      operationId: searchMenuItems
      parameters:
        - { in: query, name: q, required: true, schema: { type: string } }
        - { in: query, name: near, schema: { type: string, pattern: "^-?\\d+\\.\\d+,-?\\d+\\.\\d+$", example: "41.8781,-87.6298" } }
        - { in: query, name: limit, schema: { type: integer, default: 20 } }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: "#/components/schemas/MenuItem" }
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API key
      description: |
        Partner API key obtained from https://bringmethat.com/developers.
        Rate-limited per key; see headers X-RateLimit-Remaining, X-RateLimit-Reset.
  responses:
    BadRequest: { description: Bad request, content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
    Unauthorized: { description: Missing/invalid API key, content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
    NotFound: { description: Not found, content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
    RateLimited: { description: Rate limit exceeded, content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
  schemas:
    Error:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message, retriable]
          properties:
            code: { type: string, example: INVALID_ADDRESS }
            message: { type: string }
            retriable: { type: boolean }
            documentation_url: { type: string, format: uri }
    RestaurantList:
      type: object
      properties:
        restaurants:
          type: array
          items: { $ref: "#/components/schemas/Restaurant" }
        total: { type: integer }
        next_cursor: { type: string, nullable: true }
    Restaurant:
      type: object
      properties:
        id: { type: integer }
        name: { type: string }
        address: { type: string }
        city: { type: string }
        state: { type: string }
        zip: { type: string }
        lat: { type: number }
        lng: { type: number }
        cuisines: { type: array, items: { type: string } }
        phone: { type: string }
        hours: { type: object, additionalProperties: { type: array, items: { type: string } } }
        time_zone: { type: string }
        is_open_now: { type: boolean }
        menu_last_verified_at: { type: string, format: date-time, nullable: true }
        menu_confidence_score: { type: number, nullable: true }
    Menu:
      type: object
      properties:
        business_id: { type: integer }
        updated_at: { type: string, format: date-time }
        sections:
          type: array
          items:
            type: object
            properties:
              name: { type: string }
              items:
                type: array
                items: { $ref: "#/components/schemas/MenuItem" }
    MenuItem:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
        description: { type: string, nullable: true }
        price_cents: { type: integer }
        category: { type: string, nullable: true }
        dietary_tags: { type: array, items: { type: string } }
        modifiers:
          type: array
          items:
            type: object
            properties:
              name: { type: string }
              required: { type: boolean }
              options:
                type: array
                items:
                  type: object
                  properties:
                    name: { type: string }
                    price_delta_cents: { type: integer }
    OrderRequest:
      type: object
      required: [restaurant_id, items, fulfillment]
      properties:
        restaurant_id: { type: integer }
        items:
          type: array
          items:
            type: object
            required: [menu_item_id, quantity]
            properties:
              menu_item_id: { type: string }
              quantity: { type: integer, minimum: 1 }
              modifiers:
                type: array
                items: { type: string }
              special_instructions: { type: string }
        fulfillment: { type: string, enum: [pickup, delivery] }
        address:
          type: object
          properties:
            line1: { type: string }
            city: { type: string }
            state: { type: string }
            zip: { type: string }
        contact:
          type: object
          properties:
            name: { type: string }
            phone: { type: string }
            email: { type: string, format: email }
    OrderCreated:
      type: object
      properties:
        order_id: { type: integer }
        total_cents: { type: integer }
        pay_url: { type: string, format: uri }
        expires_at: { type: string, format: date-time }
    Order:
      type: object
      properties:
        id: { type: integer }
        status: { type: string, enum: [pending_payment, paid, confirmed_by_restaurant, in_transit, delivered, failed, refunded] }
        restaurant_id: { type: integer }
        items: { type: array, items: { $ref: "#/components/schemas/MenuItem" } }
        total_cents: { type: integer }
        created_at: { type: string, format: date-time }
        eta: { type: string, format: date-time, nullable: true }
