openapi: 3.1.0
info:
  title: HomingBird Public API
  version: "1.0.0"
  description: >
    Public REST API for hotel Lost & Found integrations (PMS, guest-experience,
    housekeeping). Authenticate with a per-hotel API key. Each key is scoped to a
    single hotel — you can only read and write that hotel's items.
servers:
  - url: https://gethomingbird.com/v1
    description: Production
security:
  - ApiKey: []
paths:
  /items:
    get:
      summary: List found items
      description: Returns the hotel's found items, newest first. Requires the `read` scope.
      parameters:
        - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 50 } }
        - { name: status, in: query, schema: { $ref: '#/components/schemas/ItemStatus' } }
        - { name: found_room, in: query, schema: { type: string } }
      responses:
        "200":
          description: OK
          headers:
            X-RateLimit-Limit: { schema: { type: integer }, description: Requests allowed per minute per key }
          content:
            application/json:
              schema:
                type: object
                properties:
                  items: { type: array, items: { $ref: '#/components/schemas/Item' } }
        "401": { $ref: '#/components/responses/Unauthorized' }
        "403": { $ref: '#/components/responses/ForbiddenScope' }
        "429": { $ref: '#/components/responses/RateLimited' }
    post:
      summary: Create a found item
      description: Registers a newly found item (status `found`). Requires the `ingest` scope.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [title]
              properties:
                title: { type: string, maxLength: 200 }
                category: { type: string, maxLength: 40 }
                colors: { type: array, items: { type: string } }
                found_room: { type: string, maxLength: 40 }
                found_at: { type: string, format: date-time }
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                type: object
                properties:
                  item:
                    type: object
                    properties:
                      id: { type: string, format: uuid }
                      status: { $ref: '#/components/schemas/ItemStatus' }
        "401": { $ref: '#/components/responses/Unauthorized' }
        "403": { $ref: '#/components/responses/ForbiddenScope' }
        "429": { $ref: '#/components/responses/RateLimited' }
  /items/{id}:
    get:
      summary: Get a found item
      description: Returns a single item by id. Requires the `read` scope. Returns 404 if the item does not belong to your hotel.
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties: { item: { $ref: '#/components/schemas/Item' } }
        "401": { $ref: '#/components/responses/Unauthorized' }
        "404": { $ref: '#/components/responses/NotFound' }
  /reservations:
    get:
      summary: List reservations
      description: Reservations synced into HomingBird. Requires `read`. Filter by `status`, `room`, `limit`.
      parameters:
        - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 50 } }
        - { name: status, in: query, schema: { $ref: '#/components/schemas/ReservationStatus' } }
        - { name: room, in: query, schema: { type: string } }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  reservations: { type: array, items: { $ref: '#/components/schemas/Reservation' } }
        "401": { $ref: '#/components/responses/Unauthorized' }
    post:
      summary: Upsert a reservation
      description: >
        Create or update a reservation from your PMS (keyed on `external_id` per hotel).
        Requires the `ingest` scope.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [external_id]
              properties:
                external_id: { type: string, description: Your PMS reservation id }
                room: { type: string }
                guest_name: { type: string }
                guest_email: { type: string, format: email }
                guest_phone: { type: string }
                check_in: { type: string, format: date }
                check_out: { type: string, format: date }
                status: { $ref: '#/components/schemas/ReservationStatus' }
      responses:
        "200":
          description: Upserted
          content:
            application/json:
              schema:
                type: object
                properties: { reservation: { $ref: '#/components/schemas/Reservation' } }
        "401": { $ref: '#/components/responses/Unauthorized' }
        "403": { $ref: '#/components/responses/ForbiddenScope' }
  /reservations/{external_id}:
    get:
      summary: Get a reservation
      parameters:
        - { name: external_id, in: path, required: true, schema: { type: string } }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties: { reservation: { $ref: '#/components/schemas/Reservation' } }
        "401": { $ref: '#/components/responses/Unauthorized' }
        "404": { $ref: '#/components/responses/NotFound' }
  /reservations/{external_id}/items:
    get:
      summary: "Did the guest leave something? — items found in the reservation's room during the stay"
      description: >
        Returns found items in the reservation's room, found between check-in and up to 2 days after
        check-out (post-checkout cleaning) and still available (`found`/`stored`). Ideal for a
        "did you leave something?" prompt at check-out in a guest-experience platform. Requires `read`.
      parameters:
        - { name: external_id, in: path, required: true, schema: { type: string } }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  reservation:
                    type: object
                    properties:
                      external_id: { type: string }
                      room: { type: string, nullable: true }
                      check_in: { type: string, format: date, nullable: true }
                      check_out: { type: string, format: date, nullable: true }
                  items: { type: array, items: { $ref: '#/components/schemas/Item' } }
                  count: { type: integer }
        "401": { $ref: '#/components/responses/Unauthorized' }
        "404": { $ref: '#/components/responses/NotFound' }
components:
  securitySchemes:
    ApiKey:
      type: http
      scheme: bearer
      description: "Per-hotel API key: `Authorization: Bearer hb_live_…` (created in Admin → API keys)."
  schemas:
    ItemStatus:
      type: string
      enum: [found, stored, matched, released, shipped, transferred, disposed, archived]
    ReservationStatus:
      type: string
      enum: [confirmed, in_house, checked_out, cancelled, unknown]
    Reservation:
      type: object
      properties:
        external_id: { type: string }
        room: { type: string, nullable: true }
        guest_name: { type: string, nullable: true }
        guest_email: { type: string, nullable: true }
        guest_phone: { type: string, nullable: true }
        check_in: { type: string, format: date, nullable: true }
        check_out: { type: string, format: date, nullable: true }
        status: { $ref: '#/components/schemas/ReservationStatus' }
    Item:
      type: object
      properties:
        id: { type: string, format: uuid }
        title: { type: string }
        category: { type: string, nullable: true }
        colors: { type: array, items: { type: string } }
        status: { $ref: '#/components/schemas/ItemStatus' }
        found_at: { type: string, format: date-time }
        found_room: { type: string, nullable: true }
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code: { type: string }
            message: { type: string }
  responses:
    Unauthorized:
      description: Missing, invalid or revoked API key
      content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
    ForbiddenScope:
      description: Key lacks the required scope (read / ingest)
      content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
    NotFound:
      description: Resource not found (or not in your hotel)
      content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
    RateLimited:
      description: Too many requests (120/min per key)
      headers:
        Retry-After: { schema: { type: integer } }
      content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
