---
description: "Create, edit, remove and reorder ticket products, including packages, pricing tiers and shared pools."
group: "Resources"
order: 13
---

# Product management

Manage an event's ticket catalog using its existing product actions.

## Read configuration {#read-configuration}

Use `GET /{event_id}/products/index.json` for the catalog and `GET /{event_id}/products/edit/{product_id}.json` for current configuration. The latter includes `Product`, volume/pool/package rows, attachments, metadata and event context. `GET /{event_id}/products/add/free.json` prepares defaults and creates nothing.

## Create a product {#create-a-product}

PUT `/{event_id}/products/add/{type}.json` with a `Product` object. Types are `paid`, `free`, `donation` and `package`. Set the corresponding payload fields: free tickets use `price: "0.00"`; donations use `donation: 1`; packages use `package_flag: 1`. The path alone does not populate every submitted flag.

```
{"Product":{"title":"General admission","category_new":"Tickets","price":"0.00","stock":100,"visible":1}}
```

Use an event-owned `category_id`, or `category_new` to create a group with the ticket. If omitted, the action chooses the first existing group. New product IDs and ordering are assigned by the server. Omitted/empty stock becomes `-1` (unlimited); an explicit zero remains zero. Paid tickets require a configured payment provider. The first paid ticket can also update event fee/VAT settings.

Successful creation returns `{"ok":true,"id":"..."}`. Validation may return an array containing submitted data and errors with HTTP 200. Read [writing data](/en-us/docs/writing-data.md) before treating the request as successful.

## Product fields and defaults {#product-fields}

| Field inside Product | Validation and meaning | Creation/edit behavior |
| --- | --- | --- |
| `title` | String, at least 2 characters; permitted HTML is filtered. | Required on creation and full configuration edits. |
| `price` | Numeric amount from 0 through 999999.9999 in event currency. Send a decimal string such as `"12.50"`. | Required; free tickets use `"0.00"`. Donations can send null with `donation:1`. |
| `category_id` / `category_new` | Event-owned category UUID, or new name of 2–100 characters. | Creation can select the first existing group when both are omitted. Supply one deliberately. Edits require the current category or a new group. |
| `stock` | Whole quantity ≥ -1. `-1` means unlimited; 0 means no capacity. | Creation uses -1 when omitted/empty. Send current stock on edit; an empty edit value also becomes -1. This is configured capacity, not computed remaining stock. |
| `max_per_sale` | Whole number from 1 through the deployment's `Product.max_per_sale` setting. | Creation uses that setting when omitted/empty. Retain the returned value on edit; do not assume the same maximum in every environment. |
| `visible` | 0/1 shop visibility. | Supply deliberately; visibility, sales windows and event state all affect checkout availability. |
| `available_from`, `available_till` | Server-local SQL timestamps. | Retain existing values unless changing sales windows. They do not contain a UTC offset. |
| `waitinglist`, `keep_soldout_in_shop` | 0/1 waiting-list and sold-out display settings. | Optional; preserve the current flags on full edits. |
| `package_flag`, `donation` | 0/1 product modes. | The generic creation path does not assign every mode flag; set the relevant flag explicitly. |
| `description`, `text_on_ticket` | Strings; description HTML is filtered. | Optional content; keep the returned configuration when editing another field. |
| `duplicate` | 0/1 command flag. | On edit, 1 delegates to creation and can return a new product ID. Omit for an ordinary update. |

For an edit, start with `products/edit/{product_id}.json`, map its fields into the write envelopes and retain title, price, category, stock and maximum per sale. The endpoint is a configuration form save rather than a field patch. Volume prices, pool membership and package members are replaced; omitting related selections can clear them. Read [availability](/en-us/docs/product-availability.md) for computed stock and shared pools.

## Pricing, pools and packages {#pricing-pools-and-packages}

Additional top-level envelopes are `ProductsVolume`, `ProductsPool`, `PackagesProduct`, `Meta` and first-paid-ticket `Event` settings. Metadata is limited by server policy.

```
{
  "Product": {"title":"Bundle","price":"30.00","stock":100,"package_flag":1},
  "ProductsVolume": {"5":{"price":"25.00"}},
  "ProductsPool": {"1":{"pool":1}},
  "PackagesProduct": [{"product_id":"22222222-2222-4222-8222-222222222222","amount":2,"active":1}]
}
```

Volume keys are quantities; pool keys identify shared pools. Package rows use event-owned product IDs, positive whole quantities and `active:1` to select a member; rows without an active value are skipped. Do not send the read serializer's pool rows unchanged: submitted pool selections use the keyed structure above. `products/add_package.json` is a PUT alias that forces the package flag; package-mode form/failure rendering still has legacy behavior.

## Edit and delete {#edit-and-delete}

PUT `/{event_id}/products/edit/{product_id}.json` with current required configuration and the changes. The route ID is authoritative. Success returns `{"ok":true}`. Setting `Product.duplicate` delegates to creation and can return a new ID. Historical purchases can retain their original product title/price.

POST `/{event_id}/products/delete/{product_id}.json` deletes a permitted product. Sold-ticket, package and other references can block deletion. Require `ok:true` and refresh the catalog; don't infer removal from HTTP 200.

## Reorder the catalog {#reorder-the-catalog}

PUT `/{event_id}/products/order.json` with a complete `Product` map from category UUID to ordered product UUIDs. Include every category and every product exactly once. An empty group uses `["empty"]`. Success is HTTP 204, without JSON.

```
{"Product":{"55555555-5555-4555-8555-555555555555":["22222222-2222-4222-8222-222222222222"]}}
```

Use [categories](/en-us/docs/categories.md) and [category questions](/en-us/docs/ticket-questions.md) for group configuration. The [catalog recipe](/en-us/docs/integration-recipes.md#create-event-catalog) connects creation and availability reads.
