# Prodotti

> Elenco, ricerca, lettura e creazione o aggiornamento di prodotti, con prezzi di lista o qualsiasi prezzo del cliente.

Un prodotto ha un SKU, un prezzo di listino e un marchio, una categoria, uno stato di magazzino e dettagli aggiuntivi opzionali. I prezzi vengono sempre calcolati per voi: consulta [Prezzi](/docs/api/prices).

I prodotti possono anche avere `familyId` e `orderStep` (valore predefinito 1). Una lettura di un singolo prodotto include metadati opzionali `family` con il suo nome, i nomi degli attributi delle varianti e i valori predefiniti. Un prezzo configurato contiene `orderingFields`, `configurationRequired`, `definitionVersion` e un prezzo clienti opzionale `quantityBreaks`. I campi di ordinamento sono input dell'acquirente; `details` rimangono fatti del prodotto.

**POST** `/api/v1/products/{id}/quote`

Questa operazione in sola lettura accetta `{ "customer": "ACME01", "qty": 3, "configuration": { "length": 2.75, "reference": "Kitchen" } }`. `customer` è opzionale per il listino prezzi del personale. Le chiavi in sola lettura possono utilizzarla. Restituisce `data.productId`, `qty`, `fields` validati, `definitionVersion` e `price` con `unit`, `line` e la specifica scelta. I campi obbligatori mancanti restituiscono Configura per il prezzo; le combinazioni non valide vengono rifiutate. Non crea un carrello, un ordine o un pagamento. Usa `price.line` invece di moltiplicare il prezzo unitario.

Gli import accettano `family`, `orderStep` e `orderingDefinition` opzionali. Una definizione utilizza lo schema in OpenAPI, richiede il piano Pro e il permesso per i prezzi globali, e restituisce `ruleDrafts` per la revisione attraverso il flusso di lavoro esistente per modifiche al prezzo. La struttura omessa lascia invariati i settaggi esistenti. Vedi [setup del catalogo commerciale](/docs/guides/trade-catalogues).

## L'oggetto prodotto

```json
{
  "id": "3f8a2c1e-9b4d-4e6f-a7c8-1d2e3f4a5b6c",
  "sku": "HX-8510",
  "name": "Hex bolt M10 x 50 zinc",
  "description": "Grade 8.8, zinc plated, fully threaded.",
  "brandId": "a1b2c3d4-0000-4000-8000-000000000001",
  "categoryId": "a1b2c3d4-0000-4000-8000-000000000002",
  "manufacturerRef": "DIN933-M10-50",
  "unitLabel": "Box of 100",
  "minOrderQty": 1,
  "availability": "in_stock",
  "stockQty": 240,
  "lowStockAt": null,
  "leadTimeNote": null,
  "dealText": null,
  "active": true,
  "visibility": "all",
  "details": { "Finish": "Zinc", "Thread": "M10" },
  "createdAt": "2026-08-14T09:30:00.000Z",
  "updatedAt": "2026-09-20T16:02:11.000Z",
  "price": {
    "unit": 1850,
    "line": 1850,
    "list": 1850,
    "discountBp": 0,
    "source": "list",
    "label": ""
  }
}
```

| Campo                   | Tipo           | Descrizione                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| ----------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                    | stringa        | ID del prodotto.                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `sku`                   | stringa        | Il tuo codice di magazzino. Unico nello spazio di lavoro, ignorando la maiuscola/minuscola.                                                                                                                                                                                                                                                                                                                                                              |
| `name`                  | string         | Nome del prodotto.                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `description`           | string o null  | Descrizione più lunga.                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `brandId`, `categoryId` | string o null  | Vedi [brand e categorie](#brands-and-categories).                                                                                                                                                                                                                                                                                                                                                                                                        |
| `manufacturerRef`       | string o null  | Numero di pezzo del produttore. Cercabile come lo SKU.                                                                                                                                                                                                                                                                                                                                                                                                   |
| `unitLabel`             | string         | Cosa rappresenta una singola unità, ad esempio "Ogni" o "Scatola da 100".                                                                                                                                                                                                                                                                                                                                                                                |
| `minOrderQty`           | intero         | Quantità minima che i clienti possono ordinare.                                                                                                                                                                                                                                                                                                                                                                                                          |
| `availability`          | string         | `not_tracked`, `in_stock`, `low_stock`, `out_of_stock` o `made_to_order`. Un'etichetta solo per i compratori: ogni prodotto può essere ordinato. Inviandolo senza un `stockQty` si cancella il conteggio memorizzato (stai impostando lo stato manualmente). Con un `stockQty` viene calcolato per te: 0 o meno è `out_of_stock`, fino al livello minimo è `low_stock`, sopra di esso è `in_stock`. `made_to_order` non viene mai modificato dal numero. |
| `stockQty`              | integer o null | Unità disponibili, o `null` quando non conti questo prodotto. Quando impostato, `availability` lo segue. Escluso quando lo chiedi come cliente (`customer`).                                                                                                                                                                                                                                                                                             |
| `lowStockAt`            | integer o null | Livello di scorta minimo del prodotto, o `null` per utilizzare il livello dell'ambiente di lavoro. Escluso quando lo chiedi come cliente.                                                                                                                                                                                                                                                                                                                |
| `leadTimeNote`          | string o null  | Ad esempio "3-5 giorni lavorativi".                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `dealText`              | string o null  | Un nota visualizzata accanto al prezzo, ad esempio "min 12 btls 45". La sua intestazione è l'etichetta di offerta del workspace (Impostazioni, Visualizzazione catalogo).                                                                                                                                                                                                                                                                                |
| `active`                | boolean        | I prodotti non attivi sono nascosti ai clienti.                                                                                                                                                                                                                                                                                                                                                                                                          |
| `visibility`            | string         | `all`, o `selected` quando solo i clienti selezionati possono vederlo.                                                                                                                                                                                                                                                                                                                                                                                   |
| `details`               | object         | Colonne aggiuntive dagli import, come `{ "Finish": "Zinc" }`.                                                                                                                                                                                                                                                                                                                                                                                            |
| `price`                 | object o null  | Il prezzo alla quantità 1 (o `qty`). Vedere [Prezzi](/docs/api/prices#the-price-object). `unit` è `null` quando il prodotto non ha ancora un prezzo.                                                                                                                                                                                                                                                                                                     |

I conteggi di magazzino sono tuoi da tenere aggiornati: gli ordini non cambiano mai `stockQty`, quindi invialo dal tuo sistema di magazzino ogni volta che cambia. I clienti vedono solo lo stato `availability`, mai il numero. A zero un prodotto può ancora essere ordinato; è semplicemente contrassegnato come esaurito.

`GET /products/{id}` include anche i nomi `brand` e `category`.

Le immagini non vengono restituite dall'API. Puoi impostarne una con `imageUrl` quando crei o aggiorni.

## Elenco o ricerca prodotti

**GET** `/api/v1/products`

Senza `q`, i prodotti vengono elencati per nome. Con `q`, i risultati vengono restituiti in questo ordine:

1. SKU esatto o riferimento del produttore
2. SKU o riferimento del produttore che inizia con `q`
3. corrispondenze complete del testo sul nome, marca, categoria e descrizione (3 o più caratteri; le query più brevi corrispondono all'inizio del nome)

Passa `customer` per applicare il prezzo a ogni riga per quel cliente e restituire solo i prodotti che quel cliente può visualizzare (attivi e che sono per tutti o selezionati per loro). È esattamente ciò che il cliente vede nel loro portale di commercio.

| Nome           | Tipo             | Obbligatorio | Descrizione                                                                                                                                              |
| -------------- | ---------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `q`            | stringa          | no           | SKU, riferimento del produttore, nome, marchio o categoria. Fino a 200 caratteri.                                                                        |
| `category`     | stringa          | no           | ID della categoria.                                                                                                                                      |
| `brand`        | stringa          | no           | ID del marchio.                                                                                                                                          |
| `availability` | stringa          | no           | Uno dei valori di disponibilità.                                                                                                                         |
| `active`       | `true` o `false` | no           | `true` restituisce solo i prodotti attivi. Il valore predefinito è `true` quando `customer` è impostato; altrimenti vengono restituiti tutti i prodotti. |
| `customer`     | stringa          | no           | ID del cliente o codice account.                                                                                                                         |
| `limit`        | intero           | no           | Da 1 a 50. Predefinito 50.                                                                                                                               |
| `offset`       | intero           | no           | Righe da saltare. Predefinito 0.                                                                                                                         |

```bash
curl "https://tradecatalog.app/api/v1/products?q=hex%20bolt&customer=OAK01&limit=20" \
  -H "Authorization: Bearer $TRADECATALOG_API_KEY"
```

```js
const params = new URLSearchParams({
  q: 'hex bolt',
  customer: 'OAK01',
  limit: '20',
})
const res = await fetch(`https://tradecatalog.app/api/v1/products?${params}`, {
  headers: { Authorization: `Bearer ${process.env.TRADECATALOG_API_KEY}` },
})
const { data: products, nextOffset } = await res.json()
```

```json
{
  "data": [
    {
      "id": "3f8a2c1e-9b4d-4e6f-a7c8-1d2e3f4a5b6c",
      "sku": "HX-8510",
      "name": "Hex bolt M10 x 50 zinc",
      "description": "Grade 8.8, zinc plated, fully threaded.",
      "brandId": "a1b2c3d4-0000-4000-8000-000000000001",
      "categoryId": "a1b2c3d4-0000-4000-8000-000000000002",
      "manufacturerRef": "DIN933-M10-50",
      "unitLabel": "Box of 100",
      "minOrderQty": 1,
      "availability": "in_stock",
      "leadTimeNote": null,
      "active": true,
      "visibility": "all",
      "details": { "Finish": "Zinc", "Thread": "M10" },
      "createdAt": "2026-08-14T09:30:00.000Z",
      "updatedAt": "2026-09-20T16:02:11.000Z",
      "price": {
        "unit": 1573,
        "line": 1573,
        "list": 1850,
        "discountBp": 1500,
        "source": "brand",
        "label": "Brand discount −15%"
      }
    }
  ],
  "nextOffset": null,
  "currency": "GBP"
}
```

## Ottieni un prodotto

**GET** `/api/v1/products/{id}`

| Nome       | In    | Tipo    | Obbligatorio | Descrizione                                                                                        |
| ---------- | ----- | ------- | ------------ | -------------------------------------------------------------------------------------------------- |
| `id`       | path  | stringa | sì           | ID del prodotto.                                                                                   |
| `customer` | query | stringa | no           | ID del cliente o codice account. Restituisce `404` se il cliente non può visualizzare il prodotto. |
| `qty`      | query | intero  | no           | Quantità da prezzare, da 1 a 1.000.000. Predefinito 1. `price.line` è `unit × qty`.                |

```bash
curl "https://tradecatalog.app/api/v1/products/3f8a2c1e-9b4d-4e6f-a7c8-1d2e3f4a5b6c?customer=OAK01&qty=5" \
  -H "Authorization: Bearer $TRADECATALOG_API_KEY"
```

```js
const res = await fetch(
  `https://tradecatalog.app/api/v1/products/${productId}?customer=OAK01&qty=5`,
  { headers: { Authorization: `Bearer ${process.env.TRADECATALOG_API_KEY}` } },
)
const { data: product } = await res.json()
```

```json
{
  "data": {
    "id": "3f8a2c1e-9b4d-4e6f-a7c8-1d2e3f4a5b6c",
    "sku": "HX-8510",
    "name": "Hex bolt M10 x 50 zinc",
    "description": "Grade 8.8, zinc plated, fully threaded.",
    "brandId": "a1b2c3d4-0000-4000-8000-000000000001",
    "categoryId": "a1b2c3d4-0000-4000-8000-000000000002",
    "manufacturerRef": "DIN933-M10-50",
    "unitLabel": "Box of 100",
    "minOrderQty": 1,
    "availability": "in_stock",
    "leadTimeNote": null,
    "active": true,
    "visibility": "all",
    "details": { "Finish": "Zinc", "Thread": "M10" },
    "createdAt": "2026-08-14T09:30:00.000Z",
    "updatedAt": "2026-09-20T16:02:11.000Z",
    "brand": "Northgate",
    "category": "Bolts",
    "price": {
      "unit": 1573,
      "line": 7865,
      "list": 1850,
      "discountBp": 1500,
      "source": "brand",
      "label": "Brand discount −15%"
    }
  },
  "currency": "GBP"
}
```

Per cercare un prodotto per SKU, utilizzare `GET /products?q=HX-8510`: un match esatto dello SKU è sempre prioritario. Oppure utilizzare [`GET /prices`](/docs/api/prices), che accetta un SKU.

## Crea o aggiorna prodotti

**PUT** `/api/v1/products`

Invia fino a **100 prodotti** per chiamata. Ogni prodotto viene abbinato a un prodotto esistente tramite SKU, ignorando il caso. Se non c'è corrispondenza, viene creato un nuovo prodotto. Marche e categorie vengono abbinati per nome e creati se non esistono.

### Come vengono gestiti i prezzi di listino

| Il prodotto...                    | Cosa succede al prezzo                                                                                                                                                                                                        |
| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| è nuovo                           | Va in vendita a `listPrice` immediatamente.                                                                                                                                                                                   |
| esiste ma non ha ancora un prezzo | Va in vendita a `listPrice` immediatamente.                                                                                                                                                                                   |
| esiste allo stesso prezzo         | Niente.                                                                                                                                                                                                                       |
| esiste a un prezzo diverso        | Niente cambia ancora. Il nuovo prezzo va in un **cambiamento di prezzo in bozza** per quella lista. `priceChangeId` è la bozza per la lista di `listPrice`. `priceChangesByList` ha un'entrata per ogni lista che è cambiata. |

I cambiamenti di prezzo in bozza sono il modo in cui TradeCatalog evita che un errore di battitura raggiunga i clienti. Il proprietario o il personale recensisce la bozza nell'app (**Cambiamenti prezzo**), controlla i movimenti più importanti e gli avvisi, sceglie una data e se inviare una e-mail ai clienti, quindi pubblica. È possibile visualizzare in anteprima una bozza con [`GET /price-changes/{id}`](/docs/api/price-changes#preview-a-price-change).

**Una sincronizzazione, un bozzo per lista:** Una chiamata che modifica i prezzi inizia una bozza per lista, chiamata "Importazione API (nome chiave)" per Standard e con il nome della lista quando invii più di una. Per mantenere una sincronizzazione intera in quelle bozze, invia `priceChangeId` (la lista per `listPrice`) e `drafts` (le altre liste) dalla prima chiamata con ogni chiamata successiva. Ogni lista deve ancora essere una bozza: una volta pubblicata, annullata o eliminata, lascia fuori quell'id per iniziare una nuova bozza.

### Corpo

| Campo                        | Tipo          | Obbligatorio | Descrizione                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| ---------------------------- | ------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `products`                   | array         | sì           | 1 a 100 prodotti. I codici SKU devono essere unici all'interno della chiamata.                                                                                                                                                                                                                                                                                                                                                                                |
| `priceChangeId`              | string        | no           | Una bozza per l'elenco `listPrice` da una chiamata precedente. I prezzi modificati per quell'elenco vengono aggiunti ad essa. 404 se non esiste, 400 se non è più una bozza.                                                                                                                                                                                                                                                                                  |
| `drafts`                     | array         | no           | Fino a 30 voci `{ list, priceChangeId }` in modo che altri elenchi possano continuare le proprie bozze.                                                                                                                                                                                                                                                                                                                                                       |
| `products[].sku`             | string        | sì           | Fino a 64 caratteri.                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `products[].name`            | string        | sì           | Fino a 200 caratteri.                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `products[].listPrice`       | intero o null | sì           | Unità minori, da 0 a 1.000.000.000. `null` è prezzo su richiesta: un prodotto nuovo non ha prezzo, un prodotto esistente mantiene il proprio.                                                                                                                                                                                                                                                                                                                 |
| `products[].priceList`       | string o null | no           | Nome di un elenco prezzi esistente per `listPrice`. Omettere o `null` è Standard. Un nome non noto viene rifiutato.                                                                                                                                                                                                                                                                                                                                           |
| `products[].listPrices`      | array         | no           | Altre liste, come `{ list, price }`. `price` è unità minori, o `null` per lasciare quella lista invariata. Un nome sconosciuto viene rifiutato.                                                                                                                                                                                                                                                                                                               |
| `products[].unitLabel`       | stringa       | sì           | Fino a 60 caratteri, ad esempio “Ogni”.                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `products[].brand`           | string        | no           | Nome del marchio, fino a 120 caratteri. **Pulito se omesso.**                                                                                                                                                                                                                                                                                                                                                                                                 |
| `products[].category`        | string        | no           | Nome della categoria, fino a 120 caratteri. **Cancellato se omesso.**                                                                                                                                                                                                                                                                                                                                                                                         |
| `products[].manufacturerRef` | string        | no           | Fino a 120 caratteri. **Cancellato se omesso.**                                                                                                                                                                                                                                                                                                                                                                                                               |
| `products[].description`     | string        | no           | Fino a 4.000 caratteri. **Cancellato se omesso.**                                                                                                                                                                                                                                                                                                                                                                                                             |
| `products[].availability`    | string        | no           | Uno dei valori di disponibilità. Conservato se omesso; i nuovi prodotti iniziano come `not_tracked`.                                                                                                                                                                                                                                                                                                                                                          |
| `products[].stockQty`        | intero o null | no           | Unità in magazzino, da -1.000.000 a 1.000.000.000. Imposta `availability` (vedi l'oggetto prodotto); uno stato che invii insieme a esso viene ignorato, tranne `made_to_order`. Mantenuto se omesso, a meno che non invii un `availability` senza di esso: in tal caso, il conteggio viene cancellato e viene utilizzato quel stato. `null` smette di contare; il `availability` inviato nella stessa riga si applica, o lo stato corrente se non ce n'è uno. |
| `products[].lowStockAt`      | intero o null | no           | Livello di scorta bassa per questo prodotto, da 0. `null` utilizza il livello del workspace (Impostazioni, 5 per iniziare). Mantenuto se omesso.                                                                                                                                                                                                                                                                                                              |
| `products[].leadTimeNote`    | string        | no           | Fino a 200 caratteri. Conservato se omesso.                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `products[].dealText`        | string        | no           | Fino a 200 caratteri, visualizzati accanto al prezzo. Conservato se omesso.                                                                                                                                                                                                                                                                                                                                                                                   |
| `products[].details`         | oggetto       | no           | Fino a 30 colonne aggiuntive: nomi fino a 60 caratteri, valori fino a 500. Sostituisce tutti i dettagli quando inviato; mantenuto se omesso.                                                                                                                                                                                                                                                                                                                  |
| `products[].imageUrl`        | stringa       | no           | Un URL di immagine `https`. Copiato in TradeCatalog in background quando cambia. Mantenuto se omesso.                                                                                                                                                                                                                                                                                                                                                         |
| `products[].onlyFor`         | array         | no           | Fino a 200 codici account o e-mail di contatto. Rende visibile il prodotto **solo** per quei clienti, sostituendo qualsiasi elenco precedente. Mantenuto se omesso.                                                                                                                                                                                                                                                                                           |

**Invia ogni campo che ti interessa:** `brand`, `category`, `manufacturerRef` e `description` vengono scritti come inviati, quindi lasciarne uno fuori li cancella. Invia sempre il prodotto completo, come una riga di un foglio di calcolo.

Queste operazioni non possono essere eseguite tramite l'API per il momento: rendere un prodotto non attivo, `minOrderQty`, rimuovere un'immagine, cancellare un'appunto sul tempo di attesa e rendere nuovamente visibile a tutti un prodotto riservato ai clienti. Esegui queste operazioni nell'app.

```bash
curl -X PUT https://tradecatalog.app/api/v1/products \
  -H "Authorization: Bearer $TRADECATALOG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "products": [
      {
        "sku": "HX-8510",
        "name": "Hex bolt M10 x 50 zinc",
        "listPrice": 1950,
        "unitLabel": "Box of 100",
        "brand": "Northgate",
        "category": "Bolts",
        "manufacturerRef": "DIN933-M10-50",
        "availability": "in_stock",
        "details": { "Finish": "Zinc", "Thread": "M10" }
      },
      {
        "sku": "OAK-HNG-75",
        "name": "Oak-finish butt hinge 75mm",
        "listPrice": 695,
        "unitLabel": "Pair",
        "category": "Hinges",
        "onlyFor": ["OAK01"]
      }
    ]
  }'
```

```js
const res = await fetch('https://tradecatalog.app/api/v1/products', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${process.env.TRADECATALOG_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ products }),
})
const { data: result } = await res.json()
if (result.priceChangeId)
  console.log('Review the draft price change in TradeCatalog')
```

```json
{
  "data": {
    "created": 1,
    "updated": 1,
    "priceChanges": 1,
    "priceChangeId": "c4d5e6f7-1234-4abc-9def-0123456789ab",
    "priceChangesByList": [
      {
        "list": "Standard",
        "priceChangeId": "c4d5e6f7-1234-4abc-9def-0123456789ab"
      }
    ],
    "unknownCustomers": []
  }
}
```

| Campo                | Descrizione                                                                                                            |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `created`            | Nuovi prodotti.                                                                                                        |
| `updated`            | Prodotti esistenti corrispondenti al SKU.                                                                              |
| `priceChanges`       | Quanti prezzi sono entrati in un bozza, su ogni elenco nella chiamata.                                                 |
| `priceChangeId`      | La bozza per l'elenco `listPrice`, o `null` se quell'elenco non aveva modifiche.                                       |
| `priceChangesByList` | Un `{ list, priceChangeId }` per ogni elenco che ha avuto un cambiamento di prezzo in questa chiamata.                 |
| `unknownCustomers`   | Valori in `onlyFor` che non hanno corrisposto a nessun cliente (fino a 50). A quei clienti non è stato dato l'accesso. |

I cambiamenti appaiono nel registro delle attività con il nome della chiave, e le ricerche vengono aggiornate immediatamente.

## Marche e categorie

**GET** `/api/v1/brands`

**GET** `/api/v1/categories`

Entrambi restituiscono ogni riga, ordinata per nome, senza paginazione. Utilizza gli id con i filtri `brand` e `category` su `GET /products`.

```bash
curl https://tradecatalog.app/api/v1/categories \
  -H "Authorization: Bearer $TRADECATALOG_API_KEY"
```

```js
const res = await fetch('https://tradecatalog.app/api/v1/categories', {
  headers: { Authorization: `Bearer ${process.env.TRADECATALOG_API_KEY}` },
})
const { data: categories } = await res.json()
```

```json
{
  "data": [
    { "id": "a1b2c3d4-0000-4000-8000-000000000002", "name": "Bolts" },
    { "id": "a1b2c3d4-0000-4000-8000-000000000003", "name": "Hinges" }
  ]
}
```

Per creare un marchio o una categoria, assegnane un nome a un prodotto in `PUT /products`.
