openapi: 3.0.3
info:
  title: Miguel WooCommerce API
  version: "1"
  description: >
    REST API exposed by the Miguel WooCommerce plugin under the `/wp-json/miguel/v1` namespace.
    All endpoints require bearer-token authentication.

servers:
  - url: https://{domain}/wp-json/miguel/v1
    variables:
      domain:
        default: example.com
        description: WordPress site domain

security:
  - bearerAuth: []

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: API token configured in the Miguel plugin settings.

  schemas:
    Error:
      type: object
      properties:
        code:
          type: string
          example: argument.missing
        message:
          type: string
          example: The updated_since parameter is required.
        data:
          type: object
          properties:
            status:
              type: integer
              example: 400

    ZoneLocation:
      type: object
      properties:
        type:
          type: string
          description: Location type.
          enum: [country, state, postcode, continent]
          example: country
        code:
          type: string
          description: ISO code for the location (e.g. country code, state code).
          example: CZ

    ShippingMethod:
      type: object
      properties:
        instance_id:
          type: integer
          description: Unique instance ID of this shipping method within WooCommerce.
          example: 3
        method_id:
          type: string
          description: WooCommerce shipping method identifier.
          example: flat_rate
        title:
          type: string
          description: Display title configured by the merchant.
          example: Flat Rate
        description:
          type: string
          description: Method description from WooCommerce.
          example: Lets you charge a fixed rate for shipping.
        enabled:
          type: boolean
          description: Whether the method is enabled.
          example: true
        currency:
          type: string
          description: ISO 4217 store currency code the monetary values are expressed in.
          example: CZK
        cost:
          type: string
          description: >
            Shipping cost. Taken from the method's `cost` setting (flat_rate and methods
            following the same convention). When that setting is empty, the method is asked
            to calculate its rates for an empty package addressed to the zone, and the cost
            of its first rate is reported instead — this is how carriers that keep their
            pricing outside WooCommerce (e.g. Toret Balíkovna) are priced. Empty string when
            the method is disabled, produces no rate, or fails to calculate one.
            Excludes tax. See the endpoint description for the caveats.
          example: "5.00"
        min_amount:
          type: string
          description: Minimum order amount required for free shipping. Empty string when not applicable.
          example: "50.00"
        free_shipping:
          type: string
          description: free_shipping option value on the method. Empty string when not set.
          example: ""
        requires:
          type: string
          description: >
            Condition required to qualify for free shipping (free_shipping method).
            One of: '' (none), 'coupon', 'min_amount', 'either', 'both'.
            Empty string when not applicable.
          example: min_amount
        ignore_discounts:
          type: string
          description: >
            Whether to ignore discounts when checking the min_amount condition
            (free_shipping method). 'yes' or 'no'. Empty string when not applicable.
          example: "yes"

    Zone:
      type: object
      properties:
        id:
          type: integer
          description: WooCommerce shipping zone ID. 0 = Rest of World.
          example: 1
        name:
          type: string
          description: Shipping zone name.
          example: Europe
        locations:
          type: array
          items:
            $ref: '#/components/schemas/ZoneLocation'
          description: Countries/states/postcodes this zone applies to. Empty for Rest of World.
        methods:
          type: array
          items:
            $ref: '#/components/schemas/ShippingMethod'

    OrderAddress:
      type: object
      properties:
        first_name:
          type: string
        last_name:
          type: string
        company:
          type: string
        address_1:
          type: string
        address_2:
          type: string
        city:
          type: string
        state:
          type: string
        postcode:
          type: string
        country:
          type: string
          description: Full country name.
          example: Czech Republic
        email:
          type: string
          format: email
        phone:
          type: string

    OrderUser:
      type: object
      properties:
        id:
          type: integer
          nullable: true
          description: WordPress user ID. Null for guest orders.
        email:
          type: string
          format: email
        full_name:
          type: string
        address:
          $ref: '#/components/schemas/OrderAddress'
        lang:
          type: string
          nullable: true
          description: Customer locale/language.

    OrderProductPrice:
      type: object
      properties:
        sold_without_vat:
          type: number
          format: float
          description: Line item total excluding tax and rounding.

    OrderProduct:
      type: object
      properties:
        code:
          type: string
          description: >
            Miguel product code for the line. Derived from the downloadable file
            shortcode for digital items, or from the product SKU plus the configured
            printed-book suffix for printed books.
          example: ABC123
        price:
          $ref: '#/components/schemas/OrderProductPrice'

    Order:
      type: object
      properties:
        id:
          type: string
          description: WooCommerce order ID as a string.
          example: "42"
        status:
          type: string
          description: WooCommerce order status slug (without wc- prefix).
          example: processing
        currency_code:
          type: string
          description: ISO 4217 currency code.
          example: CZK
        paid:
          type: boolean
          description: Whether the order has been paid.
        purchase_date:
          type: string
          format: date-time
          nullable: true
          description: Date the order was placed (ISO 8601).
        update_date:
          type: string
          format: date-time
          nullable: true
          description: Date the order was last modified (ISO 8601).
        user:
          $ref: '#/components/schemas/OrderUser'
        products:
          type: array
          items:
            $ref: '#/components/schemas/OrderProduct'
          description: >
            Line items whose product exposes a Miguel code are included: digital items
            with a Miguel shortcode, and printed books when a printed-book suffix is
            configured. Items with no Miguel code are omitted.

    OrderLineItem:
      type: object
      properties:
        product_id:
          type: integer
          example: 15
        name:
          type: string
          example: My eBook
        sku:
          type: string
        quantity:
          type: integer
          example: 1
        total:
          type: string
          description: Line total excluding tax (store currency).
          example: "199.00"
        tax:
          type: string
          description: Line tax total (store currency).
          example: "0.00"
        code:
          type: string
          nullable: true
          description: >
            First Miguel product code on the line — a digital shortcode code, or a
            printed book's SKU plus the configured suffix — or null when the line has
            no Miguel code. Full code+price detail is in the order's products array.
          example: "9788024271101"

    OrderShippingAddress:
      type: object
      properties:
        first_name:
          type: string
        last_name:
          type: string
        company:
          type: string
        address_1:
          type: string
        address_2:
          type: string
        city:
          type: string
        state:
          type: string
        postcode:
          type: string
        country:
          type: string
          description: ISO 3166-1 alpha-2 country code.
        phone:
          type: string
          description: Empty string on WooCommerce versions without shipping-phone support.

    OrderDetail:
      description: >
        Richer single-order view returned by GET /orders/{id}. Includes all
        properties of Order plus totals, structured addresses, payment/shipping
        metadata, and a full line-item breakdown.
      allOf:
        - $ref: '#/components/schemas/Order'
        - type: object
          properties:
            total:
              type: string
              example: "228.00"
            subtotal:
              type: string
              example: "199.00"
            total_tax:
              type: string
              example: "0.00"
            shipping_total:
              type: string
              example: "29.00"
            discount_total:
              type: string
              example: "0.00"
            billing:
              $ref: '#/components/schemas/BillingAddress'
            shipping:
              $ref: '#/components/schemas/OrderShippingAddress'
            payment_method:
              type: string
              example: bacs
            payment_method_title:
              type: string
              example: Direct Bank Transfer
            transaction_id:
              type: string
            shipping_lines:
              type: array
              items:
                $ref: '#/components/schemas/ShippingLine'
            customer_note:
              type: string
            line_items:
              type: array
              items:
                $ref: '#/components/schemas/OrderLineItem'

    MiguelItem:
      type: object
      properties:
        book_id:
          type: string
          description: >
            Miguel book ID. From the shortcode for digital items; the product SKU
            for printed books.
          example: "9788024271101"
        format:
          type: string
          description: >
            Item format. For digital items, the file format from the shortcode
            (e.g. epub, mobi, pdf, audio) or an empty string if unspecified.
            "print" for printed (non-downloadable) books.
          example: epub
        code:
          type: string
          description: >
            Exact Miguel product code used to reference this item on order line
            items. For digital items this equals book_id; for printed books it is
            the SKU plus the configured printed-book suffix (e.g. "harry-potter:print").
          example: "9788024271101"

    Product:
      type: object
      properties:
        id:
          type: integer
          example: 15
        name:
          type: string
          example: My eBook
        status:
          type: boolean
          description: True when the product is published.
        type:
          type: string
          description: WooCommerce product type (simple, variable, variation, etc.).
          example: simple
        sku:
          type: string
        currency:
          type: string
          example: CZK
        price:
          type: number
          format: float
          nullable: true
          description: Current price including tax.
        price_without_tax:
          type: number
          format: float
          nullable: true
        tax:
          type: number
          format: float
          nullable: true
          description: Total tax rate percentage.
        tax_total:
          type: number
          format: float
          nullable: true
        in_stock:
          type: boolean
        stock_status:
          type: string
          example: instock
        manage_stock:
          type: boolean
        stock_quantity:
          type: integer
          nullable: true
          description: Null when manage_stock is false.
        backorders_allowed:
          type: string
          description: WooCommerce backorders setting (no, notify, yes).
          example: "no"
        sold_individually:
          type: boolean
        regular_price:
          type: number
          format: float
          nullable: true
        sale_price:
          type: number
          format: float
          nullable: true
        parent_id:
          type: integer
          description: Parent product ID for variations. 0 for top-level products.
        miguel_items:
          type: array
          items:
            $ref: '#/components/schemas/MiguelItem'
          description: >
            Miguel items exposed by the product: shortcodes found in a downloadable
            product's files, plus — when a printed-book suffix is configured — a
            "print" item for a non-downloadable product, derived from its SKU.

    ProductCodeDetail:
      type: object
      properties:
        product_id:
          type: integer
        product_ids:
          type: array
          items:
            type: integer
          description: All product IDs that share this code (useful when is_unique is false).
        is_unique:
          type: boolean
          description: False when multiple products share the same code.

    LineItem:
      type: object
      required: [quantity]
      properties:
        product_id:
          type: integer
          description: WooCommerce product ID. Required when productCode is not supplied.
        productCode:
          type: string
          description: >
            Miguel product code. Resolved to product_id before passing to WooCommerce.
            Accepts a digital shortcode code, a printed book's SKU plus the configured
            suffix, or a bare product SKU. Use either product_id or productCode, not both
            (unless they refer to the same product).
        product_code:
          type: string
          description: Snake-case alias for productCode.
        quantity:
          type: integer
          minimum: 1
          example: 1

    BillingAddress:
      type: object
      properties:
        first_name:
          type: string
        last_name:
          type: string
        company:
          type: string
        address_1:
          type: string
        address_2:
          type: string
        city:
          type: string
        state:
          type: string
        postcode:
          type: string
        country:
          type: string
          description: ISO 3166-1 alpha-2 country code.
        email:
          type: string
          format: email
        phone:
          type: string

    ShippingAddress:
      type: object
      properties:
        first_name:
          type: string
        last_name:
          type: string
        company:
          type: string
        address_1:
          type: string
        address_2:
          type: string
        city:
          type: string
        state:
          type: string
        postcode:
          type: string
        country:
          type: string

    ShippingLine:
      type: object
      properties:
        method_id:
          type: string
          example: flat_rate
        method_title:
          type: string
          example: Flat Rate
        total:
          type: string
          example: "5.00"

    CreateOrderRequest:
      type: object
      required: [payment_method, billing, shipping, shipping_lines, line_items]
      properties:
        idempotency_key:
          type: string
          description: >
            Idempotency key for safe retries. Can also be sent via the
            Idempotency-Key or X-Idempotency-Key request header.
          example: "order-2024-abc123"
        payment_method:
          type: string
          description: WooCommerce payment method ID.
          example: bacs
        payment_method_title:
          type: string
          example: Direct Bank Transfer
        customer_id:
          type: integer
          description: >
            WordPress user ID. When valid, the order is linked to that user.
            If missing or invalid, the API falls back to `user_email` and
            links the order to a matching user when one exists.
        user_email:
          type: string
          format: email
          description: >
            Top-level customer email used as the fallback for user matching.
        status:
          type: string
          description: Initial order status. Defaults to WooCommerce default (pending).
          example: processing
        billing:
          $ref: '#/components/schemas/BillingAddress'
        shipping:
          $ref: '#/components/schemas/ShippingAddress'
        shipping_lines:
          type: array
          items:
            $ref: '#/components/schemas/ShippingLine'
          minItems: 1
        line_items:
          type: array
          items:
            $ref: '#/components/schemas/LineItem'
          minItems: 1
        send_emails:
          type: boolean
          description: >
            Send standard WooCommerce order notification emails after creation.
            Ignored when email_template is set.
        send_email:
          type: boolean
          description: Alias for send_emails.
        email_template:
          type: string
          description: >
            Send a specific WooCommerce email template instead of the default set.
            Setting this implies send_emails=true.
          enum:
            - new_order
            - customer_invoice
            - customer_on_hold_order
            - customer_processing_order
            - customer_completed_order
            - customer_failed_order

    CreateOrderResponse:
      type: object
      description: >
        WooCommerce order object (as returned by WC REST Orders controller)
        with additional Miguel fields merged in.
      properties:
        id:
          type: integer
        status:
          type: string
        idempotent_replay:
          type: boolean
          description: True when the response was served from a previous idempotent result.
        debug_log_path:
          type: string
          nullable: true
          description: Server-side path to the Miguel debug log file.
        emails_requested:
          type: boolean
        emails_dispatched:
          type: boolean
        email_template:
          type: string
          nullable: true

    UpdateOrderStatusRequest:
      type: object
      required: [status]
      properties:
        status:
          type: string
          description: >
            Target WooCommerce order status slug (without wc- prefix), or `paid`
            to trigger native WooCommerce `payment_complete()`.
          example: completed

    UpdateOrderStatusResponse:
      type: object
      properties:
        id:
          type: integer
          description: WooCommerce order ID.
        status:
          type: string
          description: Current WooCommerce order status slug.
        idempotent_replay:
          type: boolean
          description: True when the response was served from a previous idempotent result.

paths:
  /delivery-methods:
    get:
      summary: List shipping methods grouped by zone
      operationId: getDeliveryMethods
      description: >
        Returns all configured WooCommerce shipping methods grouped by shipping zone.
        Zones with no methods are omitted. Zone 0 (Rest of World) is appended last
        when it has methods.


        A method whose `cost` setting is empty is priced by asking it to calculate its
        rates, which is the only way to read the price of carriers that store their pricing
        in their own plugin settings rather than in WooCommerce. Because a REST request has
        no cart, the package used is empty and addressed to the zone's first country
        location (the store base country for zone 0). Carriers that scale their price by
        weight, dimensions or order value therefore report their baseline tier, and any
        free-shipping threshold or cash-on-delivery surcharge is not reflected. Methods
        returning several rates report the first one.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  count:
                    type: integer
                    description: Number of zones returned.
                  zones:
                    type: array
                    items:
                      $ref: '#/components/schemas/Zone'
        "401":
          description: Missing bearer token.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "403":
          description: Invalid bearer token.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /orders:
    get:
      summary: List orders modified since a given date
      operationId: getOrders
      description: >
        Returns all WooCommerce orders whose modification date is on or after
        `updated_since`, ordered by modification date ascending.
        Line items whose product exposes a Miguel code — digital shortcode items,
        and printed books when a printed-book suffix is configured — are included in
        each order's `products` array.
      parameters:
        - in: query
          name: updated_since
          required: true
          schema:
            type: string
          description: >
            Date/time threshold. Any value parseable by PHP `strtotime()` is accepted
            (ISO 8601, RFC 2822, Unix timestamp, relative strings like "yesterday", etc.).
          example: "2024-01-01T00:00:00Z"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  count:
                    type: integer
                  orders:
                    type: array
                    items:
                      $ref: '#/components/schemas/Order'
        "400":
          description: Missing or unparseable updated_since parameter.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                missing:
                  value:
                    code: argument.missing
                    message: The updated_since parameter is required.
                    data:
                      status: 400
                invalid:
                  value:
                    code: argument.invalid
                    message: The updated_since parameter is not a valid date.
                    data:
                      status: 400
        "401":
          description: Missing bearer token.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "403":
          description: Invalid bearer token.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    post:
      summary: Create an order
      operationId: createOrder
      description: >
        Creates a WooCommerce order by delegating to the official WC REST Orders controller.
        Supports idempotency: repeat the same request with the same `idempotency_key` (or
        `Idempotency-Key` / `X-Idempotency-Key` header) to safely retry without creating
        duplicate orders.


        Line items may reference products by `product_id` (WooCommerce ID) or `productCode`
        (a Miguel code: a digital shortcode code, a printed book's SKU plus the configured
        printed-book suffix, or a bare product SKU). When `productCode` is supplied it is
        resolved to a `product_id` before the order is created.
      parameters:
        - in: header
          name: Idempotency-Key
          schema:
            type: string
          description: Idempotency key (alternative to body field).
        - in: header
          name: X-Idempotency-Key
          schema:
            type: string
          description: Idempotency key (alternative to body field).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateOrderRequest'
            example:
              idempotency_key: "order-2024-abc123"
              payment_method: bacs
              payment_method_title: Direct Bank Transfer
              status: processing
              billing:
                first_name: Jan
                last_name: Novák
                email: jan@example.com
                address_1: Václavské náměstí 1
                city: Praha
                postcode: "11000"
                country: CZ
              shipping:
                first_name: Jan
                last_name: Novák
                address_1: Václavské náměstí 1
                city: Praha
                postcode: "11000"
                country: CZ
              shipping_lines:
                - method_id: flat_rate
                  method_title: Flat Rate
                  total: "5.00"
              line_items:
                - productCode: ABC123
                  quantity: 1
      responses:
        "201":
          description: Order created successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateOrderResponse'
        "200":
          description: Idempotent replay — order already existed with this key.
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: integer
                  status:
                    type: string
                  idempotent_replay:
                    type: boolean
                    example: true
        "400":
          description: Validation error (missing required fields, invalid line items, etc.).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "401":
          description: Missing bearer token.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "403":
          description: Invalid bearer token.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /orders/{id}:
    get:
      summary: Get a single order by ID
      operationId: getOrder
      description: >
        Returns a single WooCommerce order by ID with a richer detail view: the
        same base fields as a list item plus order totals, structured
        billing/shipping addresses, payment and shipping metadata, and a full
        line-item breakdown. Only top-level shop orders are returned; refunds and
        other types yield 404.
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: integer
            minimum: 1
          description: WooCommerce order ID.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderDetail'
        "404":
          description: Order not found (unknown ID or non-shop_order type).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                code: order.not_found
                message: Order was not found.
                data:
                  status: 404
        "401":
          description: Missing bearer token.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "403":
          description: Invalid bearer token.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /orders/{id}/status:
    patch:
      summary: Update order status
      operationId: updateOrderStatus
      description: >
        Updates status of an existing WooCommerce order using native WooCommerce
        order methods. Supports idempotency: repeat the same request with the
        same `Idempotency-Key` header to safely retry the operation.
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: integer
            minimum: 1
          description: WooCommerce order ID.
        - in: header
          name: Idempotency-Key
          required: true
          schema:
            type: string
          description: Required idempotency key for safe retries.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateOrderStatusRequest'
            example:
              status: paid
      responses:
        "200":
          description: Order status updated or idempotent replay.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UpdateOrderStatusResponse'
              examples:
                updated:
                  value:
                    id: 42
                    status: completed
                    idempotent_replay: false
                replay:
                  value:
                    id: 42
                    status: completed
                    idempotent_replay: true
        "400":
          description: Missing required data (status or idempotency key).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "404":
          description: Order not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "401":
          description: Missing bearer token.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "403":
          description: Invalid bearer token.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "409":
          description: >
            Conflict — the requested order status is not supported by WooCommerce,
            the idempotency key was already used with a different payload, or the same
            request is currently being processed concurrently.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                status_invalid:
                  value:
                    code: order.status_invalid
                    message: Order status is not supported by WooCommerce.
                    data:
                      status: 409
                payload_mismatch:
                  value:
                    code: idempotency.payload_mismatch
                    message: Idempotency key was already used with different payload.
                    data:
                      status: 409
                in_progress:
                  value:
                    code: idempotency.in_progress
                    message: Request with this idempotency key is currently being processed.
                    data:
                      status: 409
        "500":
          description: Server error (WC controller unavailable, order not created, etc.).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /products:
    get:
      summary: List all products with prices and Miguel items
      operationId: getProducts
      description: >
        Returns all WooCommerce products and product variations (published, private, draft,
        pending, future) with pricing, stock, and their Miguel items — shortcodes found in
        downloadable files, plus a "print" item for non-downloadable products when a
        printed-book suffix is configured.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  count:
                    type: integer
                  currency:
                    type: string
                    description: Store currency (ISO 4217).
                    example: CZK
                  products:
                    type: array
                    items:
                      $ref: '#/components/schemas/Product'
        "401":
          description: Missing bearer token.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "403":
          description: Invalid bearer token.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /product-code-map:
    get:
      summary: Get Miguel product code to WooCommerce product ID mapping
      operationId: getProductCodeMap
      description: >
        Returns a map of all Miguel product codes with their resolved WooCommerce
        product IDs: codes from downloadable product files, plus printed-book codes
        (SKU plus the configured suffix) when a printed-book suffix is configured.
        Useful for detecting duplicate codes (same code on multiple products).
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  count:
                    type: integer
                    description: Total number of unique product codes.
                  duplicate_count:
                    type: integer
                    description: Number of codes that map to more than one product.
                  product_code_map:
                    type: object
                    additionalProperties:
                      type: integer
                    description: >
                      Map of product_code → product_id. When a code is not unique,
                      the ID of one of the matching products is returned.
                    example:
                      ABC123: 15
                      XYZ789: 22
                  product_code_details:
                    type: object
                    additionalProperties:
                      $ref: '#/components/schemas/ProductCodeDetail'
                    description: >
                      Extended map of product_code → detail object including all
                      matching product IDs and uniqueness flag.
        "401":
          description: Missing bearer token.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "403":
          description: Invalid bearer token.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
