openapi: 3.1.0
info:
  title: Koobify API
  version: 1.1.0
  license:
    name: Proprietary
    identifier: LicenseRef-Koobify-Proprietary
  description: |
    Create, estimate, monitor, and cancel Koobify delivery requests.
    Generate an API key from the Credentials page in the developer dashboard,
    then send it as a Bearer token with every request.
servers:
  - url: https://api.koobify.com/v1
    description: Production
  - url: https://api.koobify.com/sandbox
    description: Sandbox
security:
  - bearerAuth: []
tags:
  - name: Deliveries
    description: Estimate, create, inspect, and cancel delivery requests.
  - name: Account
    description: Configure integration-level account settings.
  - name: Observability
    description: Inspect API requests and integration errors.
paths:
  /deliveries:
    get:
      tags: [Deliveries]
      operationId: listDeliveries
      summary: List delivery requests
      responses:
        '200':
          description: Delivery requests found.
          content:
            application/json:
              schema:
                type: object
                required: [deliveries, message]
                properties:
                  deliveries:
                    type: array
                    items:
                      $ref: '#/components/schemas/Delivery'
                  message:
                    type: string
              example:
                deliveries: []
                message: Delivery requests found.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalError'
    post:
      tags: [Deliveries]
      operationId: createDelivery
      summary: Create a delivery request from a quote
      description: |
        Charges the account's default card in production. The `quote_id` is
        also used as the idempotency scope, so retrying the same quote does not
        create another charge or delivery.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateDeliveryRequest'
            example:
              pickup_location:
                latitude: 45.516035
                longitude: -73.688167
                address: 1345 Rue Beaulieu, Saint-Laurent, QC H4L 3G9, Canada
                notes: Front Door
              dropoff_location:
                latitude: 45.5658245
                longitude: -73.6483576
                address: 1345 Rue Decarie, Saint-Laurent, QC H4L 3N2, Canada
                notes: Front Door
              pickup_phone_number: '+15145619526'
              dropoff_phone_number: '+15145619527'
              dropoff_contact_name: Client
              delivery_type: Parcel Delivery
              quote_id: dldfVhRqpWl5SvTYvzQj
              order_item:
                name: Hamburger
                quantity: 2
                size: Small
      responses:
        '200':
          description: The delivery was already created for this quote.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeliveryResponse'
        '201':
          description: Delivery request created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeliveryResponse'
        '402':
          description: The payment could not be completed or confirmed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: The quote belongs to another account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationError'
        '500':
          $ref: '#/components/responses/InternalError'
  /deliveries/estimate:
    post:
      tags: [Deliveries]
      operationId: estimateDelivery
      summary: Estimate a delivery and create a quote
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EstimateDeliveryRequest'
      responses:
        '200':
          description: Delivery estimate created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EstimateResponse'
        '403':
          description: The route could not be calculated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          $ref: '#/components/responses/ValidationError'
        '500':
          $ref: '#/components/responses/InternalError'
  /deliveries/{delivery_id}:
    parameters:
      - $ref: '#/components/parameters/DeliveryId'
    get:
      tags: [Deliveries]
      operationId: getDelivery
      summary: Get delivery details
      responses:
        '200':
          description: Delivery found.
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: '#/components/schemas/Delivery'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /deliveries/{delivery_id}/status:
    parameters:
      - $ref: '#/components/parameters/DeliveryId'
    get:
      tags: [Deliveries]
      operationId: getDeliveryStatus
      summary: Get the current delivery status
      responses:
        '200':
          description: Delivery status found.
          content:
            application/json:
              schema:
                type: object
                required: [status, delivery]
                properties:
                  status:
                    $ref: '#/components/schemas/DeliveryStatus'
                  delivery:
                    $ref: '#/components/schemas/Delivery'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /deliveries/{delivery_id}/cancel:
    parameters:
      - $ref: '#/components/parameters/DeliveryId'
    put:
      tags: [Deliveries]
      operationId: cancelDelivery
      summary: Cancel a delivery request
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CancelDeliveryRequest'
            examples:
              outOfItems:
                value:
                  reason: OUT_OF_ITEMS
                  cancelling_party: MERCHANT
              other:
                value:
                  reason: OTHER
                  details: Customer requested another delivery date.
                  cancelling_party: CUSTOMER
      responses:
        '200':
          description: Delivery request cancelled.
          content:
            application/json:
              schema:
                type: object
                required: [message]
                properties:
                  message:
                    type: string
              example:
                message: Delivery request successfully cancelled.
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationError'
  /account/webhook:
    get:
      tags: [Account]
      operationId: getWebhook
      summary: Get the configured webhook URL
      responses:
        '200':
          description: Webhook configuration returned.
          content:
            application/json:
              schema:
                type: object
                required: [message]
                properties:
                  webhookUrl:
                    type: [string, 'null']
                    format: uri
                  message:
                    type: string
        '404':
          $ref: '#/components/responses/NotFound'
  /account/webhook/update:
    put:
      tags: [Account]
      operationId: updateWebhook
      summary: Set the delivery-status webhook URL
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [webhook_url]
              properties:
                webhook_url:
                  type: string
                  format: uri
                  pattern: '^https://'
            example:
              webhook_url: https://merchant.example.com/koobify/events
      responses:
        '201':
          description: Webhook URL updated.
          content:
            application/json:
              schema:
                type: object
                required: [message]
                properties:
                  message:
                    type: string
        '422':
          $ref: '#/components/responses/ValidationError'
  /logs:
    get:
      tags: [Observability]
      operationId: listApiLogs
      summary: List API activity for the authenticated account
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            minimum: 1
            default: 1
        - name: size
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 12
        - name: method
          in: query
          schema:
            type: string
            enum: [DELETE, GET, PATCH, POST, PUT]
        - name: status
          in: query
          schema:
            type: string
            enum: [success, client_error, server_error, error]
        - name: q
          in: query
          description: Case-insensitive endpoint path search.
          schema:
            type: string
            maxLength: 100
        - name: from
          in: query
          schema:
            type: string
            format: date-time
        - name: to
          in: query
          schema:
            type: string
            format: date-time
      responses:
        '200':
          description: API activity returned.
          content:
            application/json:
              schema:
                type: object
                required: [logs, message]
                properties:
                  logs:
                    $ref: '#/components/schemas/ApiLogPage'
                  message:
                    type: string
        '401':
          $ref: '#/components/responses/Unauthorized'
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: API key generated from the developer dashboard Credentials page.
  parameters:
    DeliveryId:
      name: delivery_id
      in: path
      required: true
      schema:
        type: string
  responses:
    Unauthorized:
      description: Missing, invalid, or expired API key.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Forbidden:
      description: The resource does not belong to the authenticated account.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    NotFound:
      description: The requested resource was not found.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    ValidationError:
      description: Request validation failed.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            message: Validation failed.
            data:
              - value: ''
                msg: This field is required
                param: quote_id
                location: body
    InternalError:
      description: Unexpected server error.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  schemas:
    Coordinate:
      oneOf:
        - type: number
        - type: string
          pattern: '^-?[0-9]+(\.[0-9]+)?$'
    Location:
      type: object
      required: [latitude, longitude, address]
      properties:
        latitude:
          $ref: '#/components/schemas/Coordinate'
        longitude:
          $ref: '#/components/schemas/Coordinate'
        address:
          type: string
        apt_floor_suite:
          type: [string, 'null']
        building_name:
          type: [string, 'null']
        notes:
          type: [string, 'null']
    OrderItem:
      type: object
      required: [name, quantity, size]
      properties:
        name:
          type: string
        quantity:
          type: integer
          minimum: 1
        size:
          type: string
          enum: [Small, Food, Medium, Large, Mooving]
        weight:
          type: [string, 'null']
    EstimateDeliveryRequest:
      type: object
      required:
        - pickup_location
        - dropoff_location
        - pickup_phone_number
        - dropoff_phone_number
        - delivery_type
        - order_item
      properties:
        pickup_location:
          $ref: '#/components/schemas/Location'
        dropoff_location:
          $ref: '#/components/schemas/Location'
        pickup_phone_number:
          type: string
        dropoff_phone_number:
          type: string
        delivery_type:
          type: string
          enum: [Food Delivery, Parcel Delivery]
        order_item:
          $ref: '#/components/schemas/OrderItem'
        external_store_id:
          type: [string, 'null']
    CreateDeliveryRequest:
      allOf:
        - $ref: '#/components/schemas/EstimateDeliveryRequest'
        - type: object
          required: [quote_id]
          properties:
            quote_id:
              type: string
            dropoff_contact_name:
              type: string
            pickup_at:
              oneOf:
                - type: integer
                - type: string
                  format: date-time
            courier_tip:
              type: number
              minimum: 0
    DeliveryStatus:
      type: string
      enum:
        - pending
        - confirmed
        - picked_up
        - pickup
        - dropoff
        - delivered
        - cancelled
        - returned
    Delivery:
      type: object
      required: [uid, status, user_id]
      properties:
        uid:
          type: string
        quote_id:
          type: string
        status:
          $ref: '#/components/schemas/DeliveryStatus'
        user_id:
          type: string
        pickup:
          $ref: '#/components/schemas/Location'
        dropoff:
          $ref: '#/components/schemas/Location'
        pickup_phone_number:
          type: string
        dropoff_phone_number:
          type: string
        delivery_type:
          type: string
          enum: [Food Delivery, Parcel Delivery]
        order_item:
          $ref: '#/components/schemas/OrderItem'
        ride_distance_text:
          type: string
        ride_distance_value:
          type: number
        ride_duration:
          type: string
        ride_price:
          oneOf:
            - type: number
            - type: string
        currency:
          type: string
          example: CAD
        created_at:
          oneOf:
            - type: string
              format: date-time
            - type: integer
        updated_at:
          oneOf:
            - type: string
              format: date-time
            - type: integer
      additionalProperties: true
    DeliveryResponse:
      type: object
      required: [data, message]
      properties:
        data:
          $ref: '#/components/schemas/Delivery'
        message:
          type: string
    EstimateResponse:
      type: object
      required:
        - pickup_location
        - dropoff_location
        - pickup_phone_number
        - dropoff_phone_number
        - duration
        - ride_distance_text
        - fee
        - currency
        - quote_id
        - order_item
      properties:
        created_at:
          oneOf:
            - type: string
              format: date-time
            - type: integer
        pickup_location:
          $ref: '#/components/schemas/Location'
        dropoff_location:
          $ref: '#/components/schemas/Location'
        pickup_phone_number:
          type: string
        dropoff_phone_number:
          type: string
        duration:
          type: string
        ride_distance_text:
          type: string
        fee:
          type: number
        currency:
          type: string
          const: CAD
        quote_id:
          type: string
        order_item:
          $ref: '#/components/schemas/OrderItem'
        external_store_id:
          type: [string, 'null']
    CancelDeliveryRequest:
      type: object
      required: [reason]
      properties:
        reason:
          type: string
          enum:
            - OUT_OF_ITEMS
            - KITCHEN_CLOSED
            - CUSTOMER_CALLED_TO_CANCEL
            - RESTAURANT_TOO_BUSY
            - CANNOT_COMPLETE_CUSTOMER_NOTE
            - OTHER
        details:
          type: string
          description: Required when `reason` is `OTHER`.
        cancelling_party:
          type: string
          enum: [MERCHANT, CUSTOMER]
          default: MERCHANT
      anyOf:
        - properties:
            reason:
              enum:
                - OUT_OF_ITEMS
                - KITCHEN_CLOSED
                - CUSTOMER_CALLED_TO_CANCEL
                - RESTAURANT_TOO_BUSY
                - CANNOT_COMPLETE_CUSTOMER_NOTE
        - required: [details]
          properties:
            reason:
              const: OTHER
    Error:
      type: object
      required: [message]
      properties:
        message:
          type: string
        data:
          type: [array, object, 'null']
    ApiLog:
      type: object
      required: [id, status, method, path, createdAt]
      properties:
        id:
          type: string
        status:
          type: integer
        method:
          type: string
        path:
          type: string
        createdAt:
          type: string
          format: date-time
        errorMessage:
          type: [string, 'null']
    ApiLogSummary:
      type: object
      required:
        - totalRequests
        - successfulRequests
        - errorRequests
        - clientErrors
        - serverErrors
        - successRate
      properties:
        totalRequests:
          type: integer
        successfulRequests:
          type: integer
        errorRequests:
          type: integer
        clientErrors:
          type: integer
        serverErrors:
          type: integer
        successRate:
          type: number
    ApiLogPage:
      type: object
      required:
        - logs
        - currentPage
        - perPage
        - totalElements
        - totalPages
        - summary
      properties:
        logs:
          type: array
          items:
            $ref: '#/components/schemas/ApiLog'
        currentPage:
          type: integer
        perPage:
          type: integer
        totalElements:
          type: integer
        totalPages:
          type: integer
        hasNextPage:
          type: boolean
        hasPreviousPage:
          type: boolean
        nextPage:
          type: [integer, 'null']
        previousPage:
          type: [integer, 'null']
        summary:
          $ref: '#/components/schemas/ApiLogSummary'
webhooks:
  deliveryStatus:
    post:
      operationId: receiveDeliveryStatus
      summary: Delivery status notification sent to the configured webhook URL
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - status
                - delivery_id
                - customer_id
                - courier_trip_id
                - created_at
              properties:
                status:
                  $ref: '#/components/schemas/DeliveryStatus'
                delivery_id:
                  type: string
                customer_id:
                  type: string
                courier_trip_id:
                  type: string
                created_at:
                  oneOf:
                    - type: string
                      format: date-time
                    - type: integer
      responses:
        '200':
          description: Merchant endpoint accepted the notification.
