openapi: 3.0.1
info:
  title: Carriyo Products API
  description: |-
    The Carriyo Products API manages your product catalogue. Products are referenced by shipments, orders, returns, and inventory, so keeping the catalogue up to date keeps downstream data accurate.
  contact:
    name: Carriyo Support
    url: https://help.carriyo.com
    email: support@carriyo.com
  version: '1.0'
servers:
  - url: https://api.carriyo.com
    description: Production Server
  - url: https://demo-api.carriyo.com
    description: Demo Server
security:
  - OAuth2-Production: []
    ApiKey: []
    TenantId: []
  - OAuth2-Demo: []
    ApiKey: []
    TenantId: []
tags:
  - name: Products
    description: |-
      The **Products** resource represents items in your catalogue. Products are scoped to a merchant within your tenant and may be referenced by shipments, orders, returns, and inventory records.

      ## Identifiers

      Carriyo supports three ways to identify a product:

      - **`product_id`**: assigned by Carriyo when the product is created. Unique within a tenant. Use this when you have stored the Carriyo identifier alongside your own.
      - **`product_ref`**: your reference for the product. We recommend keeping this unique within a merchant so it can be used as a reliable lookup key.
      - **`sku`**: stock keeping unit. SKU is not enforced as unique, so lookups by SKU return an array of matching products.

      ## Listing products

      The `GET /products` endpoint is backed by Elasticsearch and supports:

      - **Free-text search** across `sku`, `barcode`, `product_ref`, `description`, `hs_code`, `manufacturer_id`, and `material_composition`
      - **Filters** on `merchant`, `product_id`, `sku`, `barcode`, `product_ref`, and `status`. Repeat any filter parameter to match multiple values.
      - **Sorting** by `sku`, `barcode`, `product_ref`, `description`, `hs_code`, or `manufacturer_id`, in ascending or descending direction
      - **Pagination** via `page` (zero-indexed) and `page_size` (max 250)

      The response returns the matching page of products together with the total count, so you can drive paging UIs and report-style queries from the same endpoint.

      ## Cross-border attributes

      For shipments crossing customs borders, certain product attributes are typically required by carriers and customs:

      - `hs_code`: Harmonised System code for customs classification.
      - `origin_country`: ISO 3166-1 alpha-2 country code.
      - `material_composition`: for example "100% cotton"; relevant for textile classification.
      - `dangerous_goods`: set `true` if the product is classified as dangerous goods.
      - `battery`: supply `material_type` and `packing_type` if the product contains batteries.
      - `manufacturer_id`: manufacturer reference, required by some carriers in certain destinations.

      ## Custom metadata

      Use `carriyo_metadata` to attach arbitrary name/value pairs to a product when the standard fields don't capture the attribute you need. These values are preserved in the product record and can be referenced in automation rules and downstream integrations.

      ## Bulk operations

      For larger catalogues, two bulk operations are available:

      - **Bulk create/update** (`POST /products/{merchant_id}/bulk/import`): submit up to 500 products in a single request. Each item is processed independently and the response reports per-item outcomes.
      - **Bulk delete** (`DELETE /products/{merchant_id}/bulk`): deletes all products for the merchant. Large catalogues may require multiple invocations; the `more_items_available` flag in the response indicates whether to re-invoke.
paths:
  /products:
    get:
      tags:
        - Products
      summary: List products
      operationId: list-products
      description: |-
        Returns a paginated list of products that match the supplied filter, sort, and search
        criteria.


        The list endpoint is backed by Elasticsearch and supports free-text search across product
        fields such as `description`, `sku`, `barcode`, `product_ref`, `hs_code`, `manufacturer_id`,
        and `material_composition`.


        Filters are applied as exact term matches. Most filters can be repeated to match any of the
        supplied values.
      parameters:
        - name: search_string
          in: query
          required: false
          schema:
            type: string
          description: |-
            Free-text search applied across `sku`, `barcode`, `product_ref`, `description`,
            `hs_code`, `manufacturer_id`, and `material_composition`.
        - name: merchant
          in: query
          required: false
          schema:
            type: string
          description: Filter by merchant. Repeat the parameter to match multiple merchants.
        - name: product_id
          in: query
          required: false
          schema:
            type: string
          description: Filter by Carriyo product ID. Repeat the parameter to match multiple values.
        - name: sku
          in: query
          required: false
          schema:
            type: string
          description: Filter by SKU. Repeat the parameter to match multiple values.
        - name: barcode
          in: query
          required: false
          schema:
            type: string
          description: Filter by barcode. Repeat the parameter to match multiple values.
        - name: product_ref
          in: query
          required: false
          schema:
            type: string
          description: Filter by your reference (`product_ref`). Repeat the parameter to match multiple values.
        - name: status
          in: query
          required: false
          schema:
            type: string
            enum:
              - ACTIVE
              - INACTIVE
              - DELETED
          description: Filter by product status.
        - name: sort_by
          in: query
          required: false
          schema:
            type: string
            enum:
              - sku
              - barcode
              - product_ref
              - description
              - hs_code
              - manufacturer_id
          description: Field to sort the results by.
        - name: sort_direction
          in: query
          required: false
          schema:
            type: string
            enum:
              - asc
              - desc
            default: desc
          description: Sort direction. Defaults to `desc`.
        - name: page
          in: query
          required: false
          schema:
            type: integer
            default: 0
          description: Page number, starting from 0. Defaults to 0.
        - name: page_size
          in: query
          required: false
          schema:
            type: integer
            default: 10
            maximum: 250
          description: Number of results per page. Defaults to 10. Maximum 250.
      responses:
        '200':
          description: A paginated list of products.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/product-list-response'
  /products/{merchant_id}:
    post:
      tags:
        - Products
      summary: Create product
      operationId: create-product
      description: |-
        Creates a new product for the specified merchant. Either `sku` or `product_ref` is required
        to identify the product within your catalogue.


        Returns the created product, including the Carriyo-assigned `product_id`.
      parameters:
        - $ref: '#/components/parameters/merchant_id'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/product-request'
      responses:
        '200':
          description: Successfully created the product.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/product-response'
        '400':
          description: Invalid request payload.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/error-response'
    get:
      tags:
        - Products
      summary: Search products by SKU
      operationId: search-products-by-sku
      description: |-
        Returns all products belonging to the merchant that match the supplied `sku`. SKU is not
        guaranteed to be unique within a merchant, so this endpoint returns an array.
      parameters:
        - $ref: '#/components/parameters/merchant_id'
        - name: sku
          in: query
          required: true
          schema:
            type: string
          description: The SKU to look up.
      responses:
        '200':
          description: List of matching products.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/product-response'
        '404':
          description: No products found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/error-response'
  /products/{merchant_id}/{product_id}:
    get:
      tags:
        - Products
      summary: Get product by ID
      operationId: get-product
      description: Retrieves a single product by its Carriyo `product_id`.
      parameters:
        - $ref: '#/components/parameters/merchant_id'
        - $ref: '#/components/parameters/product_id'
      responses:
        '200':
          description: The product record.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/product-response'
        '404':
          description: No product found for the supplied ID.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/error-response'
    put:
      tags:
        - Products
      summary: Update product
      operationId: update-product
      description: |-
        Replace the full product record. All fields are overwritten with the values in the request
        body; fields omitted from the body are cleared.


        Use `PATCH` to update only a subset of fields.
      parameters:
        - $ref: '#/components/parameters/merchant_id'
        - $ref: '#/components/parameters/product_id'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/product-request'
      responses:
        '200':
          description: The updated product record.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/product-response'
        '404':
          description: No product found for the supplied ID.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/error-response'
    patch:
      tags:
        - Products
      summary: Patch product
      operationId: patch-product
      description: |-
        Partially updates the product record. Only fields supplied in the request body are updated;
        omitted fields retain their current values.
      parameters:
        - $ref: '#/components/parameters/merchant_id'
        - $ref: '#/components/parameters/product_id'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/product-request'
      responses:
        '200':
          description: The updated product record.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/product-response'
        '404':
          description: No product found for the supplied ID.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/error-response'
    delete:
      tags:
        - Products
      summary: Delete product
      operationId: delete-product
      description: |-
        Deletes the product. The returned record reflects the product's state at the time of
        deletion.
      parameters:
        - $ref: '#/components/parameters/merchant_id'
        - $ref: '#/components/parameters/product_id'
      responses:
        '200':
          description: The deleted product record.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/product-response'
        '404':
          description: No product found for the supplied ID.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/error-response'
  /products/{merchant_id}/product-ref/{product_ref}:
    get:
      tags:
        - Products
      summary: Get product by product ref
      operationId: get-product-by-product-ref
      description: Retrieves a single product by your `product_ref`.
      parameters:
        - $ref: '#/components/parameters/merchant_id'
        - $ref: '#/components/parameters/product_ref'
      responses:
        '200':
          description: The product record.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/product-response'
        '404':
          description: No product found for the supplied product_ref.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/error-response'
    delete:
      tags:
        - Products
      summary: Delete product by product ref
      operationId: delete-product-by-product-ref
      description: Deletes the product identified by your `product_ref`.
      parameters:
        - $ref: '#/components/parameters/merchant_id'
        - $ref: '#/components/parameters/product_ref'
      responses:
        '200':
          description: The deleted product record.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/product-response'
        '404':
          description: No product found for the supplied product_ref.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/error-response'
  /products/{merchant_id}/bulk/import:
    post:
      tags:
        - Products
      summary: Bulk create products
      operationId: bulk-create-products
      description: |-
        Create or update multiple products in a single request. Each item in the request array is
        processed independently; the response array reports the outcome (`accepted` or `rejected`)
        for each item, in the same order.


        ### Limits


        - Maximum **500** items per request.
      parameters:
        - $ref: '#/components/parameters/merchant_id'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: '#/components/schemas/product-request'
      responses:
        '200':
          description: Per-item processing results.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/bulk-create-update-response'
        '400':
          description: Invalid request (e.g., too many items).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/error-response'
  /products/{merchant_id}/bulk:
    delete:
      tags:
        - Products
      summary: Bulk delete products
      operationId: bulk-delete-products
      description: |-
        Deletes all products for the specified merchant.


        > **Warning:** This operation is destructive and cannot be undone. The response indicates
        how many records were deleted, attempted, and failed, and whether more items remain. Large
        catalogues may require multiple invocations to fully clear.
      parameters:
        - $ref: '#/components/parameters/merchant_id'
      responses:
        '200':
          description: Bulk delete summary.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/bulk-delete-response'
components:
  parameters:
    merchant_id:
      name: merchant_id
      in: path
      required: true
      schema:
        type: string
      description: The merchant identifier the product belongs to.
    product_id:
      name: product_id
      in: path
      required: true
      schema:
        type: string
      description: The Carriyo-assigned product identifier.
    product_ref:
      name: product_ref
      in: path
      required: true
      schema:
        type: string
      description: Your reference for the product (`product_ref`).
  schemas:
    product-request:
      title: Product Request
      type: object
      properties:
        product_ref:
          type: string
          description: Your reference for the product. Recommended to be unique within a merchant.
        sku:
          type: string
          description: Stock keeping unit.
        description:
          type: string
          description: Human-readable product description.
        barcode:
          type: string
          description: Barcode (e.g., EAN, UPC).
        image_link:
          type: string
          description: URL to a product image.
        hs_code:
          type: string
          description: Harmonised System code. Often required for cross-border shipments.
        origin_country:
          type: string
          description: ISO 3166-1 alpha-2 country code of origin.
        manufacturer_id:
          type: string
          description: Manufacturer reference. Required by some carriers in certain destinations.
        material_composition:
          type: string
          description: Material details (e.g., "100% cotton"). Relevant for textile customs classification.
        category:
          type: string
          description: Product category.
        dangerous_goods:
          type: boolean
          description: Whether the product is classified as dangerous goods.
        battery:
          $ref: '#/components/schemas/battery'
        digital:
          type: boolean
          description: Whether the product is a digital good (no physical shipment).
        non_returnable:
          type: boolean
          description: Whether the product is excluded from returns.
        status:
          $ref: '#/components/schemas/status'
        weight:
          $ref: '#/components/schemas/weight'
        dimension:
          $ref: '#/components/schemas/dimension'
        do_not_place_above:
          type: boolean
          description: 'Stacking constraint: must not be placed above other items.'
        do_not_place_below:
          type: boolean
          description: 'Stacking constraint: must not be placed below other items.'
        carriyo_metadata:
          type: array
          description: Custom name/value pairs for storing additional product attributes.
          items:
            $ref: '#/components/schemas/carriyo-metadata'
    product-response:
      title: Product Response
      type: object
      properties:
        tenant:
          type: string
          description: The tenant identifier.
        merchant:
          type: string
          description: The merchant the product belongs to.
        product_id:
          type: string
          description: The Carriyo-assigned product identifier.
        product_ref:
          type: string
          description: Your reference for the product.
        sku:
          type: string
        description:
          type: string
        barcode:
          type: string
        image_link:
          type: string
        hs_code:
          type: string
        origin_country:
          type: string
        manufacturer_id:
          type: string
        material_composition:
          type: string
        category:
          type: string
        dangerous_goods:
          type: boolean
        battery:
          $ref: '#/components/schemas/battery'
        digital:
          type: boolean
        non_returnable:
          type: boolean
        status:
          $ref: '#/components/schemas/status'
        weight:
          $ref: '#/components/schemas/weight'
        dimension:
          $ref: '#/components/schemas/dimension'
        do_not_place_above:
          type: boolean
        do_not_place_below:
          type: boolean
        carriyo_metadata:
          type: array
          items:
            $ref: '#/components/schemas/carriyo-metadata'
        creation_date:
          type: string
          format: date-time
          description: Creation timestamp (ISO 8601).
        update_date:
          type: string
          format: date-time
          description: Last update timestamp (ISO 8601).
        creation_source:
          $ref: '#/components/schemas/request-source'
        update_source:
          $ref: '#/components/schemas/request-source'
    product-list-response:
      title: Product List Response
      type: object
      properties:
        items:
          type: array
          description: The page of matching products.
          items:
            $ref: '#/components/schemas/product-response'
        total:
          type: integer
          description: Total number of products matching the query (across all pages).
    bulk-create-update-response:
      title: Bulk Create/Update Response Item
      type: object
      properties:
        productId:
          type: string
          description: The Carriyo-assigned product ID, if assigned.
        sku:
          type: string
        productRef:
          type: string
        result:
          type: string
          enum:
            - accepted
            - rejected
          description: Outcome for this item.
        reason:
          type: string
          description: Reason for rejection, if `result` is `rejected`.
        item:
          $ref: '#/components/schemas/product-response'
    bulk-delete-response:
      title: Bulk Delete Response
      type: object
      properties:
        deleted_count:
          type: integer
          description: Number of products successfully deleted.
        attempted_count:
          type: integer
          description: Number of products attempted in this invocation.
        failed_count:
          type: integer
          description: Number of products that failed to delete.
        reason:
          type: string
          description: Failure reason, if applicable.
        result:
          type: string
          enum:
            - accepted
            - rejected
        more_items_available:
          type: boolean
          description: Whether more products remain to be deleted. Re-invoke until `false`.
    status:
      title: Status
      type: string
      enum:
        - ACTIVE
        - INACTIVE
        - DELETED
      description: Lifecycle status of the product.
    weight:
      title: Weight
      type: object
      properties:
        value:
          type: number
        unit:
          type: string
          enum:
            - kg
          description: Default unit is `kg`.
    dimension:
      title: Dimension
      type: object
      properties:
        width:
          type: number
        height:
          type: number
        depth:
          type: number
        unit:
          type: string
          enum:
            - cm
          description: Default unit is `cm`.
    battery:
      title: Battery
      type: object
      description: Battery details. Usually required for cross-border shipments containing batteries.
      properties:
        material_type:
          type: string
          enum:
            - lithium_metal
            - lithium_ion
        packing_type:
          type: string
          enum:
            - contained_in_equipment
            - packed_with_equipment
            - stand_alone
    carriyo-metadata:
      title: Carriyo Metadata
      type: object
      properties:
        name:
          type: string
        value:
          type: string
    request-source:
      title: Request Source
      type: object
      properties:
        source_type:
          type: string
          description: The system that originated the request (e.g., `api`, `dashboard`, `shopify_connector`).
        source_id:
          type: string
          description: Identifier of the user or system that made the request.
        api_pattern:
          type: string
          description: 'The API pattern that was invoked (for `source_type: api`).'
        request_type:
          type: string
        system_log_identifier:
          type: string
    error-response:
      title: Error Response
      type: object
      properties:
        error:
          type: string
          description: Error message describing what went wrong.
        code:
          type: string
          description: Error code for programmatic handling.
  securitySchemes:
    OAuth2-Production:
      type: oauth2
      flows:
        clientCredentials:
          tokenUrl: https://api.carriyo.com/oauth/token
          scopes: {}
    OAuth2-Demo:
      type: oauth2
      flows:
        clientCredentials:
          tokenUrl: https://demo-api.carriyo.com/oauth/token
          scopes: {}
    ApiKey:
      type: apiKey
      in: header
      name: x-api-key
      description: "Your tenant's API key, issued in the Carriyo Dashboard. Required on every request except `POST /oauth/token`."
    TenantId:
      type: apiKey
      in: header
      name: tenant-id
      description: "Your Carriyo tenant ID, shown in the Carriyo Dashboard. Required on every request except `POST /oauth/token`."
