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

# Bulk Product Import API

> Queue CSV bulk product imports and poll job status.

Use `POST /api/v1/products/bulk-import` to queue a CSV bulk product import for your organization. The API accepts either a CSV file upload (`multipart/form-data`) or a JSON payload with inline `csvText` / `documentId`. It returns a `bulk_...` job ID that you poll with `GET /api/v1/jobs/{jobId}`.

Interactive reference: [API docs](/api-reference)

## Authentication

Send an OmniCommerce API key or OAuth client token with the `catalog:write` scope:

```http theme={null}
Authorization: Bearer $OMNI_API_KEY
```

API keys and OAuth tokens are bound to a single organization, so you do **not** need to send `organizationId` in the request body or multipart form fields.

Session-authenticated requests (browser cookie) must include `organizationId` in the request body or multipart form fields.

## 1. Upload a CSV file

```bash theme={null}
curl -X POST "https://your-domain.com/api/v1/products/bulk-import" \
  -H "Authorization: Bearer $OMNI_API_KEY" \
  -F "file=@products.csv" \
  -F 'targetMarketplaces=["shopee"]' \
  -F "targetCountries=sg,my" \
  -F 'fieldMapping={"sku":"SKU","title":"Product Name","images":"Image URLs"}'
```

CSV uploads must be `.csv` files up to 2MB.

## 2. Or send JSON programmatically

```http theme={null}
POST /api/v1/products/bulk-import
Content-Type: application/json
```

```json theme={null}
{
  "csvText": "sku,title,price,images\nSKU-1,Sample product,19.90,https://cdn.example.com/sku-1.jpg\n",
  "targetMarketplaces": ["shopee"],
  "targetCountries": ["sg"],
  "fieldMapping": {
    "sku": "sku",
    "title": "title",
    "price": "price",
    "images": "images"
  }
}
```

For session-authenticated calls, add `"organizationId": "org_1"` to the JSON body or multipart form.

You can also pass `documentId` instead of `csvText` when the CSV is already stored in the organization's document library.

## Accepted response

```json theme={null}
{
  "ok": true,
  "apiVersion": "developer_v1",
  "operationId": "8f7e8f8f-2b9e-46e6-a2c0-50d0f4612b2d",
  "status": "accepted",
  "jobId": "bulk_7f4f5f0d-2f0a-4f0a-9a2f-8d2f0a7f4f5f",
  "links": {
    "job": "/api/v1/jobs/bulk_7f4f5f0d-2f0a-4f0a-9a2f-8d2f0a7f4f5f"
  },
  "recommendations": [
    "Poll GET /api/v1/jobs/{jobId}?organizationId=org_1 for row progress and completion."
  ],
  "data": {
    "rowCount": 120,
    "fileName": "products.csv",
    "targetMarketplaces": ["shopee"],
    "targetCountries": ["sg"],
    "fieldMapping": {
      "sku": "sku",
      "title": "title"
    },
    "counts": {
      "pending": 120
    }
  }
}
```

## 3. Poll job status

```http theme={null}
GET /api/v1/jobs/bulk_7f4f5f0d-2f0a-4f0a-9a2f-8d2f0a7f4f5f?organizationId=org_1
Authorization: Bearer $OMNI_API_KEY
```

The `data` payload includes row counts (`pending`, `enriching`, `applied`, `failed`, `skipped`), progress fields, and the underlying import status.

## 4. Cancel an in-flight import (optional)

```http theme={null}
POST /api/v1/jobs/bulk_7f4f5f0d-2f0a-4f0a-9a2f-8d2f0a7f4f5f/cancel
Authorization: Bearer $OMNI_API_KEY
Content-Type: application/json

{
  "organizationId": "org_1"
}
```

Rows already applied remain in the catalog. Remaining queued rows are skipped.

## Multipart form fields

| Field                        | Required            | Description                                                     |
| ---------------------------- | ------------------- | --------------------------------------------------------------- |
| `file`                       | Yes for file upload | CSV file                                                        |
| `targetMarketplaces`         | Yes                 | JSON array or comma-separated list                              |
| `targetCountries`            | Yes                 | JSON array or comma-separated list                              |
| `organizationId`             | Session only        | Organization scope for browser session auth. Omit for API keys. |
| `fieldMapping`               | No                  | JSON object string                                              |
| `marketplaceStoreSelections` | No                  | JSON object string                                              |
| `selectedRowIndexes`         | No                  | JSON array string                                               |
| `workflow`                   | No                  | JSON workflow options                                           |
| `lane`                       | No                  | `standard` or `enterprise`                                      |
| `dedupPolicy`                | No                  | Duplicate SKU handling policy                                   |
| `autoApproveThreshold`       | No                  | Number from 0 to 1, or `null`                                   |
| `model`                      | No                  | Enrichment model override                                       |

## Job ID format

Developer bulk imports return IDs with the `bulk_` prefix, for example `bulk_7f4f5f0d-2f0a-4f0a-9a2f-8d2f0a7f4f5f`. Use the same job polling and cancellation endpoints as single-product `prod_...` jobs and Look `look_...` jobs.

## Errors

* `400` - invalid JSON, missing CSV source, invalid field mapping, or CSV validation failure
* `401` - missing or invalid Bearer token
* `403` - token missing `catalog:write` or wrong organization
* `413` - multipart body or CSV file too large
