# Product setup

Build complete product records with packaging, attributes, relationships, identifiers, and documents.

Canonical HTML: https://developer.shelfcycle.com/guides/product-setup

Build a complete product record from packaging and code attributes, then connect the supplier names, customer part numbers, documents, and lifecycle actions that keep the catalog useful.

Public API v1.39.1 supports a connected product setup flow and product code renaming. A workflow can resolve or create packaging, read the organization's product code attributes, create a product with typed assignments, maintain its supplier and customer-facing identities, rename its code while preserving connected history, and reconcile the saved catalog record before continuing.

## Setup flow

1. Call `GET /me` and confirm the matching product, product-family, and write-readiness capabilities.
2. Resolve the product family, packaging, and code-attribute values the product needs.
3. Create packaging when an exact reusable reference does not already exist.
4. Preview and create the product with its complete `attributeAssignments` set.
5. Rename the product code when needed, then add supplier relationships, aliases, and customer part numbers with the latest product `updatedAt` value.
6. Follow the product detail's `catalog.links` to reconcile every saved relationship.
7. Attach product documents or assign the current SDS when needed.

## Endpoint map

| Job | Endpoints |
| --- | --- |
| Product families | `GET /product-families`, `GET /product-families/{id}`, `POST /product-families`, `PATCH /product-families/{id}` |
| Packaging | `GET /reference/product-packaging`, `POST /product-packaging`, `GET /product-packaging/{id}` |
| Code attributes | `GET /product-code-attributes`, `GET /product-code-attributes/{attributeId}/values` |
| Products and assignments | `POST /products`, `GET /products/{id}`, `PATCH /products/{id}`, `GET /products/{id}/attribute-assignments` |
| Supplier relationships | `GET` and `POST /products/{id}/suppliers`, `DELETE /products/{id}/suppliers/{supplierId}` |
| Product aliases | `GET` and `POST /products/{id}/aliases`, `PATCH` and `DELETE /products/{id}/aliases/{aliasId}` |
| Product-family aliases | `GET` and `POST /product-families/{id}/aliases`, `PATCH` and `DELETE /product-families/{id}/aliases/{aliasId}` |
| Customer part numbers | `GET` and `POST /products/{id}/customer-part-numbers`, `PATCH` and `DELETE /products/{id}/customer-part-numbers/{customerId}` |
| Product lifecycle | `POST /products/{id}/archive`, `POST /products/{id}/unarchive` |

Product setup uses `products:read` and `products:write` together with the acting person's current Product access. A supplier relationship or supplier-specific alias also requires `suppliers:read`. A customer part number or customer-specific alias also requires `customers:read`.

## Resolve or create packaging

Search the active packaging directory by text, exact name, or unit of measure. Continue larger results with `meta.nextCursor` by itself.

```bash
curl "$SHELFCYCLE_API_BASE_URL/reference/product-packaging?name=55%20gallon%20drum&limit=25" \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY"
```

When the product needs a new packaging reference, preview the exact definition and then create it with the same body and `Idempotency-Key`.

```bash
curl "$SHELFCYCLE_API_BASE_URL/product-packaging?dryRun=true" \
  -X POST \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: catalog:55-gallon-steel-drum" \
  -d '{
    "name": "55 gallon steel drum",
    "unitOfMeasure": "GALLON",
    "shape": "cylindrical",
    "tareWeightLbs": "42",
    "capacity": "55",
    "material": "steel"
  }'
```

Execution returns the packaging `id`, saved physical details, archive state, `updatedAt`, and `idempotencyStatus`. Use `GET /product-packaging/{id}` for exact readback.

## Resolve code attributes

List the available attribute definitions, then list the accepted values for each `SELECT` or `MULTISELECT` definition.

```bash
curl "$SHELFCYCLE_API_BASE_URL/product-code-attributes?limit=50" \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY"
```

```bash
curl "$SHELFCYCLE_API_BASE_URL/product-code-attributes/grade-attribute-id/values?limit=50" \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY"
```

Each definition reports its type as `SELECT`, `MULTISELECT`, `FREE_TEXT`, or `BOOLEAN`. Build each assignment with the matching value field:

```json
[
  { "attributeId": "grade-attribute-id", "optionIds": ["technical-grade-option-id"] },
  { "attributeId": "market-attribute-id", "optionIds": ["industrial-option-id", "coatings-option-id"] },
  { "attributeId": "color-attribute-id", "textValue": "Clear" },
  { "attributeId": "kosher-attribute-id", "booleanValue": true }
]
```

## Create a complete product

Include `attributeAssignments` in the product create body so the accepted product identity and its assignments are saved together.

```bash
curl "$SHELFCYCLE_API_BASE_URL/products?dryRun=true" \
  -X POST \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: source-catalog:ipa-55-drum" \
  -d '{
    "code": "IPA-55-DRUM",
    "externalId": "source-product-913",
    "productFamilyId": "product-family-id",
    "packagingId": "packaging-id",
    "packagingType": "FIXED",
    "supplierType": "VARIABLE",
    "qty": "55",
    "casNumber": "67-63-0",
    "unNumber": "1219",
    "properShippingName": "Isopropanol",
    "hazardClass": ["3"],
    "packingGroup": "II",
    "attributeAssignments": [
      { "attributeId": "grade-attribute-id", "optionIds": ["technical-grade-option-id"] },
      { "attributeId": "color-attribute-id", "textValue": "Clear" }
    ]
  }'
```

Inspect `data.status`, `checks`, and `duplicateCandidates`. When the preview is ready, remove `dryRun=true` and submit the same body and key. The response returns the normalized accepted `attributeAssignments` set, including for a key that can write the product but cannot separately read it.

For an update, fetch the product first and send its latest `updatedAt` as `If-Match`. If `attributeAssignments` is present, it replaces the complete assignment set. Omit the field to preserve the current assignments, or send `[]` to clear them.

## Rename a product code

API v1.39.1 lets an existing product take a new unique code while its connected sales, purchasing, inventory, pricing, cost, and accounting history stays attached to the same product.

Preview the rename with the current product timestamp:

```bash
curl "$SHELFCYCLE_API_BASE_URL/products/product-id?dryRun=true" \
  -X PATCH \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY" \
  -H "Content-Type: application/json" \
  -H 'If-Match: "2026-09-13T14:30:00.000Z"' \
  -d '{ "code": "IPA-55-DRUM-NEW" }'
```

When the preview is ready, remove `dryRun=true` and send the same update with the latest `updatedAt`. Product codes are unique across active and archived products; a matching code returns `duplicate_product`. Other structural product changes continue to follow their existing readiness checks.

The response returns the renamed product and its next `updatedAt`. Read `GET /products/{id}` to confirm the new code before continuing with another catalog change.

## Add catalog relationships

Every relationship write starts from the latest owner `updatedAt`. Use the new timestamp returned by one relationship change before applying the next.

### Connect a supplier

```bash
curl "$SHELFCYCLE_API_BASE_URL/products/product-id/suppliers" \
  -X POST \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "If-Match: 2026-09-10T14:30:00.000Z" \
  -d '{ "supplierId": "supplier-id" }'
```

An exact repeat reports `changed: false`. This relationship makes the supplier available for the product; Current Source selection remains under `GET` and `PUT /products/{id}/current-cost-source`.

### Add the names each relationship uses

Aliases can be organization-wide or specific to one customer or supplier. Use the same alias endpoints for products and product families.

```bash
curl "$SHELFCYCLE_API_BASE_URL/products/product-id/aliases" \
  -X POST \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: source-catalog:product-id:ipa" \
  -H "If-Match: 2026-09-10T14:31:00.000Z" \
  -d '{
    "value": "IPA",
    "context": { "type": "global" }
  }'
```

Customer and supplier contexts use `{ "type": "customer", "customerId": "customer-id" }` or `{ "type": "supplier", "supplierId": "supplier-id" }`. An alias can change its display value through `PATCH`; changing its owner or context uses remove and create.

### Add a customer part number

```bash
curl "$SHELFCYCLE_API_BASE_URL/products/product-id/customer-part-numbers" \
  -X POST \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "If-Match: 2026-09-10T14:32:00.000Z" \
  -d '{
    "customerId": "customer-id",
    "partNo": "CUST-IPA-55"
  }'
```

An exact repeat reports no change. Update the value through `PATCH /products/{id}/customer-part-numbers/{customerId}`.

## Reconcile the saved catalog

`GET /products/{id}` returns a `catalog` block with the product family ID and external ID, packaging ID, archive state, and links to the relationship collections available to the current key.

```json
{
  "catalog": {
    "family": { "id": "product-family-id", "externalId": "family-ipa" },
    "packaging": { "id": "packaging-id" },
    "archived": false,
    "links": {
      "attributeAssignments": { "available": true, "method": "GET", "path": "/api/v1/products/product-id/attribute-assignments" },
      "suppliers": { "available": true, "method": "GET", "path": "/api/v1/products/product-id/suppliers" },
      "aliases": { "available": true, "method": "GET", "path": "/api/v1/products/product-id/aliases" },
      "customerPartNumbers": { "available": true, "method": "GET", "path": "/api/v1/products/product-id/customer-part-numbers" }
    }
  }
}
```

Follow the available links and continue each collection with its returned cursor until `hasMore` is false. Supplier rows show whether a relationship is the selected Current Source. Assignment rows preserve the definition type and selected value identity. Alias and part-number rows retain their customer or supplier context.

## Archive or restore a product

Preview the named action with `dryRun=true`, then apply it with the latest product timestamp in `If-Match`.

```bash
curl "$SHELFCYCLE_API_BASE_URL/products/product-id/archive?dryRun=true" \
  -X POST \
  -H "Authorization: Bearer $SHELFCYCLE_API_KEY"
```

The preview reports the current timestamp and any stock or inbound quantity that needs attention. Execution returns `archived`, `changed`, and the next `updatedAt` value. Restore uses the matching `/unarchive` endpoint. See [Archive and restore](/guides/archive-restore) for the shared lifecycle flow.

## Documents and SDS

Use the [Attached documents guide](/guides/attached-documents) to upload and attach product files. Assign the current SDS through `PUT /products/{id}/sds` with the latest product `updatedAt` as `If-Match`.

> **Information**: Keep each returned product timestamp with the completed step. It is the starting point for the next catalog relationship, document, or lifecycle change.
