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

# Merchant products

> Create, update, and delete store products with a Bearer API key.

All routes require:

```txt theme={null}
Authorization: Bearer mk_live_<your_key>
```

Paths are store-scoped. A key must include the matching product scope **and** access to that shop.

## List

```http theme={null}
GET /api/merchant/v1/stores/{storeId}/products?page=1&limit=50
```

Scope: `product:read`

```json theme={null}
{
  "products": [
    {
      "id": "clxyz...",
      "productId": 123456,
      "name": "Premium key",
      "slug": "premium-key",
      "price": 9.99,
      "slashedPrice": null,
      "images": [],
      "visibility": "public",
      "categoryId": null,
      "groupId": null,
      "variants": [],
      "stockAvailable": 12,
      "storeId": "cm...",
      "createdAt": "2026-09-21T00:00:00.000Z",
      "updatedAt": "2026-09-21T00:00:00.000Z"
    }
  ],
  "page": 1,
  "limit": 50,
  "total": 1
}
```

`limit` max is 100. Variant `stockKeys` are never returned here — use the stock endpoint.

## Create

```http theme={null}
POST /api/merchant/v1/stores/{storeId}/products
```

Scope: `product:create` · `201`

```json theme={null}
{
  "name": "Premium key",
  "description": "Delivered as a license key.",
  "price": 9.99,
  "visibility": "public",
  "categoryId": null
}
```

`storeId` in the JSON body is ignored. The path store wins.

Default delivery is license keys. Add stock before customers can check out, or send `deliveryConfig` with another method (`manual_delivery`, `download_file`, …).

## Get / update / delete

```http theme={null}
GET    /api/merchant/v1/stores/{storeId}/products/{productId}
PATCH  /api/merchant/v1/stores/{storeId}/products/{productId}
PUT    /api/merchant/v1/stores/{storeId}/products/{productId}
DELETE /api/merchant/v1/stores/{storeId}/products/{productId}
```

Scopes: `product:read` · `product:update` · `product:delete`

`{productId}` may be the cuid `id` or the numeric `productId`. PATCH/PUT are partial — omitted fields stay as they are.

DELETE removes unused license keys automatically. If keys were already delivered on orders, delete returns `400` — hide the product instead.

## Stock (license keys)

```http theme={null}
GET  /api/merchant/v1/stores/{storeId}/products/{productId}/stock
PUT  /api/merchant/v1/stores/{storeId}/products/{productId}/stock
POST /api/merchant/v1/stores/{storeId}/products/{productId}/stock
```

Scopes: `product:stock:read` · `product:stock:write`

Optional query: `?variantName=Pro`

* **PUT** replaces unused keys for that variant (or the product when `variantName` is omitted)
* **POST** appends keys

```json theme={null}
{ "variantName": null, "keys": ["AAAA-BBBB-CCCC", "DDDD-EEEE-FFFF"] }
```

## Errors

| Status | Meaning |
| - | - |
| 400 | Invalid JSON or validation (name, price, category, delivery) |
| 401 | Missing, invalid, expired, or revoked token |
| 403 | Scope or shop not allowed for this key |
| 404 | Product not in this store |
| 429 | Rate limit exceeded |

## Related

* [Merchant categories](/api/merchant-categories)
* [Merchant groups](/api/merchant-groups)
* [API keys](/developers/api-keys)
* [Authentication](/api/authentication)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.