openapi: 3.0.3
info:
  title: BookDirect Core (Nellalink) — lodging v1
  version: 1.0.0
  description: |
    API-first contract for BookDirect PMS on Nellalink Core.
    Hotels, shortlets, and apartments are entity type `property` plus metadata
    `property_category`. Host includes `/public`.
    Live explorer: https://backend-dev.bookdirect.live/public/docs/api#/
servers:
  - url: https://backend-dev.bookdirect.live/public/api/v1/nellalink
    description: BookDirect core dev
tags:
  - name: Auth
  - name: Entities
  - name: Metadata
  - name: Taxonomy
paths:
  /user/register:
    post:
      tags: [Auth]
      operationId: registerUser
      summary: Register
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, password, password_confirmation]
              properties:
                email: { type: string, format: email }
                password: { type: string, minLength: 4 }
                password_confirmation: { type: string }
                name: { type: string }
                username: { type: string }
                extra_data: { type: object }
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Envelope' }
  /user/login:
    post:
      tags: [Auth]
      operationId: loginUser
      summary: Login
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, password]
              properties:
                email: { type: string, format: email }
                password: { type: string }
                device:
                  type: object
                  properties:
                    device_id: { type: string }
                    platform: { type: string }
                    device_label: { type: string }
                    issue_refresh_token: { type: boolean }
                    bind_ip: { type: boolean }
      responses:
        '200':
          description: JWT in data.access_token; store data.uuid
  /user/refresh:
    post:
      tags: [Auth]
      operationId: refreshToken
      summary: Refresh access token
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [refresh_token]
              properties:
                refresh_token: { type: string }
      responses:
        '200': { description: New access_token }
  /user/logout:
    post:
      tags: [Auth]
      operationId: logoutUser
      summary: Logout
      security:
        - ApiKeyAuth: []
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                refresh_token: { type: string }
      responses:
        '200': { description: Logged out }
  /user/logout-all:
    post:
      tags: [Auth]
      operationId: logoutAll
      summary: Logout all devices
      security:
        - ApiKeyAuth: []
      responses:
        '200': { description: All sessions revoked }
  /user/reset-password:
    post:
      tags: [Auth]
      operationId: resetPasswordSend
      security:
        - ApiKeyAuth: []
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [email]
              properties:
                email: { type: string, format: email }
      responses:
        '200': { description: Token emailed }
  /user/reset-password/validate:
    post:
      tags: [Auth]
      operationId: resetPasswordValidate
      security:
        - ApiKeyAuth: []
      responses:
        '200': { description: Password changed }
  /user/verify-email-address:
    post:
      tags: [Auth]
      operationId: verifyEmailSend
      security:
        - ApiKeyAuth: []
      responses:
        '200': { description: Token emailed }
  /user/{user_uuid}:
    get:
      tags: [Auth]
      operationId: getUser
      summary: Get user — only own uuid
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/UserUuid'
      responses:
        '200': { description: User }
    put:
      tags: [Auth]
      operationId: updateUser
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/UserUuid'
      responses:
        '200': { description: Updated }
  /smart-meta-manager/entity/{entity_type}:
    post:
      tags: [Entities]
      operationId: createEntity
      summary: Create entity (property, room, rate_plan, booking, payment)
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/EntityType'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CreateEntity' }
      responses:
        '201': { description: Created — store data.uuid }
    get:
      tags: [Entities]
      operationId: listEntities
      summary: List entities — private lists must pass owned_by=JWT uuid
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/EntityType'
        - name: owned_by
          in: query
          schema: { type: string, format: uuid }
        - name: parent_entity_type
          in: query
          schema: { type: string }
        - name: parent_entity_uuid
          in: query
          schema: { type: string, format: uuid }
        - name: status
          in: query
          schema: { type: string }
        - name: search
          in: query
          schema: { type: string }
        - name: search_by
          in: query
          schema: { type: string }
        - name: per_page
          in: query
          schema: { type: integer, default: 20 }
        - name: page
          in: query
          schema: { type: integer, default: 1 }
        - name: sort_by
          in: query
          schema: { type: string }
        - name: sort_order
          in: query
          schema: { type: string, enum: [asc, desc] }
      responses:
        '200': { description: Paginated list }
  /smart-meta-manager/entity/{entity_type}/{entity_uuid}:
    get:
      tags: [Entities]
      operationId: getEntity
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/EntityType'
        - $ref: '#/components/parameters/EntityUuid'
      responses:
        '200': { description: Entity + meta_data }
    put:
      tags: [Entities]
      operationId: updateEntity
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/EntityType'
        - $ref: '#/components/parameters/EntityUuid'
      requestBody:
        content:
          application/json:
            schema: { $ref: '#/components/schemas/UpdateEntity' }
      responses:
        '200': { description: extra_data replaces the whole object }
  /smart-meta-manager/entity/{entity_type}/slug/{slug}:
    get:
      tags: [Entities]
      operationId: getEntityBySlug
      security:
        - ApiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/EntityType'
        - name: slug
          in: path
          required: true
          schema: { type: string }
      responses:
        '200': { description: Entity }
  /smart-meta-manager/entity/{entity_type}/trash/{entity_uuid}:
    delete:
      tags: [Entities]
      operationId: trashEntity
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/EntityType'
        - $ref: '#/components/parameters/EntityUuid'
      responses:
        '200': { description: Soft deleted }
  /smart-meta-manager/entity/{entity_type}/restore/{entity_uuid}:
    patch:
      tags: [Entities]
      operationId: restoreEntity
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/EntityType'
        - $ref: '#/components/parameters/EntityUuid'
      responses:
        '200': { description: Restored }
  /meta-data/entity/{entity_type}/{entity_uuid}:
    post:
      tags: [Metadata]
      operationId: createMetaData
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/EntityType'
        - $ref: '#/components/parameters/EntityUuid'
      requestBody:
        content:
          application/json:
            schema:
              oneOf:
                - $ref: '#/components/schemas/MetaRow'
                - type: array
                  items: { $ref: '#/components/schemas/MetaRow' }
      responses:
        '201': { description: Created }
    put:
      tags: [Metadata]
      operationId: updateMetaData
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/EntityType'
        - $ref: '#/components/parameters/EntityUuid'
      responses:
        '200': { description: Updated }
  /tag:
    post:
      tags: [Taxonomy]
      operationId: createTags
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      requestBody:
        content:
          application/json:
            schema:
              type: array
              items: { type: string }
      responses:
        '201': { description: Tag names /^[a-z0-9_]+$/ }
  /tags:
    get:
      tags: [Taxonomy]
      operationId: listTags
      security:
        - ApiKeyAuth: []
      responses:
        '200': { description: Tags }
  /category:
    post:
      tags: [Taxonomy]
      operationId: createCategories
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      responses:
        '201': { description: Created }
  /categories:
    get:
      tags: [Taxonomy]
      operationId: listCategories
      security:
        - ApiKeyAuth: []
      responses:
        '200': { description: Categories }
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
  parameters:
    EntityType:
      name: entity_type
      in: path
      required: true
      schema:
        type: string
        enum: [property, room, rate_plan, inventory, booking, payment]
    EntityUuid:
      name: entity_uuid
      in: path
      required: true
      schema: { type: string, format: uuid }
    UserUuid:
      name: user_uuid
      in: path
      required: true
      schema: { type: string, format: uuid }
  schemas:
    Envelope:
      type: object
      properties:
        status: { type: boolean }
        status_code: { type: integer }
        message: { type: string }
        data: {}
        meta: { type: object }
    CreateEntity:
      type: object
      required: [request_id, meta_key, owned_by]
      properties:
        request_id: { type: string }
        meta_key: { type: string }
        slug: { type: string }
        title_name: { type: string }
        description: { type: string }
        status: { type: string, example: draft }
        owned_by: { type: string, format: uuid }
        parent_entity_type: { type: string }
        parent_entity_uuid: { type: string, format: uuid }
        extra_data: { type: object }
        tags:
          type: array
          items: { type: string }
        categories:
          type: array
          items:
            type: object
            properties:
              name: { type: string }
        co_owned_by:
          type: array
          items: { type: string, format: uuid }
    UpdateEntity:
      type: object
      properties:
        title_name: { type: string }
        description: { type: string }
        status: { type: string }
        extra_data: { type: object }
        tags:
          type: array
          items: { type: string }
        categories:
          type: array
          items:
            type: object
            properties:
              name: { type: string }
    MetaRow:
      type: object
      required: [meta_key, meta_value]
      properties:
        meta_key: { type: string }
        meta_value: {}
        data_type: { type: string, enum: [string, boolean, number, json] }
        extra_data: { type: object }
