---
title: "Upsert a product"
method: POST
path: "/products"
tags: ["Products"]
---

# Upsert a product

`POST /products`

## Request

- Base URL: `https://api.savanto.ai`
- URL: `https://api.savanto.ai/products`
- Auth: HTTP bearer

Create a new product or update the existing one whose `externalId` matches the body. Embeddings for the product's searchable text are generated and persisted as part of the call, so the product becomes searchable immediately after a successful response. Required fields: `externalId`, `name`. Optional: `description`, `price` (in cents), `priceMin` / `priceMax`, `stockStatus` (`in_stock` | `out_of_stock` | `limited`), `images` (array of URLs), `brand`, `category`, `attributes` (key/value). Workspaces with native Shopify / WooCommerce sync should normally rely on the plugin rather than calling this endpoint directly. Idempotent on `externalId`.

## Request body

- ProductInput
  - `id` string, required
  - `name` string, required
  - `content` string
  - `searchableText` string
  - `excerpt` string
  - `price` number, nullable
  - `salePrice` number, nullable
  - `regularPrice` number, nullable
  - `onSale` boolean
  - `priceMin` number, nullable
  - `priceMax` number, nullable
  - `stockQuantity` number, nullable
  - `stockStatus` 'instock' | 'outofstock' | 'onbackorder'
  - `sku` string
  - `type` 'simple' | 'variable' | 'grouped' | 'external'
  - `weight` number, nullable
  - `dimensions` object, nullable
    - `length` number, nullable
    - `width` number, nullable
    - `height` number, nullable
  - `rating` number, nullable
  - `reviewCount` number
  - `categories` string[]
  - `tags` string[]
  - `brands` string[]
  - `image` string, nullable
  - `url` string
  - `status` string
  - `visibility` string
  - `featured` boolean
  - `virtual` boolean
  - `downloadable` boolean
  - `attributes` object, nullable
  - `metadata` object
  - `source` string
  - `indexStatus` 'active' | 'hidden' | 'disabled'

## Response `200`

Product upserted successfully

- object
  - `object` 'product', required
  - `requestId` string, required
  - `data` object, required
    - `id` string, required
    - `message` string, required

## Other responses

- `400`: Validation error
- `401`: Unauthorized
- `403`: Forbidden
- `404`: Not found
- `409`: Conflict
- `413`: Payload too large
- `429`: Rate limit exceeded

---

[API](https://skmtc.dev/savanto/apis/savanto-api.md) · [All operations](https://skmtc.dev/savanto/apis/savanto-api/llms.txt) · [OpenAPI document](https://skmtc.dev/savanto/apis/savanto-api/revisions/82437327a878?raw)
