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

# Bulk Import, Export, and Validation for Products

> Run large-scale product imports, updates, and deletes with Synq's async bulk job engine. Export your catalog and surface data quality issues at scale.

The bulk operations system lets you move large volumes of product data in and out of Synq without writing per-record API loops. A **bulk job** is a named, asynchronous task — identified by a `job_type` string — that accepts a JSON `payload` describing the work to be done (e.g., a list of products to import or a set of updates to apply). Jobs are created synchronously but processed asynchronously; poll the list endpoint to track their `status`. The **export** endpoint queues an asynchronous catalog export to Cloud Storage. The **validation** endpoint surfaces unresolved data quality issues flagged by the system against your products.

<Note>
  All requests require the standard headers:
  `Authorization: Bearer <token>`, `X-Tenant-ID`, and `X-Org-ID`.
</Note>

***

## Bulk jobs

### Bulk import workflow

<Steps>
  <Step title="Prepare your payload">
    Structure your product data as a JSON array. Each item should contain at minimum a `title`. Include `sku`, `description`, `category`, and `brand` fields for better catalog quality scores.

    ```json theme={null}
    {
      "job_type": "BULK_IMPORT",
      "payload": [
        {
          "title": "Merino Wool Crew Neck Sweater",
          "description": "100% Grade-A merino wool.",
          "category": "Knitwear",
          "brand": "NordicThread"
        },
        {
          "title": "Classic Oxford Button-Down",
          "description": "Egyptian cotton, slim fit.",
          "category": "Shirts",
          "brand": "HarbourLine"
        }
      ]
    }
    ```
  </Step>

  <Step title="Create the bulk job">
    POST your payload to the bulk jobs endpoint. The API validates that `payload` is valid JSON, creates the job with status `PENDING`, and returns the job record immediately.

    ```bash theme={null}
    curl -X POST https://api.synq.com/api/v1/pim/bulk-jobs \
      -H "Authorization: Bearer $TOKEN" \
      -H "X-Tenant-ID: $TENANT_ID" \
      -H "X-Org-ID: $ORG_ID" \
      -H "Content-Type: application/json" \
      -d '{
        "job_type": "BULK_IMPORT",
        "payload": [
          { "title": "Merino Wool Crew Neck Sweater", "category": "Knitwear" },
          { "title": "Classic Oxford Button-Down", "category": "Shirts" }
        ]
      }'
    ```
  </Step>

  <Step title="Store the job ID">
    From the response, save the `id` field. You need it to poll for status.

    ```json theme={null}
    {
      "id": "c1d2e3f4-0001-5a6b-7c8d-9e0f1a2b3c4d",
      "job_type": "BULK_IMPORT",
      "status": "PENDING",
      "total_items": null,
      "processed_items": null,
      "created_at": "2024-11-15T14:00:00Z"
    }
    ```
  </Step>

  <Step title="Poll for completion">
    List bulk jobs filtered by `type=BULK_IMPORT` and check the `status` and `processed_items` fields. Poll at a reasonable interval (every 5–30 seconds depending on job size).

    ```bash theme={null}
    curl "https://api.synq.com/api/v1/pim/bulk-jobs?type=BULK_IMPORT" \
      -H "Authorization: Bearer $TOKEN" \
      -H "X-Tenant-ID: $TENANT_ID" \
      -H "X-Org-ID: $ORG_ID"
    ```
  </Step>

  <Step title="Handle completion or failure">
    When `status` reaches `COMPLETED`, all items have been processed. If `status` is `FAILED`, check validation issues at `GET /api/v1/pim/validation` to identify which records caused errors.
  </Step>
</Steps>

***

### Create a bulk job

```bash theme={null}
POST /api/v1/pim/bulk-jobs
```

**Request body**

<ParamField body="job_type" type="string" required>
  The type of operation to perform. Supported values: `BULK_IMPORT`, `BULK_UPDATE`, `BULK_DELETE`.
</ParamField>

<ParamField body="payload" type="array | object" required>
  A valid JSON structure describing the work. For imports and updates, pass an array of product objects. For deletions, pass an array of product IDs. The value must be valid JSON.
</ParamField>

<CodeGroup>
  ```bash Bulk import theme={null}
  curl -X POST https://api.synq.com/api/v1/pim/bulk-jobs \
    -H "Authorization: Bearer $TOKEN" \
    -H "X-Tenant-ID: $TENANT_ID" \
    -H "X-Org-ID: $ORG_ID" \
    -H "Content-Type: application/json" \
    -d '{
      "job_type": "BULK_IMPORT",
      "payload": [
        { "title": "Merino Wool Sweater", "category": "Knitwear" },
        { "title": "Linen Shirt", "category": "Shirts" }
      ]
    }'
  ```

  ```bash Bulk update theme={null}
  curl -X POST https://api.synq.com/api/v1/pim/bulk-jobs \
    -H "Authorization: Bearer $TOKEN" \
    -H "X-Tenant-ID: $TENANT_ID" \
    -H "X-Org-ID: $ORG_ID" \
    -H "Content-Type: application/json" \
    -d '{
      "job_type": "BULK_UPDATE",
      "payload": [
        { "id": "018e7c3a-1f2b-7d4e-9a0c-3f5b6e7d8c90", "category": "Knitwear – Premium" },
        { "id": "018e7c3a-2a3b-4c5d-8e9f-0a1b2c3d4e5f", "category": "Shirts – Formal" }
      ]
    }'
  ```

  ```bash Bulk delete theme={null}
  curl -X POST https://api.synq.com/api/v1/pim/bulk-jobs \
    -H "Authorization: Bearer $TOKEN" \
    -H "X-Tenant-ID: $TENANT_ID" \
    -H "X-Org-ID: $ORG_ID" \
    -H "Content-Type: application/json" \
    -d '{
      "job_type": "BULK_DELETE",
      "payload": [
        { "id": "018e7c3a-1f2b-7d4e-9a0c-3f5b6e7d8c90" }
      ]
    }'
  ```
</CodeGroup>

**Response** `200 OK`

```json theme={null}
{
  "id": "c1d2e3f4-0001-5a6b-7c8d-9e0f1a2b3c4d",
  "org_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "tenant_id": "f9e8d7c6-b5a4-3210-fedc-ba9876543210",
  "job_type": "BULK_IMPORT",
  "status": "PENDING",
  "payload_json": "[{\"title\":\"Merino Wool Sweater\",...}]",
  "total_items": null,
  "processed_items": null,
  "created_by": "d1e2f3a4-b5c6-7890-abcd-ef1234567890",
  "created_at": "2024-11-15T14:00:00Z",
  "updated_at": "2024-11-15T14:00:00Z"
}
```

<ResponseField name="status" type="string">
  Initial status is always `PENDING`. Transitions to `PROCESSING`, then `COMPLETED` or `FAILED`.
</ResponseField>

<ResponseField name="payload_json" type="string">
  The submitted payload stored as a JSON string. This is the raw content you sent in the `payload` field of the request.
</ResponseField>

<ResponseField name="total_items" type="integer | null">
  Total number of records in the job. Populated once the job starts processing.
</ResponseField>

<ResponseField name="processed_items" type="integer | null">
  Number of records successfully processed so far. Use alongside `total_items` to calculate progress percentage.
</ResponseField>

***

### List bulk jobs

Retrieve bulk jobs filtered by type, ordered by most recently created first.

```bash theme={null}
GET /api/v1/pim/bulk-jobs
```

**Query parameters**

<ParamField query="type" type="string">
  Filter by `job_type`. Defaults to `BULK_UPDATE` if omitted. Pass `BULK_IMPORT` or `BULK_DELETE` as needed.
</ParamField>

```bash theme={null}
curl "https://api.synq.com/api/v1/pim/bulk-jobs?type=BULK_IMPORT" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Tenant-ID: $TENANT_ID" \
  -H "X-Org-ID: $ORG_ID"
```

**Response** `200 OK`

```json theme={null}
[
  {
    "id": "c1d2e3f4-0001-5a6b-7c8d-9e0f1a2b3c4d",
    "org_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "tenant_id": "f9e8d7c6-b5a4-3210-fedc-ba9876543210",
    "job_type": "BULK_IMPORT",
    "status": "COMPLETED",
    "total_items": 250,
    "processed_items": 250,
    "created_at": "2024-11-15T14:00:00Z",
    "updated_at": "2024-11-15T14:03:42Z"
  },
  {
    "id": "c1d2e3f4-0002-5a6b-7c8d-9e0f1a2b3c4d",
    "org_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "tenant_id": "f9e8d7c6-b5a4-3210-fedc-ba9876543210",
    "job_type": "BULK_IMPORT",
    "status": "FAILED",
    "total_items": 50,
    "processed_items": 47,
    "created_at": "2024-11-14T09:00:00Z",
    "updated_at": "2024-11-14T09:01:10Z"
  }
]
```

***

## Export products

Queue an asynchronous export of your entire product catalog. When the export completes, the file is placed in Cloud Storage and you are notified (via webhook or email, depending on your tenant configuration).

```bash theme={null}
POST /api/v1/pim/export
```

```bash theme={null}
curl -X POST https://api.synq.com/api/v1/pim/export \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Tenant-ID: $TENANT_ID" \
  -H "X-Org-ID: $ORG_ID"
```

**Response** `202 Accepted`

```json theme={null}
{
  "status": "success",
  "message": "Export job queued. You will be notified when the file is ready in Cloud Storage."
}
```

<Note>
  The export endpoint returns `202 Accepted` immediately — the actual file generation happens asynchronously. Do not poll this endpoint; wait for the delivery notification instead.
</Note>

***

## Validation issues

Synq continuously evaluates your catalog against data quality rules — missing descriptions, duplicate SKUs, invalid barcodes, low data quality scores — and records each finding as a validation issue. Use this endpoint to retrieve all unresolved issues for your tenant so your team can triage and fix them.

```bash theme={null}
GET /api/v1/pim/validation
```

```bash theme={null}
curl https://api.synq.com/api/v1/pim/validation \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Tenant-ID: $TENANT_ID" \
  -H "X-Org-ID: $ORG_ID"
```

**Response** `200 OK`

```json theme={null}
[
  {
    "id": "v1d2e3f4-0001-5a6b-7c8d-9e0f1a2b3c4d",
    "org_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "tenant_id": "f9e8d7c6-b5a4-3210-fedc-ba9876543210",
    "product_id": "018e7c3a-1f2b-7d4e-9a0c-3f5b6e7d8c90",
    "issue_type": "MISSING_DESCRIPTION",
    "severity": "WARNING",
    "message": "Product has no description. Add one to improve SEO and customer experience.",
    "resolved": false,
    "created_at": "2024-11-15T08:00:00Z",
    "updated_at": "2024-11-15T08:00:00Z"
  },
  {
    "id": "v1d2e3f4-0002-5a6b-7c8d-9e0f1a2b3c4d",
    "org_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "tenant_id": "f9e8d7c6-b5a4-3210-fedc-ba9876543210",
    "product_id": "029f8d4b-3c4d-5e6f-a1b2-c3d4e5f6a7b8",
    "issue_type": "DUPLICATE_SKU",
    "severity": "ERROR",
    "message": "SKU 'MWS-RED-S' is used by more than one variant in this tenant.",
    "resolved": false,
    "created_at": "2024-11-15T08:01:00Z",
    "updated_at": "2024-11-15T08:01:00Z"
  }
]
```

<ResponseField name="issue_type" type="string">
  Machine-readable code identifying the class of issue (e.g., `MISSING_DESCRIPTION`, `DUPLICATE_SKU`, `INVALID_BARCODE`).
</ResponseField>

<ResponseField name="severity" type="string">
  Either `WARNING` (catalog quality degradation) or `ERROR` (blocks publishing or export).
</ResponseField>

<ResponseField name="resolved" type="boolean">
  The endpoint returns only issues where `resolved = false`. Once your team fixes the underlying data, the issue is automatically resolved and disappears from this list.
</ResponseField>

<Warning>
  `ERROR`-severity issues may block products from being included in exports or published to sales channels. Resolve all `ERROR` issues before triggering an export job.
</Warning>

***

## PIM audit log

Synq records every create, update, and delete operation performed on PIM resources as an immutable audit event. Retrieve the most recent 100 events for your tenant to track who changed what and when.

```bash theme={null}
GET /api/v1/pim/audit
```

```bash theme={null}
curl https://api.synq.com/api/v1/pim/audit \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Tenant-ID: $TENANT_ID" \
  -H "X-Org-ID: $ORG_ID"
```

**Response** `200 OK`

```json theme={null}
[
  {
    "id": "e1d2f3a4-0001-5b6c-7d8e-9f0a1b2c3d4e",
    "org_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "tenant_id": "f9e8d7c6-b5a4-3210-fedc-ba9876543210",
    "actor_email": "alice@example.com",
    "action": "product.created",
    "entity_type": "product",
    "entity_id": "018e7c3a-1f2b-7d4e-9a0c-3f5b6e7d8c90",
    "details": {},
    "ip_address": "203.0.113.42",
    "created_at": "2024-11-15T10:00:00Z"
  }
]
```

<ResponseField name="actor_email" type="string">
  Email address of the user who performed the action.
</ResponseField>

<ResponseField name="action" type="string">
  Description of the operation performed (e.g., `product.created`, `variant.updated`).
</ResponseField>

<ResponseField name="entity_type" type="string">
  The type of resource that was affected (e.g., `product`, `variant`, `brand`).
</ResponseField>

<ResponseField name="entity_id" type="string">
  UUID of the specific resource that was affected.
</ResponseField>
