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

# Batch upsert products

> POST /products/batch on the WISEPIM Public API: create or update up to 200 products by SKU in one atomic, idempotent call. Requires products:write.



## OpenAPI

````yaml openapi.json POST /api/public/v1/products/batch
openapi: 3.1.0
info:
  title: WISEPIM Public API
  version: 1.0.0
  description: >-
    Read and update your WisePIM catalogue from your own systems. Authenticate
    with `Authorization: Bearer wpk_…`; create keys under Settings → API keys.
    Each key is bound to one project and to the scopes it was created with.
  summary: >-
    Read and update a WISEPIM project's products, categories and attributes from
    your own systems.
  termsOfService: https://wisepim.com/terms
  contact:
    name: WISEPIM support
    url: https://wisepim.com/contact
    email: info@wisepim.com
  x-logo:
    url: >-
      https://docs.wisepim.com/logo/wisepim-logo-horizontal-transparant-lightmode.png
    altText: WISEPIM
servers:
  - url: https://wisepim.com
    description: Production. Every path below already starts with /api/public/v1.
security:
  - ProjectApiKeyAuth: []
tags:
  - name: Key and project
    description: Check which key you are calling with and read the project it is bound to.
  - name: Products
    description: >-
      Read, create and update products. Reads need products:read, writes need
      products:write.
  - name: Categories
    description: >-
      Read the project's categories as a flat list with parent ids. Needs
      categories:read.
  - name: Attributes
    description: Read the project's attribute definitions. Needs attributes:read.
externalDocs:
  description: WISEPIM API reference and guides
  url: https://docs.wisepim.com/en/api-reference/introduction
paths:
  /api/public/v1/products/batch:
    post:
      tags:
        - Products
      summary: Create or update up to 200 products by SKU
      description: |-
        Upsert keyed by `sku`: items whose SKU exists are updated, the rest
        are created. The batch is validated first and applied atomically — a
        duplicate SKU within the batch, an unknown category or attribute, an
        invalid value or more creates than the plan allows returns 422
        `batch_invalid` with one entry per offending item and **nothing is
        written**. On success every item is applied in one transaction and the
        response lists each item's `id` and whether it was `created` or
        `updated`. Retrying the same batch is idempotent. Requires
        `products:write`.
      operationId: public_v1_batch_upsert_products
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          schema:
            type: string
            minLength: 1
            maxLength: 128
            pattern: ^[!-~]+$
          description: >-
            Optional. Send the same key on every retry of one write (same
            method, path, query and JSON body) and the API replays the original
            status and body instead of applying the change again. Keys are
            scoped to the calling API key and kept until that key is deleted.
            Reusing a key for a different request returns 409
            `idempotency_conflict`; a key that is not 1-128 printable ASCII
            characters without whitespace returns 422 `invalid_idempotency_key`.
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProductBatchIn'
        required: true
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductBatchOut'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorOut'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorOut'
        '404':
          description: Not Found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorOut'
        '409':
          description: Conflict
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorOut'
        '422':
          description: Unprocessable Content
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BatchErrorOut'
        '429':
          description: Too Many Requests
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorOut'
      security:
        - ProjectApiKeyAuth: []
components:
  schemas:
    ProductBatchIn:
      additionalProperties: false
      properties:
        items:
          items:
            $ref: '#/components/schemas/ProductUpsertIn'
          maxItems: 200
          minItems: 1
          title: Items
          type: array
      required:
        - items
      title: ProductBatchIn
      type: object
    ProductBatchOut:
      properties:
        results:
          items:
            $ref: '#/components/schemas/BatchResultItemOut'
          title: Results
          type: array
        summary:
          $ref: '#/components/schemas/BatchSummaryOut'
      required:
        - results
        - summary
      title: ProductBatchOut
      type: object
    ErrorOut:
      description: |-
        The shared error envelope every NinjaAPI instance renders.

        `code` is present for :class:`user_app.ninja_api.errors.ApiError`
        (`insufficient_scope`, `rate_limited`, `invalid_cursor`, …) so machine
        clients can branch without parsing `error`.
      properties:
        error:
          title: Error
          type: string
        code:
          anyOf:
            - type: string
            - type: 'null'
          title: Code
        request_id:
          title: Request Id
          type: string
      required:
        - error
        - request_id
      title: ErrorOut
      type: object
    BatchErrorOut:
      description: 'HTTP 422 for `POST /products/batch`: nothing was written.'
      properties:
        error:
          title: Error
          type: string
        code:
          anyOf:
            - type: string
            - type: 'null'
          title: Code
        request_id:
          title: Request Id
          type: string
        errors:
          items:
            $ref: '#/components/schemas/BatchErrorItemOut'
          title: Errors
          type: array
      required:
        - error
        - request_id
        - errors
      title: BatchErrorOut
      type: object
    ProductUpsertIn:
      additionalProperties: false
      properties:
        ean:
          anyOf:
            - maxLength: 255
              type: string
            - type: 'null'
          title: Ean
        upc:
          anyOf:
            - maxLength: 12
              type: string
            - type: 'null'
          title: Upc
        manufacturer_part_number:
          anyOf:
            - maxLength: 255
              type: string
            - type: 'null'
          title: Manufacturer Part Number
        brand:
          anyOf:
            - maxLength: 255
              type: string
            - type: 'null'
          title: Brand
        name:
          anyOf:
            - maxLength: 500
              type: string
            - type: 'null'
          title: Name
        name_long:
          anyOf:
            - maxLength: 500
              type: string
            - type: 'null'
          title: Name Long
        description:
          anyOf:
            - type: string
            - type: 'null'
          title: Description
        short_description:
          anyOf:
            - type: string
            - type: 'null'
          title: Short Description
        name_localized:
          anyOf:
            - additionalProperties:
                type: string
              type: object
            - type: 'null'
          description: 'Locale code → name, e.g. {"nl": "Boormachine"}.'
          title: Name Localized
        description_localized:
          anyOf:
            - additionalProperties:
                type: string
              type: object
            - type: 'null'
          title: Description Localized
        short_description_localized:
          anyOf:
            - additionalProperties:
                type: string
              type: object
            - type: 'null'
          title: Short Description Localized
        status:
          anyOf:
            - type: string
            - type: 'null'
          description: One of active, inactive, pending, archived.
          title: Status
        meta_title:
          anyOf:
            - maxLength: 255
              type: string
            - type: 'null'
          title: Meta Title
        meta_description:
          anyOf:
            - type: string
            - type: 'null'
          title: Meta Description
        meta_keywords:
          anyOf:
            - maxLength: 255
              type: string
            - type: 'null'
          title: Meta Keywords
        price:
          anyOf:
            - type: string
            - type: integer
            - type: 'null'
          description: >-
            Decimal string with at most two decimals ("19.99") or an integer.
            Null clears it.
          title: Price
        special_price:
          anyOf:
            - type: string
            - type: integer
            - type: 'null'
          description: >-
            Decimal string with at most two decimals ("14.99") or an integer.
            Null clears it.
          title: Special Price
        special_price_start_date:
          anyOf:
            - format: date-time
              type: string
            - type: 'null'
          title: Special Price Start Date
        special_price_end_date:
          anyOf:
            - format: date-time
              type: string
            - type: 'null'
          title: Special Price End Date
        category_ids:
          anyOf:
            - items:
                type: integer
              maxItems: 200
              type: array
            - type: 'null'
          description: >-
            Replaces the product's category set. Every id must belong to this
            project.
          title: Category Ids
        attributes:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          description: >-
            Attribute values keyed by attribute code. A value is a scalar or
            list, or {"value": ..., "unit": "kg"}; null removes the value. Only
            the codes sent are touched.
          title: Attributes
        sku:
          description: Upsert key within the project.
          maxLength: 255
          minLength: 1
          title: Sku
          type: string
      required:
        - sku
      title: ProductUpsertIn
      type: object
    BatchResultItemOut:
      properties:
        index:
          title: Index
          type: integer
        sku:
          title: Sku
          type: string
        id:
          title: Id
          type: integer
        action:
          description: '"created" or "updated".'
          title: Action
          type: string
      required:
        - index
        - sku
        - id
        - action
      title: BatchResultItemOut
      type: object
    BatchSummaryOut:
      properties:
        created:
          title: Created
          type: integer
        updated:
          title: Updated
          type: integer
      required:
        - created
        - updated
      title: BatchSummaryOut
      type: object
    BatchErrorItemOut:
      properties:
        index:
          title: Index
          type: integer
        sku:
          anyOf:
            - type: string
            - type: 'null'
          title: Sku
        code:
          title: Code
          type: string
        message:
          title: Message
          type: string
      required:
        - index
        - code
        - message
      title: BatchErrorItemOut
      type: object
  securitySchemes:
    ProjectApiKeyAuth:
      type: http
      scheme: bearer
      bearerFormat: Project API key (wpk_...)
      description: >-
        Send a project API key as `Authorization: Bearer wpk_...`. Create keys
        under Settings > API Access in the WISEPIM app (project owners and
        admins). A key is bound to one project and to the scopes chosen when it
        was created; scopes cannot be widened afterwards. Scopes: `project:read`
        - Read the project's name, languages, industry and limits.;
        `products:read` - List and read products, including attribute values and
        category ids.; `products:write` - Create and update products: names,
        descriptions, identifiers, prices, SEO fields, categories and attribute
        values.; `categories:read` - List and read the project's category tree.;
        `attributes:read` - List and read the project's attribute definitions.
      x-scopes:
        project:read: Read the project's name, languages, industry and limits.
        products:read: List and read products, including attribute values and category ids.
        products:write: >-
          Create and update products: names, descriptions, identifiers, prices,
          SEO fields, categories and attribute values.
        categories:read: List and read the project's category tree.
        attributes:read: List and read the project's attribute definitions.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.