> Documentation index: https://docs.sellhidden.com/llms.txt

# Products

Source: https://docs.sellhidden.com/reference/products

Your catalogue as the seller sees it: hidden products included, every variation with its deliverables, key pools and order counts. Writes follow the dashboard rules: plan limits, unique slugs, deliverable checks, the 2,000,000-key cap per variation and an audit log entry.

## Endpoints

14 endpoints · [products:read](https://docs.sellhidden.com/start?scope=products%3Aread#scopes) · [products:write](https://docs.sellhidden.com/start?scope=products%3Awrite#scopes)

Base URL https://api.sellhidden.com/v1 · Authorization: Bearer · 120/min per key · cursor pagination · Idempotency-Key on POST · No test mode

### GET /products — List products

Scope `products:read`.

**Query parameters**

| Query parameters | Type | Description |
| --- | --- | --- |
| `active` | true \| false | Only live or only hidden products. |
| `categoryId` | string | Only this category. |
| `q` | string | Title or slug contains this text. |
| `deliveryType` | string | Primary delivery type (file, text, keys, link, discord, dynamic). |
| `limit` | 1..100 | Page size. Default 50. |
| `cursor` | string | nextCursor from the previous page. |

```bash
curl https://api.sellhidden.com/v1/products \
  -H "Authorization: Bearer hsk_live_…"
```

Response (200):

```json
{ "ok": true, "products": [ { "id": "clx…", "slug": "lifetime-license", "name": "Lifetime license", "active": true, "displayPriceUsd": 29, "stock": 120, "variations": [ … ], … } ], "nextCursor": null, "hasMore": false }
```

### POST /products — Create a product

A product has at least one variation. Send `variants` (recommended), or the short form `priceUsd` + `deliveryType` + `deliveryData` (+ `keys` for a key product) for a single-price product. Scope `products:write`. Accepts `Idempotency-Key`. Returns 201.

**Body**

| Body | Type | Description |
| --- | --- | --- |
| `title` (required) | string | Up to 120 characters. |
| `slug` | string | URL slug; derived from the title when omitted and made unique. |
| `description` | string | Markdown or plain text, up to 20,000 characters. |
| `imageUrl` | string \| null | https URL or an uploaded /media URL. |
| `categoryId` | string \| null | One of your categories. |
| `variants` | array | Variations (see Variations below). At least one. |
| `active` | boolean | Live on the storefront. Default true. Counts against the plan's product limit. |
| `sortOrder` | integer | Lower first. Left out: placed after your other products. |
| `seoTitle` | string \| null | Up to 70 characters. |
| `seoDescription` | string \| null | Up to 200 characters. |

```bash
curl -X POST https://api.sellhidden.com/v1/products \
  -H "Authorization: Bearer hsk_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "title": "Lifetime license",
    "description": "One key, forever.",
    "variants": [
      {
        "label": "1 device",
        "priceUsd": 29,
        "deliverables": [
          {
            "type": "keys",
            "keys": [
              "AAAA-1111",
              "BBBB-2222"
            ]
          }
        ]
      }
    ]
  }'
```

Response (201):

```json
{ "ok": true, "product": { "id": "clx…", "slug": "lifetime-license", "active": true, "variations": [ { "id": "clx…", "label": "1 device", "priceUsd": 29, "stock": 2, "unusedKeys": 2, "deliverables": [ { "type": "keys", "data": "", "keyOrder": "fifo" } ] } ], … } }
```

### GET /products/{id} — Get a product

Scope `products:read`.

```bash
curl https://api.sellhidden.com/v1/products/ID \
  -H "Authorization: Bearer hsk_live_…"
```

### PATCH /products/{id} — Update a product

Only the fields you send change. `variants` replaces the whole variation list (variations left out are removed); to change one variation use the Variations endpoints instead. Scope `products:write`.

**Body**

| Body | Type | Description |
| --- | --- | --- |
| `title` | string |  |
| `slug` | string |  |
| `description` | string |  |
| `imageUrl` | string \| null |  |
| `categoryId` | string \| null |  |
| `active` | boolean | Activating respects the plan limit; a product removed by the platform stays off. |
| `sortOrder` | integer |  |
| `seoTitle` | string \| null |  |
| `seoDescription` | string \| null |  |
| `variants` | array | Full list of variations (with `id` for existing ones). |

```bash
curl -X PATCH https://api.sellhidden.com/v1/products/ID \
  -H "Authorization: Bearer hsk_live_…" \
  -H "Content-Type: application/json" \
  -d '{"active":false}'
```

### DELETE /products/{id} — Delete a product

Deleted for good when it was never ordered; archived (hidden, kept for order history) otherwise. `result` says which. Scope `products:write`.

```bash
curl -X DELETE https://api.sellhidden.com/v1/products/ID \
  -H "Authorization: Bearer hsk_live_…"
```

Response (200):

```json
{ "ok": true, "id": "clx…", "result": "deleted" }
```

### GET /products/{id}/variations — List variations

Scope `products:read`.

```bash
curl https://api.sellhidden.com/v1/products/ID/variations \
  -H "Authorization: Bearer hsk_live_…"
```

### POST /products/{id}/variations — Add a variation

Scope `products:write`. Accepts `Idempotency-Key`. Returns 201.

**Body**

| Body | Type | Description |
| --- | --- | --- |
| `label` (required) | string | Shown to buyers. Required when the product has more than one variation. |
| `priceUsd` (required) | number | Price in USD. |
| `compareAtUsd` | number \| null | Old price shown struck through. |
| `stock` | integer \| null | Units left; null = unlimited. Ignored for variations that deliver keys (the pool is the stock). |
| `description` | string | Short line under the label. |
| `active` | boolean | Hidden variations cannot be bought. Default true. |
| `minPerOrder` | 1..100 | Fewest units per order. Default 1. |
| `maxPerOrder` | 1..100 \| null | Most units per order; null = no limit. |
| `billingInterval` | week \| month \| quarter \| year \| null | A subscription billed per period; null (default) = one-time purchase. |
| `trialDays` | 0..365 | Subscription: free trial days before the first payment. Default 0. |
| `renewalDelivers` | boolean | Subscription: deliver again on every renewal (default true); false only extends access. |
| `deliverables` | array | What the buyer receives: [{ type: file \| text \| keys \| link \| discord \| dynamic, data, keyOrder?: fifo \| lifo \| random, keys?: string[] }]. Each type at most once. Omitted on an existing variation = unchanged. |

```bash
curl -X POST \
  https://api.sellhidden.com/v1/products/ID/variations \
  -H "Authorization: Bearer hsk_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "label": "5 devices",
    "priceUsd": 49,
    "compareAtUsd": 69,
    "maxPerOrder": 3,
    "deliverables": [
      {
        "type": "keys"
      }
    ]
  }'
```

### GET /products/{id}/variations/{variationId} — Get a variation

Scope `products:read`.

```bash
curl \
  https://api.sellhidden.com/v1/products/ID/variations/VARIATION_ID \
  -H "Authorization: Bearer hsk_live_…"
```

### PATCH /products/{id}/variations/{variationId} — Update a variation

Only the fields you send change. Sending `deliverables` replaces that variation's deliverables. Scope `products:write`.

**Body**

| Body | Type | Description |
| --- | --- | --- |
| `label` | string | Shown to buyers. Required when the product has more than one variation. |
| `priceUsd` | number | Price in USD. |
| `compareAtUsd` | number \| null | Old price shown struck through. |
| `stock` | integer \| null | Units left; null = unlimited. Ignored for variations that deliver keys (the pool is the stock). |
| `description` | string | Short line under the label. |
| `active` | boolean | Hidden variations cannot be bought. Default true. |
| `minPerOrder` | 1..100 | Fewest units per order. Default 1. |
| `maxPerOrder` | 1..100 \| null | Most units per order; null = no limit. |
| `billingInterval` | week \| month \| quarter \| year \| null | A subscription billed per period; null (default) = one-time purchase. |
| `trialDays` | 0..365 | Subscription: free trial days before the first payment. Default 0. |
| `renewalDelivers` | boolean | Subscription: deliver again on every renewal (default true); false only extends access. |
| `deliverables` | array | What the buyer receives: [{ type: file \| text \| keys \| link \| discord \| dynamic, data, keyOrder?: fifo \| lifo \| random, keys?: string[] }]. Each type at most once. Omitted on an existing variation = unchanged. |

```bash
curl -X PATCH \
  https://api.sellhidden.com/v1/products/ID/variations/VARIATION_ID \
  -H "Authorization: Bearer hsk_live_…" \
  -H "Content-Type: application/json" \
  -d '{"priceUsd":39,"minPerOrder":1,"maxPerOrder":5}'
```

### DELETE /products/{id}/variations/{variationId} — Remove a variation

A product keeps at least one variation (409 otherwise). A variation that was ordered is archived so its orders still deliver. Scope `products:write`.

```bash
curl -X DELETE \
  https://api.sellhidden.com/v1/products/ID/variations/VARIATION_ID \
  -H "Authorization: Bearer hsk_live_…"
```

### GET /products/{id}/keys — Pool counts and unused keys

`pool` holds the counts (unused, used, shared pool, per variation); `keys` is one page of unused keys of one pool, oldest first. Scope `products:read`.

**Query parameters**

| Query parameters | Type | Description |
| --- | --- | --- |
| `variationId` | string | Which pool. Default: the product's only variation, else the product-wide shared pool. |
| `q` | string | Key value contains this text. |
| `limit` | 1..1000 | Page size. Default 100. |
| `cursor` | string | nextCursor from the previous page. |

```bash
curl https://api.sellhidden.com/v1/products/ID/keys \
  -H "Authorization: Bearer hsk_live_…"
```

Response (200):

```json
{ "ok": true, "pool": { "unused": 1200, "used": 310, "shared": 0, "variations": { "clx…": 1200 } }, "variationId": "clx…", "keys": [ { "id": "k…", "value": "AAAA-1111", "createdAt": "…" } ], "nextCursor": "…", "hasMore": true }
```

### POST /products/{id}/keys — Add keys in bulk

Up to 50,000 keys per request (send big lists in batches); a pool holds up to 2,000,000 unused keys (400 pool_full past that). Keys already in the product's pools and repeats in the list are skipped. Orders that were waiting for keys are delivered, oldest first. Scope `products:write`. Accepts `Idempotency-Key`. Returns 201.

**Body**

| Body | Type | Description |
| --- | --- | --- |
| `keys` (required) | string[] | 1 to 50,000 keys, up to 50,000 characters each. |
| `variationId` | string | Which pool. Default: the product's only variation, else the shared pool. |

```bash
curl -X POST \
  https://api.sellhidden.com/v1/products/ID/keys \
  -H "Authorization: Bearer hsk_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"keys":["AAAA-1111","BBBB-2222"]}'
```

Response (201):

```json
{ "ok": true, "added": 2, "skipped": 0, "skippedExisting": 0, "skippedRepeated": 0, "inOtherProducts": 0, "redelivered": 0, "stillWaiting": 0, "message": null, "pool": { "unused": 1202, "used": 310, "shared": 0, "variations": { "clx…": 1202 } } }
```

### DELETE /products/{id}/keys — Remove unused keys

Removes up to 500 unused keys by id or by value. Keys already handed to a buyer are never removed. Scope `products:write`.

**Body**

| Body | Type | Description |
| --- | --- | --- |
| `ids` | string[] | Key ids from the list endpoint. |
| `values` | string[] | Key values. |

```bash
curl -X DELETE \
  https://api.sellhidden.com/v1/products/ID/keys \
  -H "Authorization: Bearer hsk_live_…" \
  -H "Content-Type: application/json" \
  -d '{"values":["AAAA-1111"]}'
```

Response (200):

```json
{ "ok": true, "removed": 1, "pool": { … } }
```

### POST /products/{id}/stock — Set or adjust stock

Sets (`stock`) or shifts (`adjust`, can be negative) the stock of one variation. Variations that deliver keys answer 409 stock_from_keys: add or remove keys instead. Scope `products:write`. Accepts `Idempotency-Key`.

**Body**

| Body | Type | Description |
| --- | --- | --- |
| `variationId` | string | Default: the product's only variation. |
| `stock` | integer \| null | Absolute units; null = unlimited. |
| `adjust` | integer | Relative change, e.g. -3. |

```bash
curl -X POST \
  https://api.sellhidden.com/v1/products/ID/stock \
  -H "Authorization: Bearer hsk_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"adjust":10}'
```
