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

# Batch create/upsert vendors

> Up to 100 items in one call. With `merge_key`, every item gets upsert semantics (create-or-update by that key); without it, pure creates. Items are processed independently and the response is PER-ITEM: **HTTP 200 does NOT mean every item succeeded** — check each `status` (`created` | `updated` | `error`) and surface `error.message`/`error.field_errors` for failures. A 5,000-SKU ERP delta push is 50 calls with `merge_key: "sku"`.



## OpenAPI

````yaml https://www.merchkit.com/api/v1/openapi post /vendors/batch
openapi: 3.1.0
info:
  title: Merchkit API
  version: 1.0.0
  description: >-
    Operate your Merchkit PIM programmatically. Products, images, vendors,
    categories, and data sources are all "entities" in one uniform shape: system
    fields (`id`, `type`, `label`, `parent_id`, timestamps) at the top level and
    every customer-defined key — scalars AND references — in a sparse
    `attributes` map. References read as labeled stubs `{id, type, label}` and
    write as bare UUIDs or `{id}` objects ("write what you read"). UUIDs are the
    only path identifiers; find by your own key with a filter (`GET
    /products?filter[sku]=ARIA-DT-72`) or write by it with `POST
    /{resource}/upsert` and `/batch` (products, images, vendors, and categories
    only — sources have neither route; they are created by the async `POST
    /sources` job pipeline). Start every integration with `GET
    /attributes?type=product` to learn your keys. Completeness is per-channel.
    All errors share one envelope (see the `Error` schema). Authenticate with a
    workspace-scoped API key: `Authorization: Bearer mk_live_...`. See /llms.txt
    for an agent guide.
servers:
  - url: https://www.merchkit.com/api/v1
    description: Merchkit API v1
security:
  - ApiKeyAuth: []
paths:
  /vendors/batch:
    post:
      summary: Batch create/upsert vendors
      description: >-
        Up to 100 items in one call. With `merge_key`, every item gets upsert
        semantics (create-or-update by that key); without it, pure creates.
        Items are processed independently and the response is PER-ITEM: **HTTP
        200 does NOT mean every item succeeded** — check each `status`
        (`created` | `updated` | `error`) and surface
        `error.message`/`error.field_errors` for failures. A 5,000-SKU ERP delta
        push is 50 calls with `merge_key: "sku"`.
      operationId: batchWriteVendors
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - items
              additionalProperties: false
              properties:
                merge_key:
                  type: string
                  description: >-
                    Optional shared merge key — when present, items upsert
                    instead of create.
                items:
                  type: array
                  minItems: 1
                  maxItems: 100
                  items:
                    type: object
                    required:
                      - attributes
                    additionalProperties: false
                    properties:
                      parent_id:
                        type:
                          - string
                          - 'null'
                        format: uuid
                      attributes:
                        $ref: '#/components/schemas/AttributeWriteMap'
            example:
              merge_key: vendor_name
              items:
                - attributes:
                    vendor_name: Nordic Timber Co.
                    country: Denmark
      responses:
        '200':
          description: >-
            Per-item results, in input order, under a `summary` that makes "N of
            M failed" a single field read. Check every item — a 200 only means
            the batch was processed. Route retries on `error.retriable`, never
            on message text. Summaries from re-runs must not be summed (an
            idempotent retry reports updates for rows the first attempt
            created).
          content:
            application/json:
              schema:
                type: object
                required:
                  - summary
                  - data
                properties:
                  summary:
                    $ref: '#/components/schemas/BatchSummary'
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/BatchItemResult'
              example:
                summary:
                  total: 1
                  created: 0
                  updated: 1
                  failed: 0
                  has_errors: false
                data:
                  - index: 0
                    id: 77e1b2aa-6b3d-4f19-9d10-8c2a5e7f4b21
                    status: updated
        '400':
          description: Malformed batch (empty items, >100 items, bad shape).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
        - ApiKeyAuth: []
components:
  schemas:
    AttributeWriteMap:
      type: object
      description: >-
        Map of attribute key → new value (sparse — only keys being written).
        Scalars set the value; `null` OR `""` clears it (the key then disappears
        from reads). A single-reference key accepts a bare UUID string or an
        `{id}` object; a list-reference key accepts an array of either — list
        writes REPLACE the current set. Read stubs re-sent verbatim are valid
        writes (extra stub fields like `type`/`label` are ignored). Unknown keys
        are a 400; select values must be one of the attribute's
        `acceptable_values`.
      additionalProperties:
        $ref: '#/components/schemas/AttributeWriteValue'
    BatchSummary:
      type: object
      description: >-
        Batch-level counts. `has_errors` is the cheap "anything to look at?"
        check; clean batches can skip the per-item scan. Do not sum summaries
        across idempotent re-runs of the same batch.
      required:
        - total
        - created
        - updated
        - failed
        - has_errors
      properties:
        total:
          type: integer
        created:
          type: integer
        updated:
          type: integer
        failed:
          type: integer
        has_errors:
          type: boolean
      example:
        total: 3
        created: 1
        updated: 1
        failed: 1
        has_errors: true
    BatchItemResult:
      type: object
      description: >-
        Per-item outcome of a batch write, in input order. Always check every
        item — the batch HTTP status only says the batch ran. `merge_value`
        echoes the item’s merge-key value so results stay joinable to source
        rows without the original request (and across re-sliced batches).
      required:
        - index
        - status
      properties:
        index:
          type: integer
          description: Position in the request `items`.
        merge_value:
          type: string
          description: >-
            Trimmed echo of `attributes[merge_key]` (present when the batch has
            a merge_key).
        id:
          type: string
          format: uuid
          description: The created/updated entity id (absent on error).
        status:
          type: string
          enum:
            - created
            - updated
            - error
        error:
          type: object
          required:
            - code
            - retriable
            - message
          properties:
            code:
              type: string
              description: >-
                Platform error code (same vocabulary as the top-level error
                envelope). Fine-grained causes are in field_errors[].issue.
            retriable:
              type: boolean
              description: >-
                Whether re-submitting the same item unchanged can succeed. Route
                retry queues on this, never on message text.
            message:
              type: string
            field_errors:
              type: array
              items:
                $ref: '#/components/schemas/FieldError'
      example:
        index: 0
        merge_value: ARIA-DT-72
        id: 9b2f6c1e-8a04-4c6e-b0d3-5f2f6f7a9e21
        status: updated
    Error:
      type: object
      description: >-
        The one error envelope every non-2xx response uses. `documentation_url`
        links to the code's section of the error docs.
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
            - is_retriable
          properties:
            code:
              type: string
              enum:
                - unauthorized
                - insufficient_scope
                - forbidden
                - not_found
                - validation_failed
                - rate_limited
                - conflict
                - internal_error
            message:
              type: string
            documentation_url:
              type: string
              format: uri
              description: https://docs.merchkit.com/developers/errors#<code>
            is_retriable:
              type: boolean
              description: >-
                Only `rate_limited` and `internal_error` are retriable.
                Deterministic client errors (validation, not-found, conflict)
                return a 4xx and are never retriable.
            retry_after_seconds:
              type:
                - integer
                - 'null'
            alternative_action:
              type: string
              description: What to do instead, when there is a better call.
            request_id:
              type: string
            field_errors:
              type: array
              items:
                $ref: '#/components/schemas/FieldError'
      example:
        error:
          code: validation_failed
          message: 'Unknown attribute key(s): colour.'
          documentation_url: https://docs.merchkit.com/developers/errors#validation_failed
          is_retriable: false
          retry_after_seconds: null
          alternative_action: List valid attribute keys via GET /v1/attributes?type=product.
          request_id: req_01j9x2k8
          field_errors:
            - field: colour
              issue: not_a_defined_attribute
    AttributeWriteValue:
      description: >-
        A write value: scalar, null/"" to clear, a UUID or `{id}` object for a
        single reference, or an array of UUIDs/`{id}` objects for a list
        reference (replace semantics).
      oneOf:
        - type: string
        - type: number
        - type: boolean
        - type: 'null'
        - type: object
          required:
            - id
          properties:
            id:
              type: string
              format: uuid
          additionalProperties: true
          description: >-
            A reference by id — extra fields (a re-sent read stub's
            `type`/`label`) are ignored.
        - type: array
          items:
            oneOf:
              - type: string
                format: uuid
              - type: object
                required:
                  - id
                properties:
                  id:
                    type: string
                    format: uuid
                additionalProperties: true
    FieldError:
      type: object
      required:
        - field
        - issue
      properties:
        field:
          type: string
        issue:
          type: string
          description: Machine-readable issue code, e.g. `not_a_defined_attribute`.
        acceptable_values:
          type: array
          items:
            type: string
          description: 'For select-value violations: the allowed values.'
  securitySchemes:
    ApiKeyAuth:
      type: http
      scheme: bearer
      description: >-
        Workspace-scoped API key. Available scopes:

        - `read:products`: List and read products, including variants,
        references, and completeness.

        - `write:products`: Create, update, and upsert products and their
        attribute values.

        - `delete:products`: Delete products.

        - `read:images`: List and read images.

        - `write:images`: Create, update, and upsert images and their attribute
        values.

        - `delete:images`: Delete images.

        - `read:vendors`: List and read vendors.

        - `write:vendors`: Create, update, and upsert vendors and their
        attribute values.

        - `delete:vendors`: Delete vendors.

        - `read:categories`: List and read categories.

        - `write:categories`: Create, update, and upsert categories and their
        attribute values.

        - `delete:categories`: Delete categories.

        - `read:sources`: List and read data sources, including scraped content.

        - `write:sources`: Create data sources (triggers processing) and update
        their attribute values.

        - `delete:sources`: Delete data sources.

        - `read:attributes`: List and read attribute definitions.

        - `write:attributes`: Create and update attribute definitions.

        - `read:views`: List and read saved grid views (MCP surface).

        - `write:views`: Create and update grid views (MCP surface).

        - `read:channels`: Read the enabled channel catalog.

        - `read:events`: Read the workspace change feed and per-entity events.

        - `read:jobs`: List and poll asynchronous job status.

````