openapi: "3.1.0"
info:
  title: "Blink API"
  version: "2.1.0"
  summary: "On-demand delivery across Malaysia: quote, book, track, webhooks, sandbox and widgets."
  description: "Money is integer sen (RM1 = 100 sen). Authenticate with `Authorization: Bearer <key>`: blink_live_ (live), blink_test_ (sandbox), or a portal JWT. Publishable pk_live_/pk_test_ keys only work on /v1/widget/*. Every POST/PATCH accepts an Idempotency-Key header. Errors are {\"error\": \"...\"}; validation failures are 400 {\"error\":\"validation\",\"issues\":[...]}."
servers:
  - url: "https://api.blink.example.my"
    description: "Production"
  - url: "http://localhost:4000"
    description: "Local development"
security:
  - bearer: []
tags:
  - name: "Orders"
  - name: "Quotes"
  - name: "Stops"
  - name: "Business"
  - name: "Planning"
  - name: "Recurring"
  - name: "Developer"
  - name: "Webhooks"
  - name: "Sandbox"
  - name: "Widgets"
  - name: "Driver"
  - name: "Admin"
  - name: "Super"
  - name: "Public"
paths:
  "/health":
    get:
      tags: ["Public"]
      operationId: "health"
      summary: "Health check"
      security: []
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/config":
    get:
      tags: ["Public"]
      operationId: "getConfig"
      summary: "Public feature flags"
      security: []
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/vehicles":
    get:
      tags: ["Quotes"]
      operationId: "listVehicles"
      summary: "Vehicle tiers with rate card"
      security: []
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "array"
                items:
                  type: "object"
                  additionalProperties: true
        default:
          "$ref": "#/components/responses/Error"
  "/v1/quote":
    post:
      tags: ["Quotes"]
      operationId: "getQuote"
      summary: "Price a route (no booking)"
      security:
        - {}
        - bearer: []
      parameters:
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/QuoteRequest"
            example:
              vehicle: "motorcycle"
              stops:
                - address: "Warehouse, Bandar Sunway"
                  lat: 3.0733
                  lng: 101.6078
                  contactName: "Store"
                  contactPhone: "0123456789"
                - address: "12 Jalan SS2/24, Petaling Jaya"
                  lat: 3.118
                  lng: 101.623
                  contactName: "Aisyah"
                  contactPhone: "0172223344"
              optimize: true
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/Quote"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/route/optimize":
    post:
      tags: ["Quotes"]
      operationId: "optimizeRoute"
      summary: "Reorder stops for the shortest route (first stop fixed)"
      parameters:
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              required: ["stops"]
              properties:
                stops:
                  type: "array"
                  items:
                    "$ref": "#/components/schemas/Stop"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/auth/otp":
    post:
      tags: ["Public"]
      operationId: "requestOtp"
      summary: "Send a sign-in code by SMS"
      security: []
      parameters:
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              required: ["phone"]
              properties:
                phone:
                  type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/auth/verify":
    post:
      tags: ["Public"]
      operationId: "verifyOtp"
      summary: "Exchange the code for a JWT"
      security: []
      parameters:
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              required: ["phone", "code"]
              properties:
                phone:
                  type: "string"
                code:
                  type: "string"
                name:
                  type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/auth/logout-all":
    post:
      tags: ["Public"]
      operationId: "logoutAll"
      summary: "Revoke every session of the signed-in user"
      parameters:
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: false
        content:
          "application/json":
            schema:
              type: "object"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/me":
    get:
      tags: ["Public"]
      operationId: "getMe"
      summary: "Current user (or API-key principal)"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/places/autocomplete":
    get:
      tags: ["Public"]
      operationId: "placesAutocomplete"
      summary: "Address suggestions (Malaysia)"
      security: []
      parameters:
        - name: "q"
          in: "query"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/places/{id}":
    get:
      tags: ["Public"]
      operationId: "placeDetails"
      summary: "Resolve a place id to address + lat/lng"
      security: []
      parameters:
        - name: "id"
          in: "path"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/payments/billplz/callback":
    post:
      tags: ["Public"]
      operationId: "billplzCallback"
      summary: "Billplz payment callback (form encoded)"
      security: []
      parameters:
        - "$ref": "#/components/parameters/IdempotencyKey"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/payments/mock/{ref}/pay":
    get:
      tags: ["Public"]
      operationId: "mockPay"
      summary: "Development only: mark a mock payment paid"
      security: []
      parameters:
        - name: "ref"
          in: "path"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/track/{token}":
    get:
      tags: ["Public"]
      operationId: "trackOrder"
      summary: "Public tracking (no phones, no prices)"
      security: []
      parameters:
        - name: "token"
          in: "path"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/files/{folder}/{name}":
    get:
      tags: ["Public"]
      operationId: "getFile"
      summary: "Uploaded media (proof of delivery, documents)"
      security: []
      parameters:
        - name: "folder"
          in: "path"
          required: true
          schema:
            type: "string"
        - name: "name"
          in: "path"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/ws":
    get:
      tags: ["Public"]
      operationId: "websocket"
      summary: "WebSocket: realtime events (token=JWT query)"
      security: []
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/ws/track":
    get:
      tags: ["Public"]
      operationId: "trackWebsocket"
      summary: "WebSocket: public tracking stream (token=track token)"
      security: []
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/me/places":
    get:
      tags: ["Public"]
      operationId: "listPlaces"
      summary: "Saved places"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
    post:
      tags: ["Public"]
      operationId: "createPlace"
      summary: "Save a place"
      parameters:
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              required: ["label", "address", "lat", "lng"]
              properties:
                label:
                  type: "string"
                address:
                  type: "string"
                lat:
                  type: "number"
                lng:
                  type: "number"
                contactName:
                  type: "string"
                contactPhone:
                  type: "string"
      responses:
        "201":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/me/places/{id}":
    delete:
      tags: ["Public"]
      operationId: "deletePlace"
      summary: "Delete a saved place"
      parameters:
        - name: "id"
          in: "path"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/me/tickets":
    get:
      tags: ["Public"]
      operationId: "listMyTickets"
      summary: "My support tickets"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
    post:
      tags: ["Public"]
      operationId: "createTicket"
      summary: "Open a support ticket"
      parameters:
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              properties:
                category:
                  type: "string"
                subject:
                  type: "string"
                body:
                  type: "string"
                orderCode:
                  type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/me/tickets/{code}":
    get:
      tags: ["Public"]
      operationId: "getMyTicket"
      summary: "Ticket with messages"
      parameters:
        - name: "code"
          in: "path"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/me/tickets/{code}/reply":
    post:
      tags: ["Public"]
      operationId: "replyMyTicket"
      summary: "Reply to my ticket"
      parameters:
        - name: "code"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              required: ["body"]
              properties:
                body:
                  type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/orders":
    post:
      tags: ["Orders"]
      operationId: "createOrder"
      summary: "Create (book) an order"
      description: "Needs orders:write. With a blink_test_ key the order is a sandbox order (never dispatched, no ledger)."
      parameters:
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/CreateOrder"
            example:
              vehicle: "motorcycle"
              paymentMethod: "credit"
              externalRef: "WC-10421"
              stops:
                - address: "Warehouse, Bandar Sunway"
                  lat: 3.0733
                  lng: 101.6078
                  contactName: "Store"
                  contactPhone: "0123456789"
                - address: "12 Jalan SS2/24, Petaling Jaya"
                  lat: 3.118
                  lng: 101.623
                  contactName: "Aisyah"
                  contactPhone: "0172223344"
      responses:
        "201":
          description: "OK"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/OrderDetail"
        default:
          "$ref": "#/components/responses/Error"
    get:
      tags: ["Orders"]
      operationId: "listOrders"
      summary: "List orders (newest first)"
      parameters:
        - name: "limit"
          in: "query"
          required: false
          schema:
            type: "string"
        - name: "before"
          in: "query"
          required: false
          schema:
            type: "string"
        - name: "sandbox"
          in: "query"
          required: false
          schema:
            type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
                properties:
                  orders:
                    type: "array"
                    items:
                      "$ref": "#/components/schemas/Order"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/orders/{code}":
    get:
      tags: ["Orders"]
      operationId: "getOrder"
      summary: "Order with stops, items, events, driver and trackUrl"
      parameters:
        - name: "code"
          in: "path"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/OrderDetail"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/orders/{code}/cancel":
    post:
      tags: ["Orders"]
      operationId: "cancelOrder"
      summary: "Cancel an order"
      parameters:
        - name: "code"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              properties:
                reason:
                  type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/orders/{code}/priority":
    post:
      tags: ["Orders"]
      operationId: "addPriority"
      summary: "Add a priority fee while searching"
      parameters:
        - name: "code"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              required: ["addSen"]
              properties:
                addSen:
                  type: "integer"
                  minimum: 100
                  maximum: 10000
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/orders/{code}/messages":
    get:
      tags: ["Orders"]
      operationId: "listMessages"
      summary: "Chat with the driver"
      parameters:
        - name: "code"
          in: "path"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
    post:
      tags: ["Orders"]
      operationId: "sendMessage"
      summary: "Send a chat message"
      parameters:
        - name: "code"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              required: ["body"]
              properties:
                body:
                  type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/orders/{code}/rate":
    post:
      tags: ["Orders"]
      operationId: "rateOrder"
      summary: "Rate the driver after delivery"
      parameters:
        - name: "code"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              required: ["stars"]
              properties:
                stars:
                  type: "integer"
                  minimum: 1
                  maximum: 5
                comment:
                  type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/orders/{code}/stops/{seq}":
    patch:
      tags: ["Stops"]
      operationId: "editStop"
      summary: "Edit a stop (details, window, address before arrival)"
      parameters:
        - name: "code"
          in: "path"
          required: true
          schema:
            type: "string"
        - name: "seq"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              properties:
                ref:
                  type: "string"
                contactName:
                  type: "string"
                contactPhone:
                  type: "string"
                notes:
                  type: "string"
                windowStart:
                  type: "string"
                windowEnd:
                  type: "string"
                address:
                  type: "string"
                lat:
                  type: "number"
                lng:
                  type: "number"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/orders/{code}/stops":
    post:
      tags: ["Stops"]
      operationId: "addStop"
      summary: "Add a stop (with items)"
      parameters:
        - name: "code"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              required: ["stop"]
              properties:
                stop:
                  "$ref": "#/components/schemas/Stop"
                items:
                  type: "array"
                  items:
                    type: "object"
                    additionalProperties: true
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/orders/{code}/stops/{seq}/cancel":
    post:
      tags: ["Stops"]
      operationId: "cancelStop"
      summary: "Cancel one stop"
      parameters:
        - name: "code"
          in: "path"
          required: true
          schema:
            type: "string"
        - name: "seq"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              required: ["reason"]
              properties:
                reason:
                  type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/orders/{code}/stops/{seq}/reattempt":
    post:
      tags: ["Stops"]
      operationId: "reattemptStop"
      summary: "Retry a failed drop-off"
      parameters:
        - name: "code"
          in: "path"
          required: true
          schema:
            type: "string"
        - name: "seq"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              properties:
                windowStart:
                  type: "string"
                windowEnd:
                  type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/business/account":
    get:
      tags: ["Business"]
      operationId: "getAccount"
      summary: "Business account, amount owed and COD payable"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/business/analytics":
    get:
      tags: ["Business"]
      operationId: "businessAnalytics"
      summary: "Daily orders and spend (reports scope)"
      parameters:
        - name: "days"
          in: "query"
          required: false
          schema:
            type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/business/reports/stops":
    get:
      tags: ["Business"]
      operationId: "businessStopReport"
      summary: "Delivery performance by stop (reports scope)"
      parameters:
        - name: "days"
          in: "query"
          required: false
          schema:
            type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/business/invoices":
    get:
      tags: ["Business"]
      operationId: "listInvoices"
      summary: "Monthly invoices"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/business/invoices/{month}":
    get:
      tags: ["Business"]
      operationId: "getInvoice"
      summary: "Invoice lines for YYYY-MM"
      parameters:
        - name: "month"
          in: "path"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/business/members":
    get:
      tags: ["Business"]
      operationId: "listMembers"
      summary: "Team members (portal)"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
    post:
      tags: ["Business"]
      operationId: "addMember"
      summary: "Add a team member (business admin)"
      parameters:
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              required: ["phone", "name", "role"]
              properties:
                phone:
                  type: "string"
                name:
                  type: "string"
                role:
                  type: "string"
                  enum: ["admin", "staff"]
      responses:
        "201":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/business/members/{userId}":
    delete:
      tags: ["Business"]
      operationId: "removeMember"
      summary: "Remove a team member"
      parameters:
        - name: "userId"
          in: "path"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/business/orders/bulk":
    post:
      tags: ["Business"]
      operationId: "bulkCreateOrders"
      summary: "Create up to 500 orders (idempotent per externalRef)"
      parameters:
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "array"
              items:
                "$ref": "#/components/schemas/CreateOrder"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/business/recipients":
    get:
      tags: ["Business"]
      operationId: "searchRecipients"
      summary: "Past recipients and saved places"
      parameters:
        - name: "q"
          in: "query"
          required: false
          schema:
            type: "string"
        - name: "limit"
          in: "query"
          required: false
          schema:
            type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/business/calendar":
    get:
      tags: ["Planning"]
      operationId: "calendar"
      summary: "Scheduled orders and upcoming recurring runs (up to 6 weeks)"
      parameters:
        - name: "from"
          in: "query"
          required: true
          schema:
            type: "string"
        - name: "to"
          in: "query"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/business/import-mappings":
    get:
      tags: ["Planning"]
      operationId: "listImportMappings"
      summary: "Saved spreadsheet column mappings"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/business/import-mappings/{name}":
    put:
      tags: ["Planning"]
      operationId: "saveImportMapping"
      summary: "Save a header → field mapping"
      parameters:
        - name: "name"
          in: "path"
          required: true
          schema:
            type: "string"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              required: ["mapping"]
              properties:
                mapping:
                  type: "object"
                  additionalProperties:
                    type: "string"
                headers:
                  type: "array"
                  items:
                    type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
    delete:
      tags: ["Planning"]
      operationId: "deleteImportMapping"
      summary: "Delete a mapping"
      parameters:
        - name: "name"
          in: "path"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/business/plan-day":
    post:
      tags: ["Planning"]
      operationId: "planDay"
      summary: "Split a day of drop-offs (up to 5 pickups, 200 drops) into vehicle runs"
      parameters:
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/PlanDay"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/business/plan-day/book":
    post:
      tags: ["Planning"]
      operationId: "bookDay"
      summary: "Book planned runs"
      parameters:
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              required: ["runs", "paymentMethod"]
              properties:
                runs:
                  type: "array"
                  items:
                    type: "object"
                    properties:
                      vehicle:
                        "$ref": "#/components/schemas/Vehicle"
                      stops:
                        type: "array"
                        items:
                          "$ref": "#/components/schemas/Stop"
                      items:
                        type: "array"
                        items:
                          "$ref": "#/components/schemas/Item"
                paymentMethod:
                  type: "string"
                  enum: ["credit", "cod"]
                scheduledAt:
                  type: "string"
                ref:
                  type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/business/recurring":
    get:
      tags: ["Recurring"]
      operationId: "listRecurring"
      summary: "Recurring runs"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
                properties:
                  recurring:
                    type: "array"
                    items:
                      "$ref": "#/components/schemas/Recurring"
        default:
          "$ref": "#/components/responses/Error"
    post:
      tags: ["Recurring"]
      operationId: "createRecurring"
      summary: "Create a recurring run"
      parameters:
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/RecurringCreate"
      responses:
        "201":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
                properties:
                  recurring:
                    "$ref": "#/components/schemas/Recurring"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/business/recurring/{id}":
    patch:
      tags: ["Recurring"]
      operationId: "updateRecurring"
      summary: "Update / pause a recurring run"
      parameters:
        - name: "id"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/RecurringCreate"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
    delete:
      tags: ["Recurring"]
      operationId: "deleteRecurring"
      summary: "Delete a recurring run"
      parameters:
        - name: "id"
          in: "path"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/business/keys":
    get:
      tags: ["Developer"]
      operationId: "listKeys"
      summary: "API keys (portal)"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
                properties:
                  keys:
                    type: "array"
                    items:
                      "$ref": "#/components/schemas/ApiKey"
                  scopes:
                    type: "array"
                    items:
                      type: "string"
        default:
          "$ref": "#/components/responses/Error"
    post:
      tags: ["Developer"]
      operationId: "createKey"
      summary: "Create an API key (business admin, portal only)"
      parameters:
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/ApiKeyCreate"
            example:
              name: "Shop backend"
              mode: "test"
              scopes: ["orders:read", "orders:write", "quotes"]
              expiresInDays: 90
      responses:
        "201":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
                properties:
                  key:
                    type: "string"
                    description: "Raw key, shown once"
                  apiKey:
                    "$ref": "#/components/schemas/ApiKey"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/business/keys/{id}":
    patch:
      tags: ["Developer"]
      operationId: "updateKey"
      summary: "Change name, scopes, expiry, IP allowlist, domains, widget config"
      parameters:
        - name: "id"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/ApiKeyCreate"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
    delete:
      tags: ["Developer"]
      operationId: "revokeKey"
      summary: "Revoke a key"
      parameters:
        - name: "id"
          in: "path"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/business/webhook":
    put:
      tags: ["Webhooks"]
      operationId: "setWebhookUrl"
      summary: "v1: set the primary webhook URL (mirrors the legacy endpoint)"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              required: ["url"]
              properties:
                url:
                  type: ["string", "null"]
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/business/webhooks":
    get:
      tags: ["Webhooks"]
      operationId: "listWebhookEndpoints"
      summary: "Webhook endpoints with 7-day stats and the event catalogue"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
                properties:
                  endpoints:
                    type: "array"
                    items:
                      "$ref": "#/components/schemas/WebhookEndpoint"
                  events:
                    type: "array"
                    items:
                      type: "string"
                  signatureVersion:
                    type: "string"
        default:
          "$ref": "#/components/responses/Error"
    post:
      tags: ["Webhooks"]
      operationId: "createWebhookEndpoint"
      summary: "Add an endpoint"
      parameters:
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/WebhookEndpointCreate"
            example:
              url: "https://shop.example.my/blink/webhook"
              events: ["order.*"]
              mode: "live"
              maxAttempts: 8
      responses:
        "201":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
                properties:
                  endpoint:
                    "$ref": "#/components/schemas/WebhookEndpoint"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/business/webhooks/{id}":
    patch:
      tags: ["Webhooks"]
      operationId: "updateWebhookEndpoint"
      summary: "Update an endpoint"
      parameters:
        - name: "id"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/WebhookEndpointCreate"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
    delete:
      tags: ["Webhooks"]
      operationId: "deleteWebhookEndpoint"
      summary: "Delete an endpoint"
      parameters:
        - name: "id"
          in: "path"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/business/webhooks/{id}/rotate-secret":
    post:
      tags: ["Webhooks"]
      operationId: "rotateWebhookSecret"
      summary: "Rotate the signing secret"
      parameters:
        - name: "id"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: false
        content:
          "application/json":
            schema:
              type: "object"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/business/webhooks/{id}/test":
    post:
      tags: ["Webhooks"]
      operationId: "sendTestEvent"
      summary: "Send a test event to one endpoint"
      parameters:
        - name: "id"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              properties:
                event:
                  type: "string"
                  default: "test.ping"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/business/webhooks/deliveries":
    get:
      tags: ["Webhooks"]
      operationId: "listDeliveries"
      summary: "Delivery log"
      parameters:
        - name: "endpoint"
          in: "query"
          required: false
          schema:
            type: "string"
        - name: "status"
          in: "query"
          required: false
          schema:
            type: "string"
        - name: "event"
          in: "query"
          required: false
          schema:
            type: "string"
        - name: "before"
          in: "query"
          required: false
          schema:
            type: "string"
        - name: "limit"
          in: "query"
          required: false
          schema:
            type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
                properties:
                  deliveries:
                    type: "array"
                    items:
                      "$ref": "#/components/schemas/WebhookDelivery"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/business/webhooks/deliveries/{id}":
    get:
      tags: ["Webhooks"]
      operationId: "getDelivery"
      summary: "Delivery with payload and every attempt (headers, status, latency, response)"
      parameters:
        - name: "id"
          in: "path"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/business/webhooks/deliveries/{id}/replay":
    post:
      tags: ["Webhooks"]
      operationId: "replayDelivery"
      summary: "Replay a delivery (new delivery of the same payload)"
      parameters:
        - name: "id"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: false
        content:
          "application/json":
            schema:
              type: "object"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/business/widget-requests":
    get:
      tags: ["Widgets"]
      operationId: "listWidgetRequests"
      summary: "Booking-widget requests"
      parameters:
        - name: "status"
          in: "query"
          required: false
          schema:
            type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/business/widget-requests/{id}/approve":
    post:
      tags: ["Widgets"]
      operationId: "approveWidgetRequest"
      summary: "Approve and book (credit)"
      parameters:
        - name: "id"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: false
        content:
          "application/json":
            schema:
              type: "object"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/business/widget-requests/{id}/reject":
    post:
      tags: ["Widgets"]
      operationId: "rejectWidgetRequest"
      summary: "Decline a request"
      parameters:
        - name: "id"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              properties:
                reason:
                  type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/widget/config":
    get:
      tags: ["Widgets"]
      operationId: "widgetConfig"
      summary: "Widget config for a publishable key"
      security:
        - publishable: []
      parameters:
        - name: "key"
          in: "query"
          required: false
          schema:
            type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/widget/quote":
    post:
      tags: ["Widgets"]
      operationId: "widgetQuote"
      summary: "Delivery fee + ETA to an address"
      security:
        - publishable: []
      parameters:
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              required: ["to"]
              properties:
                key:
                  type: "string"
                to:
                  "$ref": "#/components/schemas/Stop"
                from:
                  "$ref": "#/components/schemas/Stop"
                vehicle:
                  "$ref": "#/components/schemas/Vehicle"
            example:
              to:
                address: "SS2, Petaling Jaya"
                lat: 3.118
                lng: 101.623
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
                properties:
                  vehicle:
                    type: "string"
                  totalSen:
                    type: "integer"
                  currency:
                    type: "string"
                  distanceKm:
                    type: "number"
                  durationMin:
                    type: "integer"
                  etaMin:
                    type: "integer"
                  sandbox:
                    type: "boolean"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/widget/requests":
    post:
      tags: ["Widgets"]
      operationId: "widgetRequest"
      summary: "Customer requests a delivery charged to the merchant"
      security:
        - publishable: []
      parameters:
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              required: ["to", "requester"]
              properties:
                key:
                  type: "string"
                from:
                  "$ref": "#/components/schemas/Stop"
                to:
                  "$ref": "#/components/schemas/Stop"
                vehicle:
                  "$ref": "#/components/schemas/Vehicle"
                requester:
                  type: "object"
                  required: ["name", "phone"]
                  properties:
                    name:
                      type: "string"
                    phone:
                      type: "string"
                note:
                  type: "string"
                scheduledAt:
                  type: "string"
      responses:
        "201":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/widget/requests/{id}":
    get:
      tags: ["Widgets"]
      operationId: "widgetRequestStatus"
      summary: "Request status (+ trackUrl once booked)"
      security:
        - publishable: []
      parameters:
        - name: "id"
          in: "path"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/sandbox":
    get:
      tags: ["Sandbox"]
      operationId: "sandboxInfo"
      summary: "Sandbox info for the caller"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/sandbox/orders/{code}/status":
    post:
      tags: ["Sandbox"]
      operationId: "forceSandboxStatus"
      summary: "Force a sandbox order to a status (fires every webhook in between)"
      parameters:
        - name: "code"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              required: ["status"]
              properties:
                status:
                  type: "string"
                  enum: ["assigned", "at_pickup", "picked_up", "in_transit", "delivered", "failed", "cancelled"]
                pause:
                  type: "boolean"
                note:
                  type: "string"
            example:
              status: "delivered"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/OrderDetail"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/driver/register":
    post:
      tags: ["Driver"]
      operationId: "driverRegister"
      summary: "Register as a driver"
      parameters:
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              properties:
                vehicleType:
                  "$ref": "#/components/schemas/Vehicle"
                plate:
                  type: "string"
                name:
                  type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/driver/online":
    post:
      tags: ["Driver"]
      operationId: "driverOnline"
      summary: "Go online / offline"
      parameters:
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              properties:
                online:
                  type: "boolean"
                lat:
                  type: "number"
                lng:
                  type: "number"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/driver/location":
    post:
      tags: ["Driver"]
      operationId: "driverLocation"
      summary: "Batch location pings"
      parameters:
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "array"
              items:
                type: "object"
                properties:
                  lat:
                    type: "number"
                  lng:
                    type: "number"
                  ts:
                    type: "integer"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/driver/offers/{orderId}/decline":
    post:
      tags: ["Driver"]
      operationId: "declineOffer"
      summary: "Decline an offer"
      parameters:
        - name: "orderId"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: false
        content:
          "application/json":
            schema:
              type: "object"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/driver/offers/{orderId}/accept":
    post:
      tags: ["Driver"]
      operationId: "acceptOffer"
      summary: "Accept an offer"
      parameters:
        - name: "orderId"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: false
        content:
          "application/json":
            schema:
              type: "object"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/driver/current":
    get:
      tags: ["Driver"]
      operationId: "driverCurrent"
      summary: "Current job"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/driver/orders/{id}/status":
    post:
      tags: ["Driver"]
      operationId: "driverStatus"
      summary: "Advance order status"
      parameters:
        - name: "id"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              properties:
                status:
                  type: "string"
                podRecipient:
                  type: "string"
                note:
                  type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/driver/orders/{id}/stops/{seq}/arrive":
    post:
      tags: ["Driver"]
      operationId: "driverArriveStop"
      summary: "Arrive at a stop"
      parameters:
        - name: "id"
          in: "path"
          required: true
          schema:
            type: "string"
        - name: "seq"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: false
        content:
          "application/json":
            schema:
              type: "object"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/driver/orders/{id}/stops/{seq}/complete":
    post:
      tags: ["Driver"]
      operationId: "driverCompleteStop"
      summary: "Complete or fail a stop"
      parameters:
        - name: "id"
          in: "path"
          required: true
          schema:
            type: "string"
        - name: "seq"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              properties:
                failed:
                  type: "boolean"
                failReason:
                  type: "string"
                podRecipient:
                  type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/driver/orders/{id}/plan":
    get:
      tags: ["Driver"]
      operationId: "driverGetPlan"
      summary: "Remaining stop plan"
      parameters:
        - name: "id"
          in: "path"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
    post:
      tags: ["Driver"]
      operationId: "driverReplan"
      summary: "Re-plan remaining stops"
      parameters:
        - name: "id"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: false
        content:
          "application/json":
            schema:
              type: "object"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/driver/orders/{id}/pod":
    post:
      tags: ["Driver"]
      operationId: "driverPod"
      summary: "Upload proof-of-delivery photo (multipart)"
      parameters:
        - name: "id"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: false
        content:
          "application/json":
            schema:
              type: "object"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/driver/wallet":
    get:
      tags: ["Driver"]
      operationId: "driverWallet"
      summary: "Earnings wallet"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/driver/board":
    get:
      tags: ["Driver"]
      operationId: "driverBoard"
      summary: "Open orders board"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/driver/board/{orderId}/take":
    post:
      tags: ["Driver"]
      operationId: "driverTake"
      summary: "Take an order from the board"
      parameters:
        - name: "orderId"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: false
        content:
          "application/json":
            schema:
              type: "object"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/driver/demand":
    get:
      tags: ["Driver"]
      operationId: "driverDemand"
      summary: "Demand map"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/driver/documents":
    get:
      tags: ["Driver"]
      operationId: "driverDocuments"
      summary: "Documents"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
    post:
      tags: ["Driver"]
      operationId: "driverUploadDocument"
      summary: "Upload a document (multipart)"
      parameters:
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: false
        content:
          "application/json":
            schema:
              type: "object"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/driver/bank":
    get:
      tags: ["Driver"]
      operationId: "driverBank"
      summary: "Bank details"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
    put:
      tags: ["Driver"]
      operationId: "driverSetBank"
      summary: "Set bank details"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              properties:
                bankName:
                  type: "string"
                accountNo:
                  type: "string"
                accountName:
                  type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/driver/payouts":
    get:
      tags: ["Driver"]
      operationId: "driverPayouts"
      summary: "Payouts"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/driver/sos":
    post:
      tags: ["Driver"]
      operationId: "driverSos"
      summary: "Raise an SOS"
      parameters:
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              properties:
                lat:
                  type: "number"
                lng:
                  type: "number"
                note:
                  type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/driver/orders/{id}/messages":
    get:
      tags: ["Driver"]
      operationId: "driverMessages"
      summary: "Chat messages"
      parameters:
        - name: "id"
          in: "path"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
    post:
      tags: ["Driver"]
      operationId: "driverSendMessage"
      summary: "Send chat message"
      parameters:
        - name: "id"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              properties:
                body:
                  type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/driver/orders/{id}/rate-customer":
    post:
      tags: ["Driver"]
      operationId: "driverRateCustomer"
      summary: "Rate the customer"
      parameters:
        - name: "id"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              properties:
                stars:
                  type: "integer"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/overview":
    get:
      tags: ["Admin"]
      operationId: "adminOverview"
      summary: "Today at a glance"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/analytics":
    get:
      tags: ["Admin"]
      operationId: "adminAnalytics"
      summary: "Platform analytics"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/orders":
    get:
      tags: ["Admin"]
      operationId: "adminOrders"
      summary: "Search / list orders"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/orders/{code}":
    get:
      tags: ["Admin"]
      operationId: "adminOrder"
      summary: "Order detail"
      parameters:
        - name: "code"
          in: "path"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/orders/{code}/status":
    post:
      tags: ["Admin"]
      operationId: "adminSetStatus"
      summary: "Force order status"
      parameters:
        - name: "code"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              additionalProperties: true
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/orders/{code}/redispatch":
    post:
      tags: ["Admin"]
      operationId: "adminRedispatch"
      summary: "Re-dispatch"
      parameters:
        - name: "code"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              additionalProperties: true
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/orders/{code}/stops/{seq}":
    patch:
      tags: ["Admin"]
      operationId: "adminEditStop"
      summary: "Edit a stop"
      parameters:
        - name: "code"
          in: "path"
          required: true
          schema:
            type: "string"
        - name: "seq"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              additionalProperties: true
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/orders/{code}/stops":
    post:
      tags: ["Admin"]
      operationId: "adminAddStop"
      summary: "Add a stop"
      parameters:
        - name: "code"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              additionalProperties: true
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/orders/{code}/stops/{seq}/cancel":
    post:
      tags: ["Admin"]
      operationId: "adminCancelStop"
      summary: "Cancel a stop"
      parameters:
        - name: "code"
          in: "path"
          required: true
          schema:
            type: "string"
        - name: "seq"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              additionalProperties: true
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/orders/{code}/stops/{seq}/reattempt":
    post:
      tags: ["Admin"]
      operationId: "adminReattemptStop"
      summary: "Re-attempt a stop"
      parameters:
        - name: "code"
          in: "path"
          required: true
          schema:
            type: "string"
        - name: "seq"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              additionalProperties: true
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/orders/{code}/stops/{seq}/status":
    post:
      tags: ["Admin"]
      operationId: "adminForceStop"
      summary: "Force-complete / fail a stop"
      parameters:
        - name: "code"
          in: "path"
          required: true
          schema:
            type: "string"
        - name: "seq"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              additionalProperties: true
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/orders/{code}/adjust":
    post:
      tags: ["Admin"]
      operationId: "adminAdjust"
      summary: "Price adjustment"
      parameters:
        - name: "code"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              additionalProperties: true
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/orders/{code}/support":
    get:
      tags: ["Admin"]
      operationId: "adminOrderSupport"
      summary: "Notes and tickets for an order"
      parameters:
        - name: "code"
          in: "path"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/orders/{code}/notes":
    post:
      tags: ["Admin"]
      operationId: "adminNote"
      summary: "Internal note"
      parameters:
        - name: "code"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              additionalProperties: true
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/tickets":
    get:
      tags: ["Admin"]
      operationId: "adminTickets"
      summary: "Support queue"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
    post:
      tags: ["Admin"]
      operationId: "adminCreateTicket"
      summary: "Open a ticket"
      parameters:
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              additionalProperties: true
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/tickets/{code}":
    get:
      tags: ["Admin"]
      operationId: "adminTicket"
      summary: "Ticket"
      parameters:
        - name: "code"
          in: "path"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
    patch:
      tags: ["Admin"]
      operationId: "adminUpdateTicket"
      summary: "Assign / change status"
      parameters:
        - name: "code"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              additionalProperties: true
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/tickets/{code}/reply":
    post:
      tags: ["Admin"]
      operationId: "adminReply"
      summary: "Reply"
      parameters:
        - name: "code"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              additionalProperties: true
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/routes/live":
    get:
      tags: ["Admin"]
      operationId: "adminLiveRoutes"
      summary: "Live route board"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/reports/stops":
    get:
      tags: ["Admin"]
      operationId: "adminStopReport"
      summary: "Stop reports"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/drivers":
    get:
      tags: ["Admin"]
      operationId: "adminDrivers"
      summary: "Drivers"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/drivers/live":
    get:
      tags: ["Admin"]
      operationId: "adminDriversLive"
      summary: "Online drivers"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/drivers/{id}/status":
    post:
      tags: ["Admin"]
      operationId: "adminDriverStatus"
      summary: "Approve / suspend"
      parameters:
        - name: "id"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              additionalProperties: true
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/businesses":
    post:
      tags: ["Admin"]
      operationId: "adminCreateBusiness"
      summary: "Create a business"
      parameters:
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              additionalProperties: true
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
    get:
      tags: ["Admin"]
      operationId: "adminBusinesses"
      summary: "Businesses"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/drivers/{id}/documents":
    get:
      tags: ["Admin"]
      operationId: "adminDriverDocs"
      summary: "Driver documents"
      parameters:
        - name: "id"
          in: "path"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/documents/{id}/status":
    post:
      tags: ["Admin"]
      operationId: "adminDocStatus"
      summary: "Review a document"
      parameters:
        - name: "id"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              additionalProperties: true
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/payouts/run":
    post:
      tags: ["Admin"]
      operationId: "adminRunPayouts"
      summary: "Run payouts"
      parameters:
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              additionalProperties: true
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/payouts":
    get:
      tags: ["Admin"]
      operationId: "adminPayouts"
      summary: "Payouts"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/payouts/{id}/paid":
    post:
      tags: ["Admin"]
      operationId: "adminPayoutPaid"
      summary: "Mark payout paid"
      parameters:
        - name: "id"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              additionalProperties: true
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/payouts/export.csv":
    get:
      tags: ["Admin"]
      operationId: "adminPayoutsCsv"
      summary: "Payouts CSV"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/invoices":
    get:
      tags: ["Admin"]
      operationId: "adminInvoices"
      summary: "Invoices"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/invoices/{businessId}/{month}/paid":
    post:
      tags: ["Admin"]
      operationId: "adminInvoicePaid"
      summary: "Mark invoice paid"
      parameters:
        - name: "businessId"
          in: "path"
          required: true
          schema:
            type: "string"
        - name: "month"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              additionalProperties: true
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/admins":
    post:
      tags: ["Admin"]
      operationId: "adminCreateAdmin"
      summary: "Add an ops admin"
      parameters:
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              additionalProperties: true
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/audit":
    get:
      tags: ["Admin"]
      operationId: "adminAudit"
      summary: "Audit log"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/sos":
    get:
      tags: ["Admin"]
      operationId: "adminSos"
      summary: "SOS alerts"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/admin/sos/{id}/resolve":
    post:
      tags: ["Admin"]
      operationId: "adminResolveSos"
      summary: "Resolve SOS"
      parameters:
        - name: "id"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              additionalProperties: true
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/super/settings":
    get:
      tags: ["Super"]
      operationId: "superSettings"
      summary: "Platform settings"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/super/settings/{key}":
    put:
      tags: ["Super"]
      operationId: "superSetSetting"
      summary: "Change a setting"
      parameters:
        - name: "key"
          in: "path"
          required: true
          schema:
            type: "string"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              additionalProperties: true
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/super/districts":
    get:
      tags: ["Super"]
      operationId: "superDistricts"
      summary: "Districts"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/super/admins":
    get:
      tags: ["Super"]
      operationId: "superAdmins"
      summary: "Admins"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
    post:
      tags: ["Super"]
      operationId: "superAddAdmin"
      summary: "Add admin"
      parameters:
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              additionalProperties: true
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/super/admins/{id}":
    patch:
      tags: ["Super"]
      operationId: "superUpdateAdmin"
      summary: "Update admin"
      parameters:
        - name: "id"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              additionalProperties: true
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
    delete:
      tags: ["Super"]
      operationId: "superRemoveAdmin"
      summary: "Remove admin"
      parameters:
        - name: "id"
          in: "path"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/super/finance":
    get:
      tags: ["Super"]
      operationId: "superFinance"
      summary: "Finance"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/super/system":
    get:
      tags: ["Super"]
      operationId: "superSystem"
      summary: "System health and queues"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/super/users":
    get:
      tags: ["Super"]
      operationId: "superUsers"
      summary: "Users"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/super/users/{id}":
    get:
      tags: ["Super"]
      operationId: "superUser"
      summary: "User detail"
      parameters:
        - name: "id"
          in: "path"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/super/users/{id}/logout":
    post:
      tags: ["Super"]
      operationId: "superLogoutUser"
      summary: "Force logout"
      parameters:
        - name: "id"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              additionalProperties: true
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/super/insights":
    get:
      tags: ["Super"]
      operationId: "superInsights"
      summary: "ETA insights"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/super/insights/rebuild":
    post:
      tags: ["Super"]
      operationId: "superRebuildInsights"
      summary: "Rebuild insights"
      parameters:
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              additionalProperties: true
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/super/promos":
    get:
      tags: ["Super"]
      operationId: "superPromos"
      summary: "Promo codes"
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
    post:
      tags: ["Super"]
      operationId: "superCreatePromo"
      summary: "Create promo"
      parameters:
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              additionalProperties: true
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
  "/v1/super/promos/{code}":
    patch:
      tags: ["Super"]
      operationId: "superUpdatePromo"
      summary: "Update promo"
      parameters:
        - name: "code"
          in: "path"
          required: true
          schema:
            type: "string"
        - "$ref": "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: "object"
              additionalProperties: true
      responses:
        "200":
          description: "OK"
          content:
            "application/json":
              schema:
                type: "object"
        default:
          "$ref": "#/components/responses/Error"
components:
  securitySchemes:
    bearer:
      type: "http"
      scheme: "bearer"
      description: "Secret API key (blink_live_… / blink_test_…) or a portal JWT."
    publishable:
      type: "http"
      scheme: "bearer"
      description: "Publishable key (pk_live_… / pk_test_…), quotes only, checked against the key domain allowlist. May also be sent as the key body field or ?key=."
  parameters:
    IdempotencyKey:
      name: "Idempotency-Key"
      in: "header"
      required: false
      description: "Replays the stored response for 24 h; same key with a different body = 422; still running = 409."
      schema:
        type: "string"
        maxLength: 255
  responses:
    Error:
      description: "Error"
      content:
        "application/json":
          schema:
            "$ref": "#/components/schemas/Error"
  schemas:
    Error:
      type: "object"
      required: ["error"]
      properties:
        error:
          type: "string"
          description: "Message, or \"validation\""
        issues:
          type: "array"
          items:
            type: "object"
            additionalProperties: true
    Stop:
      type: "object"
      required: ["address", "lat", "lng"]
      properties:
        address:
          type: "string"
          minLength: 3
          maxLength: 300
        lat:
          type: "number"
        lng:
          type: "number"
        contactName:
          type: "string"
        contactPhone:
          type: "string"
        notes:
          type: "string"
        kind:
          type: "string"
          description: "Default: first stop pickup, the rest drop-offs"
          enum: ["pickup", "dropoff"]
        ref:
          type: "string"
          description: "Your label for the recipient / shipment"
        windowStart:
          type: "string"
          description: "Arrive no earlier than"
          format: "date-time"
        windowEnd:
          type: "string"
          description: "Arrive no later than"
          format: "date-time"
        serviceMin:
          type: "integer"
          minimum: 0
          maximum: 240
        codAmountSen:
          type: "integer"
          minimum: 0
          maximum: 1000000
    Item:
      type: "object"
      required: ["name", "from", "to"]
      properties:
        name:
          type: "string"
        sku:
          type: "string"
        qty:
          type: "integer"
          minimum: 1
          default: 1
        weightKg:
          type: "number"
        notes:
          type: "string"
        from:
          type: "integer"
          description: "Index of the pickup stop"
        to:
          type: "integer"
          description: "Index of the drop-off stop"
    QuoteRequest:
      type: "object"
      required: ["vehicle", "stops"]
      properties:
        vehicle:
          "$ref": "#/components/schemas/Vehicle"
        stops:
          type: "array"
          items:
            "$ref": "#/components/schemas/Stop"
        addOns:
          type: "array"
          items:
            type: "string"
        optimize:
          type: "boolean"
        priorityFeeSen:
          type: "integer"
          minimum: 0
          maximum: 10000
        items:
          type: "array"
          items:
            "$ref": "#/components/schemas/Item"
        scheduledAt:
          type: "string"
          format: "date-time"
        promoCode:
          type: "string"
    CreateOrder:
      allOf:
        - "$ref": "#/components/schemas/QuoteRequest"
        - type: "object"
          required: ["paymentMethod"]
          properties:
            paymentMethod:
              type: "string"
              enum: ["online", "cod", "credit"]
            codAmountSen:
              type: "integer"
            externalRef:
              type: "string"
              description: "Your order number; repeating it returns the same order"
    Vehicle:
      type: "string"
      enum: ["motorcycle", "car", "pickup_4x4", "van", "lorry_1t", "lorry_10ft", "lorry_14ft", "lorry_17ft"]
    Quote:
      type: "object"
      properties:
        vehicle:
          "$ref": "#/components/schemas/Vehicle"
        total:
          type: "integer"
          description: "Sen"
        lines:
          type: "array"
          items:
            type: "object"
            properties:
              label:
                type: "string"
              amount:
                type: "integer"
        distanceKm:
          type: "number"
        durationMin:
          type: "integer"
        pickupEtaMin:
          type: ["integer", "null"]
        order:
          type: "array"
          items:
            type: "integer"
        stops:
          type: "array"
          items:
            "$ref": "#/components/schemas/Stop"
        schedule:
          type: "array"
          items:
            type: "object"
            additionalProperties: true
        lateStops:
          type: "integer"
        surge:
          type: "number"
        district:
          type: "string"
        routeSource:
          type: "string"
    Order:
      type: "object"
      properties:
        id:
          type: "string"
          format: "uuid"
        code:
          type: "string"
        external_ref:
          type: ["string", "null"]
        status:
          type: "string"
          enum: ["pending_payment", "searching", "assigned", "at_pickup", "picked_up", "in_transit", "delivered", "cancelled", "failed"]
        vehicle_type:
          "$ref": "#/components/schemas/Vehicle"
        fare_sen:
          type: "integer"
        payment_method:
          type: "string"
        cod_amount_sen:
          type: "integer"
        sandbox:
          type: "boolean"
        scheduled_at:
          type: ["string", "null"]
          format: "date-time"
        driver_id:
          type: ["string", "null"]
        track_token:
          type: "string"
        district:
          type: "string"
        created_at:
          type: "string"
          format: "date-time"
    OrderDetail:
      type: "object"
      properties:
        order:
          "$ref": "#/components/schemas/Order"
        stops:
          type: "array"
          items:
            type: "object"
            additionalProperties: true
        items:
          type: "array"
          items:
            type: "object"
            additionalProperties: true
        events:
          type: "array"
          items:
            type: "object"
            properties:
              status:
                type: "string"
              note:
                type: "string"
              at:
                type: "string"
                format: "date-time"
        adjustments:
          type: "array"
          items:
            type: "object"
            additionalProperties: true
        trackUrl:
          type: "string"
        geometry:
          type: ["array", "null"]
        eta:
          type: "object"
          additionalProperties: true
        duplicate:
          type: "boolean"
        checkoutUrl:
          type: "string"
    ApiKey:
      type: "object"
      properties:
        id:
          type: "string"
          format: "uuid"
        prefix:
          type: "string"
        name:
          type: "string"
        kind:
          type: "string"
          enum: ["secret", "publishable"]
        mode:
          type: "string"
          enum: ["live", "test"]
        scopes:
          type: "array"
          items:
            type: "string"
            enum: ["orders:read", "orders:write", "quotes", "webhooks", "reports"]
        lastUsedAt:
          type: ["string", "null"]
        lastUsedIp:
          type: ["string", "null"]
        expiresAt:
          type: ["string", "null"]
        ipAllowlist:
          type: "array"
          items:
            type: "string"
        domains:
          type: "array"
          items:
            type: "string"
        widget:
          type: "object"
          additionalProperties: true
        revokedAt:
          type: ["string", "null"]
        createdAt:
          type: "string"
          format: "date-time"
    ApiKeyCreate:
      type: "object"
      properties:
        name:
          type: "string"
          maxLength: 60
        kind:
          type: "string"
          enum: ["secret", "publishable"]
          default: "secret"
        mode:
          type: "string"
          enum: ["live", "test"]
          default: "live"
        scopes:
          type: "array"
          items:
            type: "string"
        expiresAt:
          type: "string"
          format: "date-time"
        expiresInDays:
          type: "integer"
          minimum: 1
          maximum: 3650
        ipAllowlist:
          type: "array"
          items:
            type: "string"
            description: "IP or IPv4 CIDR"
        domains:
          type: "array"
          items:
            type: "string"
            description: "Publishable keys: shop.example.my, *.example.my, localhost:3000"
        widget:
          type: "object"
          properties:
            pickup:
              "$ref": "#/components/schemas/Stop"
            vehicle:
              "$ref": "#/components/schemas/Vehicle"
            requireApproval:
              type: "boolean"
            maxFeeSen:
              type: "integer"
    WebhookEndpoint:
      type: "object"
      properties:
        id:
          type: "string"
          format: "uuid"
        url:
          type: "string"
        description:
          type: "string"
        events:
          type: "array"
          items:
            type: "string"
            description: "Event name, prefix.* or *; empty = all"
        mode:
          type: "string"
          enum: ["live", "test", "all"]
        active:
          type: "boolean"
        maxAttempts:
          type: "integer"
          minimum: 1
          maximum: 20
        legacy:
          type: "boolean"
        secret:
          type: "string"
        createdAt:
          type: "string"
          format: "date-time"
        stats:
          type: "object"
          additionalProperties: true
    WebhookEndpointCreate:
      type: "object"
      required: ["url"]
      properties:
        url:
          type: "string"
        description:
          type: "string"
        events:
          type: "array"
          items:
            type: "string"
        mode:
          type: "string"
          enum: ["live", "test", "all"]
          default: "all"
        active:
          type: "boolean"
        maxAttempts:
          type: "integer"
          default: 8
    WebhookDelivery:
      type: "object"
      properties:
        id:
          type: "string"
        endpointId:
          type: "string"
        url:
          type: "string"
        event:
          type: "string"
        status:
          type: "string"
          enum: ["pending", "delivered", "dead"]
        attempts:
          type: "integer"
        lastStatus:
          type: ["integer", "null"]
        lastLatencyMs:
          type: ["integer", "null"]
        lastError:
          type: ["string", "null"]
        nextAttemptAt:
          type: ["string", "null"]
        deliveredAt:
          type: ["string", "null"]
        sandbox:
          type: "boolean"
        replayOf:
          type: ["string", "null"]
        createdAt:
          type: "string"
          format: "date-time"
    WebhookEvent:
      type: "object"
      required: ["event", "data", "at"]
      properties:
        event:
          type: "string"
          enum: ["order.searching", "order.assigned", "order.at_pickup", "order.picked_up", "order.in_transit", "order.delivered", "order.cancelled", "order.failed", "stop.arrived", "stop.completed", "stop.failed", "stop.cancelled", "recurring.booked", "recurring.failed", "widget.requested", "test.ping"]
        data:
          type: "object"
          properties:
            code:
              type: "string"
            externalRef:
              type: "string"
            status:
              type: "string"
            driverId:
              type: "string"
            trackUrl:
              type: "string"
            sandbox:
              type: "boolean"
        at:
          type: "string"
          format: "date-time"
      example:
        event: "order.delivered"
        data:
          code: "BL3F9A21C0DE"
          externalRef: "WC-10421"
          status: "delivered"
          driverId: "0b6c…"
          trackUrl: "https://blink.example.my/t/9f2c…"
        at: "2026-10-22T08:15:30.000Z"
    Recurring:
      type: "object"
      properties:
        id:
          type: "string"
        name:
          type: "string"
        payload:
          "$ref": "#/components/schemas/CreateOrder"
        days:
          type: "array"
          items:
            type: "integer"
            description: "0 = Sunday"
        timeOfDay:
          type: "string"
          description: "HH:MM Malaysia time"
        active:
          type: "boolean"
        leadMin:
          type: "integer"
          description: "0 = book at run time; 30..10080 = book this far ahead as a scheduled order"
        bookAt:
          type: "string"
        nextRunAt:
          type: "string"
        lastRunAt:
          type: "string"
        lastOrderCode:
          type: "string"
        lastError:
          type: "string"
    RecurringCreate:
      type: "object"
      required: ["name", "payload", "days", "timeOfDay"]
      properties:
        name:
          type: "string"
        payload:
          "$ref": "#/components/schemas/CreateOrder"
        days:
          type: "array"
          items:
            type: "integer"
        timeOfDay:
          type: "string"
        active:
          type: "boolean"
        leadMin:
          type: "integer"
          default: 0
    PlanDay:
      type: "object"
      required: ["pickups", "drops"]
      properties:
        pickups:
          type: "array"
          items:
            "$ref": "#/components/schemas/Stop"
        drops:
          type: "array"
          items:
            "$ref": "#/components/schemas/Stop"
        vehicle:
          type: "string"
          description: "Vehicle type or auto"
        maxStopsPerRun:
          type: "integer"
          minimum: 2
          maximum: 20
        startAt:
          type: "string"
          format: "date-time"
        runs:
          type: "array"
          items:
            type: "array"
            items:
              type: "integer"
    Ok:
      type: "object"
      properties:
        ok:
          type: "boolean"
webhooks:
  orderEvent:
    post:
      summary: "Event delivered to your endpoint (signed: x-blink-signature, x-blink-signature-v2)"
      operationId: "webhookEvent"
      tags: ["Webhooks"]
      requestBody:
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/WebhookEvent"
      responses:
        "200":
          description: "Acknowledge with any 2xx within 8 s"
