---
title: Sync a catalogue
description: Push suppliers, colours and products from another system, and keep them in step.
icon: refresh-cw
---

A catalogue sync writes in a fixed order, because products reference the other
two: suppliers by id, colours by name.

1. **Create the suppliers**

    A variant's `supplierIds` must name suppliers that already exist, so send those
    first and keep the ids you get back.

    ```bash
    curl -X POST "https://app.bloomcount.com/api/v1/suppliers" \
      -H "Authorization: Bearer bloomcount_..." \
      -H "Content-Type: application/json" \
      -d '{ "name": "Marilliam Flowers", "contactEmail": "orders@marilliam.example" }'
    ```

    Suppliers are never deleted, because products keep pointing at them. Retire one
    with `POST /api/v1/suppliers/{id}/archive`, which leaves history intact and
    stops it taking new lines, and bring it back with `unarchive`.

2. **Check the colours**

    Variants carry colours **by name**, not by id, so a name that does not exist is
    simply a tag nothing else shares. Read the palette first and create what is
    missing:

    ```bash
    curl "https://app.bloomcount.com/api/v1/colors" \
      -H "Authorization: Bearer bloomcount_..."
    ```

    ```json
    {
      "items": [
        { "colorId": "col_...", "name": "Blush", "hex": "#f4c2c2", "isPreset": true }
      ]
    }
    ```

    Preset colours cannot be edited or deleted; your own can. Categories work the
    same way and are also referenced by name from a product's `category`.

3. **Upsert the products**

    One call carries up to 500 rows, each a product with one variant. Omitting
    `productId` creates; supplying it updates, patching only the fields you send.

    ```bash
    curl -X POST "https://app.bloomcount.com/api/v1/products" \
      -H "Authorization: Bearer bloomcount_..." \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: catalogue-2026-09-06-batch-1" \
      -d '{
        "items": [
          {
            "name": "Pink Mondial",
            "category": "Roses",
            "productType": "flower",
            "variant": {
              "name": "50cm",
              "stemLength": 50,
              "unitsPerPack": 10,
              "colors": ["Blush"],
              "supplierIds": ["sup_..."],
              "supplierMinPacks": [{ "supplierId": "sup_...", "minPacks": 5 }]
            }
          }
        ]
      }'
    ```

    The response reports each row separately:

    ```json
    {
      "results": [
        { "index": 0, "action": "created", "productId": "prod_...", "variantId": "var_..." }
      ],
      "errors": []
    }
    ```

    A row that fails lands in `errors` with its `index` and a code, and the rest of
    the batch still lands. That is the whole point of the bulk shape: one bad
    supplier reference does not cost you the other 499 rows.

4. **Retire what has gone**

    Deletes are per resource, not part of the batch:

    ```bash
    curl -X DELETE "https://app.bloomcount.com/api/v1/products/prod_..." \
      -H "Authorization: Bearer bloomcount_..."
    ```

    If the product is on an event, this returns `409` with
    `product_on_event_items:3` rather than quietly removing those lines. Add
    `?force=true` when removing it along with its history is what you mean. Deleting
    a single variant works the same way, at
    `DELETE /api/v1/products/{id}/variants/{variantId}`.

## Running it again

Two things make a repeated sync safe.

**Send an `Idempotency-Key` per batch.** A replay within 24 hours returns the
first response verbatim instead of writing again, so a timeout you retry cannot
double-create.

**Update by id, not by name.** Keep your own map from your system's identifier
to the `prod_`/`var_` ids the first sync returned. Matching on name instead
means a rename in either system creates a duplicate.

:::tip[Only ask for what you diff against]
A nightly reconcile usually compares a couple of fields. `GET /api/v1/products?fields=name,variants.sku`
returns those and nothing else, which is a much smaller response than the full
catalogue.
:::
