> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ugift.me/llms.txt
> Use this file to discover all available pages before exploring further.

# Bulk Order

> Creates multiple orders in one request. Optional `Idempotency-Key` applies to the bulk operation.

**Responses**
- **201** when orders are created synchronously (or idempotent replay), with `orders`, `summary`, and optional `rejectedOrders`.
- **202** when async processing is enabled (`orderRequestId`, `status: queued`); poll **GET** `/api/v1/business/orders/order-requests/{id}`.
- **402** when the wallet balance (after holds) is insufficient for the bulk total.

**How to call** — **POST** `{server}/api/v1/business/orders/bulk` with `X-API-Key` and JSON body `{ "orders": [ ... ] }` (each item matches **CreateOrderRequest**).




## OpenAPI

````yaml /openapi-business.yaml post /api/v1/business/orders/bulk
openapi: 3.1.0
info:
  title: UGiftMe REST API
  description: >
    REST API for B2B integrations under `/api/v1/business`.


    **Authentication**

    - All documented endpoints require `X-API-Key` (and respect IP whitelisting
    when configured on the key).


    **Async orders**

    When enabled, `POST /orders` may return **202** with `orderRequestId` and
    `status: queued`. Poll `GET /orders/order-requests/{id}`.


    For narrative guides see the **API Guide** documentation page.


    **Error responses**

    Failures return JSON with at least an `error` string. Many endpoints add
    optional fields such as

    `message`, `reason`, `validEvents`, or `details` depending on context. There
    is no global

    `success` / `action` / `code` / `timestamp` envelope on business API routes.

    **`403 Forbidden`** is returned for IP allow-list blocks, incomplete
    business profiles on `/auth`,

    or when the authenticated account cannot access the requested resource.
  version: 1.0.0
  contact:
    name: UGiftMe Developer Support
    email: support@ugift.me
servers:
  - url: https://api-stage.ugift.me
    description: Sandbox
  - url: https://api.ugift.me
    description: Production
security:
  - ApiKeyAuth: []
tags:
  - name: Authentication
    description: API key validation
  - name: Product Catalog
    description: Catalog (API key)
  - name: Orders & Dispatch
    description: Create and list orders (API key)
  - name: Financials
    description: Balances and transactions (API key)
  - name: Webhooks
    description: Outbound webhook registration (API key)
paths:
  /api/v1/business/orders/bulk:
    post:
      tags:
        - Orders & Dispatch
      summary: Bulk Order
      description: >
        Creates multiple orders in one request. Optional `Idempotency-Key`
        applies to the bulk operation.


        **Responses**

        - **201** when orders are created synchronously (or idempotent replay),
        with `orders`, `summary`, and optional `rejectedOrders`.

        - **202** when async processing is enabled (`orderRequestId`, `status:
        queued`); poll **GET** `/api/v1/business/orders/order-requests/{id}`.

        - **402** when the wallet balance (after holds) is insufficient for the
        bulk total.


        **How to call** — **POST** `{server}/api/v1/business/orders/bulk` with
        `X-API-Key` and JSON body `{ "orders": [ ... ] }` (each item matches
        **CreateOrderRequest**).
      operationId: createBulkOrders
      parameters:
        - name: Idempotency-Key
          in: header
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - orders
              properties:
                orders:
                  type: array
                  items:
                    $ref: '#/components/schemas/CreateOrderRequest'
      responses:
        '201':
          description: Bulk orders created (sync path or idempotent replay)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BulkOrderCreatedResponse'
        '202':
          description: Queued (async)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/QueuedOrderResponse'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          description: Insufficient wallet balance for the bulk total
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InsufficientFundsResponse'
        '403':
          $ref: '#/components/responses/AccessDenied'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
components:
  schemas:
    CreateOrderRequest:
      type: object
      required:
        - productCode
        - merchant
        - currency
        - valuePurchased
      properties:
        productCode:
          type: string
        merchant:
          type: string
        currency:
          type: string
          example: GBP
        valuePurchased:
          type: number
        recipientEmail:
          type: string
          format: email
        recipientName:
          type: string
        giftMessage:
          type: string
    BulkOrderCreatedResponse:
      type: object
      required:
        - success
        - orders
        - summary
      properties:
        success:
          type: boolean
          example: true
        orders:
          type: array
          items:
            $ref: '#/components/schemas/BusinessOrder'
        summary:
          type: object
          required:
            - totalOrders
            - totalValue
            - currency
            - remainingBalance
          properties:
            totalOrders:
              type: integer
              example: 2
            totalValue:
              type: number
              example: 75
            currency:
              type: string
              example: GBP
            remainingBalance:
              type: number
              example: 1925.5
        rejectedOrders:
          type: array
          items:
            type: object
            required:
              - productCode
              - reason
            properties:
              productCode:
                type: string
              reason:
                type: string
      example:
        success: true
        orders:
          - _id: 65b0d1e2f3a4b56789012345
            productCode: AMAZON_UK_STD
            merchant:
              name: Amazon UK
            currency: GBP
            valuePurchased: 25
            status: active
            imageUrl: https://cdn.ugift.me/products/amazon-uk-card.png
          - _id: 65b0d1e2f3a4b56789012346
            productCode: JL_UK_STD
            merchant:
              name: John Lewis
            currency: GBP
            valuePurchased: 50
            status: active
            imageUrl: https://cdn.ugift.me/products/johnlewis-card.png
        summary:
          totalOrders: 2
          totalValue: 75
          currency: GBP
          remainingBalance: 1925.5
        rejectedOrders:
          - productCode: UNKNOWN_CODE
            reason: Product not found
    QueuedOrderResponse:
      type: object
      required:
        - orderRequestId
        - status
      properties:
        orderRequestId:
          type: string
          example: 65e4f5b6c7d8e90123456789
        status:
          type: string
          example: queued
        message:
          type: string
          example: Order queued for processing
      example:
        orderRequestId: 65e4f5b6c7d8e90123456789
        status: queued
        message: Order queued for processing
    InsufficientFundsResponse:
      type: object
      required:
        - error
        - required
        - available
        - currency
      properties:
        error:
          type: string
          example: Insufficient funds
        required:
          type: number
          description: Amount required (single order or bulk total).
          example: 100
        available:
          type: number
          description: Spendable balance after active wallet holds.
          example: 42.5
        currency:
          type: string
          example: GBP
      example:
        error: Insufficient funds
        required: 100
        available: 42.5
        currency: GBP
    BusinessOrder:
      type: object
      description: >
        Account-facing order shape. List/search responses include `imageUrl`
        from `withImageUrl`; synchronous **201** creates may include populated
        `product` instead of `imageUrl`.
      properties:
        _id:
          type: string
          example: 65b0d1e2f3a4b56789012345
        productCode:
          type: string
          example: AMAZON_UK_STD
        product:
          $ref: '#/components/schemas/BusinessProductSnippet'
        merchant:
          type: object
          additionalProperties: true
        currency:
          type: string
          example: GBP
        valuePurchased:
          type: number
          example: 50
        status:
          type: string
          example: active
        isPaid:
          type: boolean
          example: true
        isActive:
          type: boolean
          example: true
        isActivated:
          type: boolean
          example: false
        usageLimit:
          type: number
          example: 1
        giftOrder:
          type: object
          additionalProperties: true
        transactionId:
          type: string
          example: txn_01HZZ9K2M4P8Q6R7S8T9V0W1X
        createdAt:
          type: string
          format: date-time
          example: '2025-04-12T14:22:10.000Z'
        updatedAt:
          type: string
          format: date-time
          example: '2025-04-12T14:22:11.000Z'
        imageUrl:
          type: string
          example: https://cdn.ugift.me/products/amazon-uk-card.png
        previousUserId:
          type: string
          description: Present on some gift/recipient flows.
      example:
        _id: 65b0d1e2f3a4b56789012345
        productCode: AMAZON_UK_STD
        merchant:
          name: Amazon UK
          distributionMethod: EMAIL
          voucherCode: XXXX-XXXX-XXXX-XXXX
          redemptionUrl: https://www.amazon.co.uk/gc/redeem
        currency: GBP
        valuePurchased: 50
        status: active
        isPaid: true
        isActive: true
        usageLimit: 1
        giftOrder:
          message: Thanks for being a great customer!
          recipientEmail: alex@example.com
          recipientName: Alex
        transactionId: txn_01HZZ9K2M4P8Q6R7S8T9V0W1X
        createdAt: '2025-04-12T14:22:10.000Z'
        updatedAt: '2025-04-12T14:22:11.000Z'
        imageUrl: https://cdn.ugift.me/products/amazon-uk-card.png
    ValidationErrorBody:
      description: >
        Client or validation failure. Always includes `error`; optional
        `message` and endpoint-specific fields.
      oneOf:
        - $ref: '#/components/schemas/ApiErrorWithMessage'
        - $ref: '#/components/schemas/WebhookValidationError'
    UnauthorizedErrorBody:
      description: Missing or invalid `X-API-Key`.
      oneOf:
        - $ref: '#/components/schemas/UnauthorizedMissingApiKey'
        - $ref: '#/components/schemas/UnauthorizedInvalidApiKey'
    AccessDeniedError:
      type: object
      description: >
        Access denied — IP not on the key allow list, incomplete/unverified
        business profile on GET /auth,

        or insufficient permission to the resource (e.g. webhook owned by
        another account).
      required:
        - error
      properties:
        error:
          type: string
        ip:
          type: string
          description: Caller IP when blocked by the key allow list
        whitelistedIps:
          type: array
          items:
            type: string
        status:
          type: string
          description: Business verification or profile status (`GET /auth`)
        missingFields:
          type: array
          items:
            type: string
        message:
          type: string
      additionalProperties: true
    RateLimitError:
      type: object
      required:
        - error
      properties:
        error:
          type: string
          description: >
            Rate-limit message depends on the route limiter (products, webhooks,
            wallet, etc.).
      example:
        error: Too many product access attempts, please try again later.
    InternalServerError:
      type: object
      required:
        - error
      properties:
        error:
          type: string
          description: Route-specific failure summary
        message:
          type: string
          description: Additional detail (common on webhook and wallet routes)
        details:
          type: string
          description: Additional detail (used on some product catalog routes)
      example:
        error: An error occurred
        message: Optional server detail
    BusinessProductSnippet:
      type: object
      description: Populated `product` on synchronous **201** order responses.
      properties:
        imageUrl:
          type: string
          example: https://cdn.ugift.me/products/amazon-uk-card.png
        merchant:
          type: string
          example: Amazon UK
        productCode:
          type: string
          example: AMAZON_UK_STD
      example:
        imageUrl: https://cdn.ugift.me/products/amazon-uk-card.png
        merchant: Amazon UK
        productCode: AMAZON_UK_STD
    ApiErrorWithMessage:
      type: object
      description: Validation and client errors often add a `message` with more context.
      required:
        - error
      properties:
        error:
          type: string
        message:
          type: string
          description: Additional detail about the failure
      example:
        error: Some required fields are missing
        message: >-
          All fields are required: productCode, merchant, currency,
          valuePurchased
    WebhookValidationError:
      type: object
      required:
        - error
      properties:
        error:
          type: string
        message:
          type: string
        validEvents:
          type: array
          items:
            type: string
      example:
        error: Invalid event types
        message: events must be a non-empty array
        validEvents:
          - order.queued
          - order.processing
          - order.succeeded
          - order.failed
          - product.updated
          - wallet.updated
    UnauthorizedMissingApiKey:
      type: object
      required:
        - error
      properties:
        error:
          type: string
          example: Missing API key
    UnauthorizedInvalidApiKey:
      type: object
      required:
        - error
      properties:
        error:
          type: string
          example: Invalid API key
        reason:
          type: string
          description: Why the key failed validation (when available)
          example: Key has been revoked
  responses:
    ValidationError:
      description: >
        Validation error — required fields missing, invalid format, or business
        rule failure.

        Always includes `error`; many responses also include `message` and
        endpoint-specific fields.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ValidationErrorBody'
          examples:
            orderMissingFields:
              summary: POST /orders — required body fields
              value:
                error: Some required fields are missing
                message: >-
                  All fields are required: productCode, merchant, currency,
                  valuePurchased
            orderIdempotencyConflict:
              summary: POST /orders — idempotency key reuse
              value:
                error: Some required fields are missing
                message: Idempotency-Key reuse with different payload is not allowed
            orderCurrencyMismatch:
              summary: POST /orders — currency mismatch
              value:
                error: Product currency does not match original order currency
                message: Order currency (USD) does not match product currency (GBP)
            webhookInvalidUrl:
              summary: POST /webhooks — invalid URL
              value:
                error: Invalid webhook URL
            webhookInvalidEvents:
              summary: POST /webhooks — invalid events
              value:
                error: Invalid event types
                message: events must be a non-empty array
                validEvents:
                  - order.queued
                  - order.processing
                  - order.succeeded
                  - order.failed
                  - product.updated
                  - wallet.updated
            walletInvalidCurrency:
              summary: GET /wallet/transactions — invalid currency filter
              value:
                error: Some required fields are missing
                message: 'Currency must be one of: GBP, USD, EUR, NGN'
    Unauthorized:
      description: >
        Authentication failed — missing, invalid, or (for some routes)
        unresolvable API key context.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/UnauthorizedErrorBody'
          examples:
            missingApiKey:
              summary: No X-API-Key header
              value:
                error: Missing API key
            invalidApiKey:
              summary: Key failed validation
              value:
                error: Invalid API key
                reason: Key has been revoked
    AccessDenied:
      description: >
        Access denied (HTTP 403). Common causes: caller IP not on the key allow
        list, business profile incomplete

        or unverified on GET /auth, or the authenticated account cannot access
        the resource

        (e.g. another account's webhook).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/AccessDeniedError'
          example:
            error: IP not whitelisted
            ip: 203.0.113.10
            whitelistedIps:
              - 198.51.100.0/24
          examples:
            ipNotWhitelisted:
              summary: IP allow list (products, wallet, orders, webhooks)
              value:
                error: IP not whitelisted
                ip: 203.0.113.10
                whitelistedIps:
                  - 198.51.100.0/24
            incompleteBusinessProfile:
              summary: GET /auth — profile incomplete
              value:
                error: Incomplete business profile
                status: incomplete
                missingFields:
                  - businessPhoneNumber
                  - registrationNumber
            businessNotVerified:
              summary: GET /auth — verification pending
              value:
                error: Business verification pending
                status: pending
                message: Complete business verification process
            webhookAccessDenied:
              summary: PATCH or DELETE /webhooks/{id} — wrong account
              value:
                error: You are forbidden from accessing this resource
    RateLimited:
      description: >
        Rate limit exceeded for the route's limiter. Response body is `{
        "error": "<message>" }`.

        Standard rate-limit headers are included when the limiter supports them.
      headers:
        retry-after:
          description: Seconds to wait before retrying
          schema:
            type: integer
            example: 60
        RateLimit-Limit:
          description: Maximum requests allowed in the window
          schema:
            type: integer
            example: 100
        RateLimit-Remaining:
          description: Remaining requests in the current window
          schema:
            type: integer
            example: 0
        RateLimit-Reset:
          description: Unix timestamp when the window resets
          schema:
            type: integer
            example: 1713187200
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/RateLimitError'
          examples:
            productCatalog:
              summary: Product catalog limiter
              value:
                error: Too many product access attempts, please try again later.
            webhooks:
              summary: Webhook configuration limiter
              value:
                error: >-
                  Too many webhook configuration attempts, please try again
                  later.
            wallet:
              summary: Wallet / sensitive-operation limiter
              value:
                error: Too many sensitive operations, please try again later.
    InternalError:
      description: >
        Unexpected server error. Usually `{ "error": "An error occurred" }` from
        the business router,

        or a route-specific failure message with optional `message` / `details`.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/InternalServerError'
          examples:
            generic:
              summary: Business router fallback
              value:
                error: An error occurred
                message: An error occurred
            products:
              summary: GET /products failure
              value:
                error: Failed to fetch one or more products
                message: Connection timeout
            productFilters:
              summary: GET /products/filters failure
              value:
                error: Failed to fetch filters
                details: Connection timeout
            webhooks:
              summary: POST /webhooks failure
              value:
                error: Failed to register webhook
                message: Database error
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: |
        Your UGiftMe API key for authentication.

        **Format**: Secure API key (e.g., `<your-api-key>`)

        **Required**: All endpoints require this header

        Example: `X-API-Key: <your-api-key>`

````