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

# Product Import Developer Guide

> Import product data through CSV upload to start enrichment.

## Prerequisites

In order to upload products, you must already have created your category taxonomy. For information on creating categories in the Product Agent UI, see [Creating Categories](/product-agent/admin-settings/settings/creating-categories).

## Step 1: Postman Environment Setup

This part of the guide walks you through setting up a Postman environment to interact with the Product Agent API.

### Create a new environment

1. Open Postman.

2. Click **Environments** in the sidebar.

3. Click **+** to create a new environment.

   Provide a name such as *Product Agent - Prod*.

### Add the environment variables

Add the following variables to your environment.

<Note> `access_token` and the various defined IDs will be empty initially — they will be populated after making API calls. </Note>

| Variable              | Description                                                            | Example                                          |
| --------------------- | ---------------------------------------------------------------------- | :----------------------------------------------- |
| `baseUrl`             | Base URL for the API                                                   | `https://commerceos.aiagents.fabric.inc/api`     |
| `authUrl`             | Auth URL for the API                                                   | `https://commerceos.aiagents.fabric.inc`         |
| `access_token`        | Token used for authenticated requests                                  | `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...`        |
| `attributeWorkflowId` | ID of the attribute workflow (used for status endpoints)               | `a1462a91-f733-45fe-993b-5d0353f33ee3`           |
| `artifactId`          | ID of an uploaded catalog artifact (returned by the artifact upload)   | `art_95c650ded4824dc79bb6df16427e4a10`           |
| `workflowId`          | ID of an optimization workflow (returned by the workflow create call)  | `9349773c-27fe-4d4f-a093-a5793dff8702`           |
| `clientId`            | Client ID provided by fabric. Used to create an access token           | `svc_peyFD9BXPRrZymhjJtFuYr7L3Ai`                |
| `clientSecret`        | Client secret provided by fabric. Used to create an access token       | `cw_GWve_9oI_aJKd-xcE7uuZJpr-WqfnRpDPznGNVI-fOc` |
| `domain`              | Brand's domain name (must be a `.com` value scoped to a brand you own) | `vessel.com`                                     |

## Step 2: Authentication

This part of the guide walks you through authenticating with the Product Agent API and storing your access token in Postman for future requests.

### Create and set the access token

To authenticate, send a POST request to the token endpoint using your `clientId` and `clientSecret`. If you do not have these credentials, contact fabric support.

<Note> Tokens expire every 60 minutes. </Note>

```shell theme={null}
curl --location '{{authUrl}}/platform/v1/auth/token' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--data '{
  "grant_type": "client_credentials",
  "client_id": "{{clientId}}",
  "client_secret": "{{clientSecret}}"
  }'
```

<Accordion title="Example Response:">
  ```json theme={null}
  {
      "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzdmNfcGV5SkQ5DlhQUnRadW1oakp0RnVZcjdMbEFvIiwidHlwZ56UIkjnZpY2UiLCJjb21wYW55X2lkIjoiNjhiNzI3NjhlYzc1NGVjNzA2MjhkNTM5Iiwic2NvcGVzIjpbXSwiYnJhbmRfaWRzIjpbImNvbnRhaW5lcnN0b3JlLmNvbSJdLCJqdGkiOiI5MGQ3YjQ0NC03MTQxLTRhMDUtYWM4MC03OGJjZTMxZGI5OGUiLCJpYXQiOjE3NzQ1NDM2NDEsImV4cCI6MTc3NDU0NzI0MX0.kc9RQSirU2vIVl5oRTmuEBoaF1tmAeYB3KH87KAHJpM",
      "token_type": "bearer",
      "expires_in": 3600,
      "scope": ""
  }
  ```
</Accordion>

To automatically save the `access_token` for future requests:

1. Go to the **Scripts** tab in your Postman request.

2. Select **Post-response**.

3. Add the following script:

   `pm.environment.set("access_token", pm.response.json().access_token);`

With this script, the `access_token` environment variable is automatically updated each time a request to the endpoint is made.

## Step 3 (Optional): Import Attribute Definitions

You can optionally import attribute definitions for enrichment and mapping workflows. For information on creating attributes in the Product Agent UI, see [Creating Attributes](/product-agent/admin-settings/settings/creating-attributes).

This endpoint accepts a CSV file and creates an import job you can monitor using the returned `import_id`. We will save this to our environment as `attributeWorkflowId`.

### Expected CSV columns for each attribute definition

| Column             | Required | Description                                           |
| :----------------- | -------: | :---------------------------------------------------- |
| `attribute_name`   |      Yes | Attribute display name                                |
| `attribute_key`    |       No | Machine-readable key                                  |
| `description`      |      Yes | Attribute description                                 |
| `data_type`        |       No | Attribute data type                                   |
| `scope`            |       No | Attribute scope                                       |
| `enum_values`      |       No | Allowed values, pipe-delimited, for example `S\|M\|L` |
| `allow_ai_content` |       No | Allow AI to generate values (`true` or `false`)       |
| `source`           |       No | `MERCHANT` or `GOLD_STANDARD`                         |
| `guideline_reason` |       No | Guidance for AI enrichment                            |

### Request

```shell theme={null}
curl --location '{{baseUrl}}/v2/attributes/import' \
--header 'Authorization: {{access_token}}' \
--header 'domain: {{domain}}' \
--form 'file=@"/D:/Demo CSV files/attributes.csv"'
```

To automatically save the `attributeWorkflowId` for future requests:

1. Go to the **Scripts** tab in your Postman request.

2. Select **Post-response**.

3. Add the following script:

   `pm.environment.set("attributeWorkflowId", pm.response.json().import_id);`

<Accordion title="Example Response:">
  ```json theme={null}
  {
      "import_id": "e2716438-b763-4be8-82d2-36abe0cb92b1",
      "name": "attribute.csv",
      "status": "PENDING",
      "total_rows": 36,
      "processed_rows": 0,
      "created_count": 0,
      "updated_count": 0,
      "skipped_count": 0,
      "failed_count": 0,
      "input_filename": "attribute.csv",
      "input_file_url": null,
      "error_file_url": null,
      "error_message": null,
      "errors": [],
      "started_at": null,
      "completed_at": null,
      "created_at": "2026-03-12T18:04:03.879876Z"
  }
  ```
</Accordion>

### Check the import status

After the upload succeeds, use the saved `attributeWorkflowId` to check workflow status with:

```shell theme={null}
curl --location '{{baseUrl}}/v2/attributes/import/{{attributeWorkflowId}}' \
--header 'Authorization: {{access_token}}' \
--header 'domain: {{domain}}'
```

<Accordion title="Example Response:">
  ```json theme={null}
  {
      "import_id": "e2716438-b763-4be8-82d2-36abe0cb92b1",
      "name": "attribute.csv",
      "status": "COMPLETED",
      "total_rows": 36,
      "processed_rows": 36,
      "created_count": 35,
      "updated_count": 0,
      "skipped_count": 0,
      "failed_count": 1,
      "input_filename": "attribute_test.csv",
      "input_file_url": "https://product-agent-data-prod-ue2.s3.amazonaws.com/attribute/vessel/e2716438-b763-4be8-82d2-36abe0cb92b1.csv...",
      "error_file_url": "https://product-agent-data-prod-ue2.s3.amazonaws.com/attribute/vessel/e2716438-b763-4be8-82d2-36abe0cb92b1_errors.csv?X-...",
      "error_message": null,
      "errors": [
          "Row 35: invalid data_type 'BOOLREAN'"
      ],
      "started_at": "2026-03-12T18:04:03.998437Z",
      "completed_at": "2026-03-12T18:04:04.724584Z",
      "created_at": "2026-03-12T18:04:03.879876Z"
  }
  ```
</Accordion>

In this example, an error was flagged in the upload. You can resolve and publish updates to just that row or re-upload the entire file. Rows with errors are skipped.

Once completed, you can review the attribute definitions you uploaded in the UI.

1. Log in to Product Agent.

2. In the left nav, click **Settings**.

   The **Settings** menu is displayed.

3. Click **Taxonomy**.

   The **Categories** tab is displayed by default.

4. Click **Attributes**.

Here you can review your attribute definitions.

## Step 4: Import Your Products

After the categories and attribute definitions have been imported, products can be uploaded. The product import flow consists of three sequential API calls:

1. **Artifact Upload.** Submit the CSV to the artifacts endpoint and store the returned `artifact_id`.
2. **Optimize Workflow.** Create a workflow that processes the artifact and store the returned workflow `id`.
3. **Get Workflow Status.** Poll the workflow until it reaches a review gate or a terminal status.

### Expected CSV columns

The catalog file must be UTF-8 encoded, comma-delimited, and include a header row. The following table lists the supported columns.

| Column             | Required    | Description                                                                                                                                                                                        |
| :----------------- | :---------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title`            | Yes         | Product display name as shown on the product detail page.                                                                                                                                          |
| `sku`              | Yes         | Unique product identifier within the brand catalog.                                                                                                                                                |
| `description`      | Yes         | Full product description from the product detail page.                                                                                                                                             |
| `category_id`      | Conditional | The brand's category identifier. Each row must provide either `category_id` or `breadcrumb`.                                                                                                       |
| `breadcrumb`       | Conditional | Full category hierarchy, separated by `>`. Example: `Mens > Clothing > Pants`. Each row must provide either `breadcrumb` or `category_id`.                                                         |
| `category`         | No          | Free-text category label. Used as a hint when neither `category_id` nor `breadcrumb` resolves to a known category.                                                                                 |
| `product_group_id` | No          | Identifier shared by product variants that belong to the same family (for example, multiple SKUs of the same shirt in different colors).                                                           |
| `price`            | No          | Numeric price value for the product.                                                                                                                                                               |
| `currency`         | No          | Currency code corresponding to `price`.                                                                                                                                                            |
| `url`              | No          | Canonical product detail page URL.                                                                                                                                                                 |
| `images`           | No          | One or more image URLs, separated by the pipe character (`\|`). Example: `https://example.com/a.jpg\|https://example.com/b.jpg`.                                                                   |
| `attribute.<name>` | No          | Custom attribute value. Free-form string, single value per cell. The `<name>` segment must match an attribute key configured for the brand (for example, `attribute.color`, `attribute.material`). |

### Artifact Upload

Submit the CSV to the artifacts endpoint. The response includes an `artifact_id` that is stored as `artifactId` and referenced when creating the workflow.

```shell theme={null}
curl --location '{{baseUrl}}/v2/optimize/artifacts' \
--header 'Authorization: Bearer {{access_token}}' \
--header 'domain: {{domain}}' \
--form 'file=@"/path/to/products.csv"' \
--form 'kind=csv'
```

To automatically save the `artifactId` for future requests:

1. Go to the **Scripts** tab in your Postman request.

2. Select **Post-response**.

3. Add the following script:

   `pm.environment.set("artifactId", pm.response.json().artifact_id);`

<Accordion title="Example Response:">
  ```json theme={null}
  {
    "artifact_id": "art_95c650ded4824dc79bb6df16427e4a10",
    "kind": "csv",
    "source": "upload",
    "bytes": 45678,
    "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
    "content_type": "text/csv",
    "uri": "s3://product-agent-data-prod-ue2/optimize-artifacts/svc_yourClientId/art_95c650ded4824dc79bb6df16427e4a10/products.csv",
    "original_filename": "products.csv",
    "brand_id": "691df5949676c8e0b1d7b6b3",
    "created_at": "2026-05-15T10:30:00Z"
  }
  ```
</Accordion>

For full request and response details, see [Upload artifact](/product-agent/api-reference/artifacts/upload-artifact).

### Optimize Workflow

Create an optimization workflow that processes the uploaded artifact. Store the returned workflow `id` as `workflowId`.

```shell theme={null}
curl --location '{{baseUrl}}/v2/optimize/workflows' \
--header 'Authorization: Bearer {{access_token}}' \
--header 'domain: {{domain}}' \
--header 'Content-Type: application/json' \
--data '{
  "input": {
    "artifact": { "artifact_id": "{{artifactId}}" }
  },
  "name": "Spring 2026 catalog enrichment",
  "origin": "API"
}'
```

To automatically save the `workflowId` for future requests:

1. Go to the **Scripts** tab in your Postman request.

2. Select **Post-response**.

3. Add the following script:

   `pm.environment.set("workflowId", pm.response.json().id);`

<Accordion title="Example Response:">
  ```json theme={null}
  {
    "id": "9349773c-27fe-4d4f-a093-a5793dff8702",
    "brand_id": "691df5949676c8e0b1d7b6b3",
    "type": "OPTIMIZE",
    "origin": "API",
    "workflow_type": null,
    "status": "PENDING",
    "current_step": null,
    "current_hitl_gate": null,
    "name": "Spring 2026 catalog enrichment",
    "tags": [],
    "started_at": null,
    "completed_at": null,
    "last_error": null,
    "retry_count": 0,
    "created_at": "2026-05-15T10:35:00Z",
    "updated_at": "2026-05-15T10:35:00Z"
  }
  ```
</Accordion>

For full request and response details, see [Create workflow](/product-agent/api-reference/optimize/create-workflow).

### Get Workflow Status

Poll the workflow until `status` reports `COMPLETED`. A `status` of `REVIEW_PENDING` indicates that the workflow is paused at a human-in-the-loop gate; the `current_hitl_gate` field identifies the active gate.

<Note>Review gates are approved in the [CommerceOS](https://commerceos.fabric.inc) web application, not through the API. When polling returns `status: REVIEW_PENDING`, direct the assigned reviewer to CommerceOS to evaluate and approve the gate. Once the reviewer acts in the UI, the workflow resumes automatically and subsequent polls reflect the new state.</Note>

```shell theme={null}
curl --location '{{baseUrl}}/v2/optimize/workflows/{{workflowId}}' \
--header 'Authorization: Bearer {{access_token}}' \
--header 'domain: {{domain}}'
```

<Accordion title="Example Response:">
  ```json theme={null}
  {
    "id": "9349773c-27fe-4d4f-a093-a5793dff8702",
    "brand_id": "691df5949676c8e0b1d7b6b3",
    "type": "OPTIMIZE",
    "origin": "API",
    "workflow_type": null,
    "status": "REVIEW_PENDING",
    "current_step": "TAXONOMY_MAP",
    "current_hitl_gate": "TAXONOMY_REVIEW",
    "name": "Spring 2026 catalog enrichment",
    "tags": [],
    "started_at": "2026-05-15T10:35:12Z",
    "completed_at": null,
    "last_error": null,
    "retry_count": 0,
    "progress": {
      "current_phase": "taxonomy",
      "current_step": "TAXONOMY_MAP",
      "current_gate": "TAXONOMY_REVIEW",
      "phases": {
        "validation": { "status": "COMPLETED" },
        "taxonomy":   { "status": "REVIEW_PENDING" },
        "enrichment": { "status": "NOT_STARTED" },
        "publish":    { "status": "NOT_STARTED" }
      }
    },
    "created_at": "2026-05-15T10:35:00Z",
    "updated_at": "2026-05-15T10:42:08Z"
  }
  ```
</Accordion>

The `status` value progresses through `PENDING`, then `RUNNING` or `IN_PROGRESS`, then `REVIEW_PENDING` when paused at a human-in-the-loop gate, and ultimately `COMPLETED`. `FAILED` and `CANCELLED` are terminal alternatives. For the complete status and gate vocabulary, see [Get workflow status](/product-agent/api-reference/optimize/get-workflow).

Once the workflow reaches `COMPLETED`, the products appear in Activate organized by category structure. For example, sweaters appear under *Tops > Sweaters* and t-shirts under *Tops > T-Shirts*.
