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

# Bulk Creation

> Create many stock items, SKUs, customers or order lines in one Admin API call, and decide first how many records the merchant needs.

Send one batch call instead of a loop. The Admin API creates stock items, SKUs, customers and order lines from an array, and one call counts as one request against your API key allowance however many records it carries.

Decide how many records the account needs before you create any. Individual tracking is a modelling decision, not a volume decision.

## Decide the shape first

Ask whether each unit needs its own history.

| Each unit needs                                            | Create                                   | Result                                  |
| ---------------------------------------------------------- | ---------------------------------------- | --------------------------------------- |
| Its own code, condition, service history, income and costs | One tracked stock item per unit          | `N` records, each with its own timeline |
| Nothing of its own, because any unit will do               | One stock item that carries the quantity | One record covering `N` units           |

A hire fleet of 40 bikes needs 40 records: each frame carries a serial number, its own servicing and its own repair costs. A resale listing for 1,000 identical t-shirts needs one record with a quantity of 1,000. Choosing per-unit records for interchangeable stock gives the account 1,000 rows to scroll where one would do, and spreads availability arithmetic across records that never needed separating.

Set the mode with `trackIndividually` on the create request. Read [Inventory tracking](/docs/concepts/inventory/inventory-tracking) for what each mode reports and what it gives up.

## How it works

Four endpoints accept an array:

| Endpoint                                                | Creates                          | One array entry is             |
| ------------------------------------------------------- | -------------------------------- | ------------------------------ |
| `POST /v1/admin/articles/create-many`                   | Stock items                      | One stock item                 |
| `POST /v1/admin/skus/many`                              | SKUs                             | One SKU                        |
| `POST /v1/admin/customers/many`                         | Customers                        | One customer, matched on email |
| `POST /v1/admin/orders/{orderId}/lineItems/create-many` | Line items on one existing order | One line                       |

Each entry creates exactly one record. On `articles/create-many`, `quantity` sets that one record's capacity: it never multiplies entries, and `trackIndividually` is read on the single create only.

So there are two ways to create many stock items, and they answer different questions:

* **Same item, many tracked units.** Call `POST /v1/admin/articles` once with `quantity`. Every unit becomes its own record.
* **Units that differ** in name, SKU, location or attributes. Call `POST /v1/admin/articles/create-many` with one entry per record.

## Usage

### Stock items

Create 40 tracked bikes in one call. `codes` assigns the first code to the first unit, the second to the second, and generates a code for any unit you leave uncovered.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://server.twicecommerce.com/v1/admin/articles \
    -H "X-API-KEY: {key}" \
    -H "Content-Type: application/json" \
    -d '{
      "article": {
        "name": "Trail bike 29\"",
        "serviceLocationId": "9f1c2d84-4c1e-4a0b-9b62-3a7e5c118d20",
        "taxonomyCategoryId": null,
        "quantity": 40,
        "trackIndividually": true,
        "codes": ["BIKE-001", "BIKE-002", "BIKE-003"]
      }
    }'
  ```

  ```json Response theme={null}
  [
    {
      "id": "3c8a5f21-0e9d-4a17-9c33-6b2f0d51a884",
      "name": "Trail bike 29\"",
      "status": "active",
      "codes": ["BIKE-001"],
      "quantity": { "total": 1 }
    },
    {
      "id": "b71d0c46-2f83-4d59-8a05-9e4c7a1b3f62",
      "name": "Trail bike 29\"",
      "status": "active",
      "codes": ["BIKE-002"],
      "quantity": { "total": 1 }
    }
  ]
  ```
</CodeGroup>

For interchangeable stock, send `trackIndividually: false` and the same `quantity`. One record comes back with `quantity.total` set to 40.

Use `create-many` when the entries differ from each other:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://server.twicecommerce.com/v1/admin/articles/create-many \
    -H "X-API-KEY: {key}" \
    -H "Content-Type: application/json" \
    -d '[
      {
        "name": "Meeting room chair",
        "serviceLocationId": "9f1c2d84-4c1e-4a0b-9b62-3a7e5c118d20",
        "taxonomyCategoryId": null,
        "quantity": 120,
        "codes": ["CHAIR-POOL-A"]
      },
      {
        "name": "Wool coat, size M",
        "serviceLocationId": "5d2b6e07-1a44-4f8c-bb90-2c6d3e7f4a15",
        "taxonomyCategoryId": null,
        "codes": ["RESALE-2026-0184"]
      }
    ]'
  ```

  ```json Response theme={null}
  [
    {
      "id": "e0a4b913-77c6-42de-9f1a-08b5c2d63417",
      "name": "Meeting room chair",
      "status": "active",
      "codes": ["CHAIR-POOL-A"],
      "quantity": { "total": 120 }
    },
    {
      "id": "1f6c8d25-93ab-4e07-8c14-5a9b0e3d7268",
      "name": "Wool coat, size M",
      "status": "active",
      "codes": ["RESALE-2026-0184"],
      "quantity": { "total": 1 }
    }
  ]
  ```
</CodeGroup>

### SKUs

`POST /v1/admin/skus/many` creates one SKU per entry and returns the id, name and code of each. Omit `code` and TWICE generates one from the name, adding a numeric suffix until it is unique in the account. Send a `code` that already exists and the call fails with `400`, naming the code. Nothing is created when it fails: the whole array is written in one transaction.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://server.twicecommerce.com/v1/admin/skus/many \
    -H "X-API-KEY: {key}" \
    -H "Content-Type: application/json" \
    -d '[
      { "name": "Trail bike 29\"", "code": "TRA-BI-29" },
      { "name": "Studio A" }
    ]'
  ```

  ```json Response theme={null}
  [
    { "id": "a2d47e19-6c30-4b85-9f27-0e1a8d5c3b46", "name": "Trail bike 29\"", "code": "TRA-BI-29" },
    { "id": "7b93f0c5-8d21-4a6e-b013-4c5f2e9a7d81", "name": "Studio A", "code": "ST-A" }
  ]
  ```
</CodeGroup>

### Customers

`POST /v1/admin/customers/many` returns one id per entry. An entry whose email already exists on the account updates that customer instead of creating a second one, and fields you leave out keep their stored values. Entries without an email are always inserted, so a repeated import without emails builds duplicates.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://server.twicecommerce.com/v1/admin/customers/many \
    -H "X-API-KEY: {key}" \
    -H "Content-Type: application/json" \
    -d '[
      { "firstName": "Aino", "lastName": "Virtanen", "email": "aino@example.com", "marketingConsent": true },
      { "firstName": "Marek", "lastName": "Novak", "email": "marek@example.com", "marketingConsent": false }
    ]'
  ```

  ```json Response theme={null}
  {
    "ids": [
      "c41e7b26-9a58-4d03-8f16-2b7d0c5e9a34",
      "8d2f5a70-3c19-4be8-a742-6f0b1d8c37e5"
    ]
  }
  ```
</CodeGroup>

### Order line items

`POST /v1/admin/orders/{orderId}/lineItems/create-many` adds several lines to one existing order. Each line carries its own `quantity`, so identical lines do not need one entry each. Prices are computed server-side from the catalog item and pricing row.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://server.twicecommerce.com/v1/admin/orders/6e0f3b74-52a8-4c19-9d75-8b1e4a2c6f03/lineItems/create-many \
    -H "X-API-KEY: {key}" \
    -H "Content-Type: application/json" \
    -d '[
      {
        "catalogItemId": "d5b81f39-7a02-4c68-9e14-3f7a5c0b28d6",
        "pricingRowId": "2a7c6e40-8b13-49f5-a0d2-7c9e5b1f3a48",
        "purchaseType": "booking",
        "quantity": 12,
        "startDate": "2026-10-02T08:00:00.000Z",
        "endDate": "2026-10-02T16:00:00.000Z"
      }
    ]'
  ```

  ```json Response theme={null}
  [
    {
      "id": "9c3a7d18-4b65-42f0-8e29-1d6b0a5c7e93",
      "catalogItemId": "d5b81f39-7a02-4c68-9e14-3f7a5c0b28d6",
      "purchaseType": "booking",
      "quantity": 12,
      "startDate": "2026-10-02T08:00:00.000Z",
      "endDate": "2026-10-02T16:00:00.000Z"
    }
  ]
  ```
</CodeGroup>

## Check codes and emails before you send

Run the validation endpoint over the whole list first and resolve the clashes up front. Each returns only the entries that would fail, so an empty `invalid` array means the batch is free to go.

| Run this                                                              | It returns                                                      |
| --------------------------------------------------------------------- | --------------------------------------------------------------- |
| `POST /v1/admin/articles/validate-codes` with `{ "codes": [...] }`    | Stock item codes already in use, each with `reason: "existing"` |
| `POST /v1/admin/skus/validate-codes` with `{ "codes": [...] }`        | SKU codes already in use                                        |
| `POST /v1/admin/customers/validate-emails` with `{ "emails": [...] }` | Emails already on a customer, which the import would merge into |

## Batch size and rate limits

One call is one request. A loop of 1,000 single creates spends 1,000 requests of the key's per-second rate and monthly cap; the same import as batch calls spends a handful. See the rate limits section of [API keys](/docs/concepts/integrations/api-keys) for the three layers and the response headers that report what is left.

The API declares no maximum on array length or on `quantity`, so nothing rejects a request to create ten million records. Treat a few hundred to around a thousand entries per call as guidance rather than a limit, and split a larger import into several calls.

Splitting also keeps a failure cheap to recover from. SKUs and customers are written in one transaction, so a rejected entry leaves nothing behind. A very large stock item array is committed in parts, so entries early in the array can exist while a later one fails. Send batches you can retry, and record which ones came back `200`.

<Note>
  Not every import needs the API. The admin imports stock items and customers from a CSV file, which is usually the faster route for a one-off migration. See [Import inventory from CSV](/docs/guides/inventory/import-inventory-csv) and [Import customers](/docs/guides/customers/import-customers).
</Note>

## Related articles

<CardGroup cols={2} className="doc-rows-condensed">
  <Card title="Inventory tracking" href="/docs/concepts/inventory/inventory-tracking">
    Serialized and pooled tracking, and what each one reports.
  </Card>

  <Card title="Stock item vs SKU" href="/docs/inventory/stock-item-vs-sku">
    Which of the two a record should be.
  </Card>

  <Card title="API keys" href="/docs/concepts/integrations/api-keys">
    Authentication, API versions and rate limits.
  </Card>

  <Card title="API: Articles" href="/docs/api-reference/articles">
    Every stock item endpoint and its schema.
  </Card>
</CardGroup>
