API reference navigation
Collections
AdminCollections groups 6 operations on the admin tier.
Admin tier. Requires an RBAC-gated vnstat_ access token, sent as Authorization: Bearer … or X-Vinosoft-Access-Token.
/api/{version}/admin/collections
One cursored page of collections, published or not. Follow the Link: rel="next" header for the next page; a page_info that was not issued for this exact query string is rejected with 422.
vnstat_ access token, sent as Authorization: Bearer … or X-Vinosoft-Access-Token.Query parameters
| Name | Type | Description |
|---|---|---|
| limit optional | integer (int32) |
Responses
- 200CollectionListResponseOK
- 422ApiErrorResponseUnprocessable Content
curl -X GET \
'https://<your-shop-domain>/api/2026-04-01/admin/collections' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer vnstat_<token>'{
"collections": [
{
"id": "00000000-0000-0000-0000-000000000000",
"title": "string",
"handle": "string",
"body_html": "string",
"collection_type": "string",
"published": true,
"sort_order": "string",
"products_count": 1,
"image": {
"id": "00000000-0000-0000-0000-000000000000",
"src": "string",
"preview_src": "string",
"alt": "string",
"position": 1,
"width": 1,
"height": 1
},
"template_name": "string",
"rules_json": "string",
"meta_title": "string",
"meta_description": "string",
"created_at": "2026-04-01T12:00:00Z",
"updated_at": "2026-04-01T12:00:00Z"
}
]
}/api/{version}/admin/collections
Creates a collection and returns it as stored. A missing body and a rejected field both surface as 422.
vnstat_ access token, sent as Authorization: Bearer … or X-Vinosoft-Access-Token.Request body
application/json, text/json, application/*+json · optional
CreateCollectionRequestBody for POST /api/{version}/admin/collections. title is required; collection_type defaults to manual.
titlestringnullablehandlestringnullablebody_htmlstringnullablecollection_typestringnullablepublishedbooleansort_orderstringnullabletemplate_namestringnullablerules_jsonstringnullablemeta_titlestringnullablemeta_descriptionstringnullable
Responses
- 201CollectionResponseCreated
- 422ApiErrorResponseUnprocessable Content
curl -X POST \
'https://<your-shop-domain>/api/2026-04-01/admin/collections' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer vnstat_<token>' \
-H 'Content-Type: application/json' \
-d '{
"title": "string",
"handle": "string",
"body_html": "string",
"collection_type": "string",
"published": true,
"sort_order": "string",
"template_name": "string",
"rules_json": "string",
"meta_title": "string",
"meta_description": "string"
}'{
"collection": {
"id": "00000000-0000-0000-0000-000000000000",
"title": "string",
"handle": "string",
"body_html": "string",
"collection_type": "string",
"published": true,
"sort_order": "string",
"products_count": 1,
"image": {
"id": "00000000-0000-0000-0000-000000000000",
"src": "string",
"preview_src": "string",
"alt": "string",
"position": 1,
"width": 1,
"height": 1
},
"template_name": "string",
"rules_json": "string",
"meta_title": "string",
"meta_description": "string",
"created_at": "2026-04-01T12:00:00Z",
"updated_at": "2026-04-01T12:00:00Z"
}
}/api/{version}/admin/collections/{id}
One collection.
vnstat_ access token, sent as Authorization: Bearer … or X-Vinosoft-Access-Token.Path parameters
| Name | Type | Description |
|---|---|---|
| id required | string (uuid) |
Responses
- 200CollectionResponseOK
- 404ApiErrorResponseNot Found
curl -X GET \
'https://<your-shop-domain>/api/2026-04-01/admin/collections/<id>' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer vnstat_<token>'{
"collection": {
"id": "00000000-0000-0000-0000-000000000000",
"title": "string",
"handle": "string",
"body_html": "string",
"collection_type": "string",
"published": true,
"sort_order": "string",
"products_count": 1,
"image": {
"id": "00000000-0000-0000-0000-000000000000",
"src": "string",
"preview_src": "string",
"alt": "string",
"position": 1,
"width": 1,
"height": 1
},
"template_name": "string",
"rules_json": "string",
"meta_title": "string",
"meta_description": "string",
"created_at": "2026-04-01T12:00:00Z",
"updated_at": "2026-04-01T12:00:00Z"
}
}/api/{version}/admin/collections/{id}
Updates a collection and returns it as stored.
vnstat_ access token, sent as Authorization: Bearer … or X-Vinosoft-Access-Token.Path parameters
| Name | Type | Description |
|---|---|---|
| id required | string (uuid) |
Request body
application/json, text/json, application/*+json · optional
UpdateCollectionRequestBody for PUT /api/{version}/admin/collections/{id}. Every field is optional; only the supplied
ones are applied. collection_type is immutable after creation and is not accepted here.
titlestringnullablehandlestringnullablebody_htmlstringnullablepublishedbooleannullablesort_orderstringnullabletemplate_namestringnullablerules_jsonstringnullablemeta_titlestringnullablemeta_descriptionstringnullable
Responses
- 200CollectionResponseOK
- 404ApiErrorResponseNot Found
- 422ApiErrorResponseUnprocessable Content
curl -X PUT \
'https://<your-shop-domain>/api/2026-04-01/admin/collections/<id>' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer vnstat_<token>' \
-H 'Content-Type: application/json' \
-d '{
"title": "string",
"handle": "string",
"body_html": "string",
"published": true,
"sort_order": "string",
"template_name": "string",
"rules_json": "string",
"meta_title": "string",
"meta_description": "string"
}'{
"collection": {
"id": "00000000-0000-0000-0000-000000000000",
"title": "string",
"handle": "string",
"body_html": "string",
"collection_type": "string",
"published": true,
"sort_order": "string",
"products_count": 1,
"image": {
"id": "00000000-0000-0000-0000-000000000000",
"src": "string",
"preview_src": "string",
"alt": "string",
"position": 1,
"width": 1,
"height": 1
},
"template_name": "string",
"rules_json": "string",
"meta_title": "string",
"meta_description": "string",
"created_at": "2026-04-01T12:00:00Z",
"updated_at": "2026-04-01T12:00:00Z"
}
}/api/{version}/admin/collections/{id}
Deletes the collection. Returns 204 with no body; deleting an already-deleted collection is a 404, not a no-op success.
vnstat_ access token, sent as Authorization: Bearer … or X-Vinosoft-Access-Token.Path parameters
| Name | Type | Description |
|---|---|---|
| id required | string (uuid) |
Responses
- 204No Content
- 404ApiErrorResponseNot Found
curl -X DELETE \
'https://<your-shop-domain>/api/2026-04-01/admin/collections/<id>' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer vnstat_<token>'/api/{version}/admin/collections/{id}/products
One cursored page of the collection's published products, in the same shape the storefront catalog returns. The facets member of that envelope is never present here: this read has no include=facets.
vnstat_ access token, sent as Authorization: Bearer … or X-Vinosoft-Access-Token.Path parameters
| Name | Type | Description |
|---|---|---|
| id required | string (uuid) |
Query parameters
| Name | Type | Description |
|---|---|---|
| limit optional | integer (int32) | Page size. Clamped to 1..250 by the handler. |
| page_info optional | string | Opaque cursor. When present, filter params must match those that produced the cursor. |
| q optional | string | Free-text search within the collection. |
| vendor optional | string (uuid) | Manufacturer (vendor) filter. |
| order optional | string | Sort spec, e.g. |
| color_id optional | string | Colour facet ids ( |
| region_id optional | string | Region facet ids. |
| year_id optional | string | Vintage facet ids. |
| country_id optional | string | Country facet ids. |
| size_id optional | string | Bottle-size facet ids. |
| category_id optional | string | Category facet ids. AND'd: a product must sit in every category passed. |
| custom_field_id optional | string | Custom-field option ids ( |
| price_min optional | number (double) | Lower bound on the gross price, in the request's active currency. |
| price_max optional | number (double) | Upper bound on the gross price, in the request's active currency. |
| available optional | boolean |
|
| on_sale optional | boolean |
|
| include optional | string | Comma-separated extras to embed in the response. |
| IncludeFacets optional | boolean | Whether |
Responses
- 200StorefrontProductListResponseOK
- 404ApiErrorResponseNot Found
- 422ApiErrorResponseUnprocessable Content
curl -X GET \
'https://<your-shop-domain>/api/2026-04-01/admin/collections/<id>/products' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer vnstat_<token>'{
"products": [
{
"id": "00000000-0000-0000-0000-000000000000",
"handle": "string",
"title": "string",
"body_html": "string",
"image": "string",
"available": true,
"currency": "string",
"price_min": 1,
"price_max": 1,
"variants_count": 1,
"manufacturer": {
"id": "00000000-0000-0000-0000-000000000000",
"name": "string",
"logo_url": "string"
},
"flag": {
"name": "string",
"color": "string"
},
"rating": 1,
"rating_count": 1,
"custom_fields": [
{}
],
"variants": [
{}
]
}
],
"facets": [
{
"key": "string",
"values": [
{}
]
}
],
"price_range": {
"min": 1,
"max": 1,
"currency": "string"
}
}Schemas
ApiErrorResponse
The error envelope every non-2xx response from this API uses. Documentation shape: actions build it as an anonymous object, so this type exists to give the OpenAPI document a schema to point at.
errorsstringnullableHuman-readable description of what went wrong, e.g.
Not Found,Invalid page_info.codestringnullableMachine-readable error code. Only a few endpoints (checkout) emit one; omitted everywhere else.
CollectionDto
Admin representation of a collection (the collections resource, backed by the existing
ShopArticleCollection domain — api-design.md §17.4). Serialized snake_case via explicit
[JsonPropertyName] attributes, because the host's global JSON policy is camelCase.
idstring (uuid)titlestringnullableDisplay name of the collection.
handlestringnullableURL slug (the domain
Alias); unique within the tenant.body_htmlstringnullablecollection_typestringnullableCollection kind:
manual(hand-picked) orsmart(rule-driven).publishedbooleanWhether the collection is visible on the storefront.
sort_orderstringnullableDefault product sort order, e.g.
manual,best-selling,alpha-asc.products_countinteger (int32)imageStorefrontImageDtotemplate_namestringnullableStorefront template override;
nullfor the default.rules_jsonstringnullableSmart-collection rule definition (JSON);
nullfor manual collections.meta_titlestringnullablemeta_descriptionstringnullablecreated_atstring (date-time)updated_atstring (date-time)nullable
CollectionListResponse
Envelope returned by GET /admin/collections.
One page of collections. Paging is cursored: when more collections exist, the response carries a
Link header of the form <https://…?limit=50&page_info=…>; rel="next". Follow that
URL verbatim — the cursor embeds a fingerprint of the query that produced the page, and a request whose
query string no longer matches is rejected with 422. There is no total count and no last page link.
The page of collections, including unpublished ones (this is the admin read). Always present; an empty array when the tenant has none.
CollectionResponse
Envelope returned by GET /admin/collections/{id}, POST /admin/collections (201) and
PUT /admin/collections/{id}.
collectionCollectionDto
CreateCollectionRequest
Body for POST /api/{version}/admin/collections. title is required; collection_type defaults to manual.
titlestringnullablehandlestringnullablebody_htmlstringnullablecollection_typestringnullablepublishedbooleansort_orderstringnullabletemplate_namestringnullablerules_jsonstringnullablemeta_titlestringnullablemeta_descriptionstringnullable
StorefrontCustomFieldDto
Storefront custom field: a tenant-defined attribute (key, localized label, value, kind).
keystringnullablelabelstringnullablevaluestringnullabletypestringnullable
StorefrontFacetDto
Storefront filter facets: one entry per dimension (manufacturers, categories, colors, sizes, years,
countries, regions, custom_fields), each with its selectable values and product counts. Drives a
faceted-search / filter sidebar, mirroring ShopController's filters.
keystringnullableDimension key. One of a closed set, always emitted in this order:
manufacturers,categories,colors,sizes,years,countries,regions,custom_fields. No key is guaranteed to be present. A dimension is emitted only when at least one of its values has a non-zero product count for the current filters, so the key set is data-dependent — it varies by tenant, by language and by the filters on the request. Treat a missing key as "this dimension has nothing to offer here", not as an error, and never hard-code the key set observed on one tenant. Conversely, no key outside the eight above is ever returned.custom_fieldsis the catch-all for tenant-defined attributes: every configured custom field's options are flattened into this one dimension rather than getting a key of their own. On a wine tenant grape variety arrives here, alongside anything else the tenant has configured. Use each value'snamefor display and itsidas thecustom_field_idfilter token; there is no per-field grouping in the response.The selectable values for this dimension. Never empty — a dimension with no matching values is omitted from the response instead. Values whose count would be zero are dropped.
StorefrontFacetRefDto
One facet value: the id to filter by and its localized label. name is null while the tenant's
periodically-rebuilt filter snapshot doesn't yet carry the id — membership is still reported.
idstring (uuid)namestringnullable
StorefrontFacetValueDto
One selectable facet value: filter token id, localized name, matching product count.
idstringnullableFilter token to send back, as a string (GUIDs are rendered in their usual dashed form). Which query parameter it belongs to follows from the facet key:
categories→category_id,colors→color_id,sizes→size_id,years→year_id,countries→country_id,regions→region_id,custom_fields→custom_field_id,manufacturers→vendor.namestringnullableDisplay label in the request's language; may be
nullwhen the tenant left it untranslated.valuestringnullableRaw value for custom-field options;
nullfor entity-backed facets.countinteger (int32)Number of products that would match if this value were selected. Always greater than zero. Counts are self-excluding within a dimension: for a dimension the caller has already filtered on, the counts ignore that dimension's own selection, so the other options in the same list stay clickable.
StorefrontFlagDto
Storefront product badge/flag: display name and CSS colour.
namestringnullablecolorstringnullable
StorefrontImageDto
Storefront representation of a product image. An image is an object (id, src, alt,
position, width, height) rather than a bare URL, so clients get the gallery order,
accessibility text, and intrinsic dimensions.
idstring (uuid)srcstringnullablepreview_srcstringnullablealtstringnullablepositioninteger (int32)widthinteger (int32)heightinteger (int32)
StorefrontManufacturerDto
Storefront manufacturer (vendor): id, display name, logo URL.
idstring (uuid)namestringnullablelogo_urlstringnullable
StorefrontPriceRangeDto
The price range of a filtered catalog: what a price slider's ends should be. Money is in the request's
active currency, quoted gross — the same basis as price_min/price_max take.
Kept out of the facet list on purpose. A facet value is an id with a count, and a price range is
neither, so a client that renders facets generically would have had to special-case it anyway.
minnumber (double)Cheapest gross price in the matching set.
maxnumber (double)Dearest gross price in the matching set.
currencystringnullableISO code the two figures are in.
StorefrontProductDto
Storefront representation of a catalog product. This is the list projection: it carries the product's
gross retail price range (price_min/price_max in currency) and variant count so a
listing/collection card can show a price range and an "N options" badge, mirroring a Shopify storefront
listing. The full variant list is served by the detail read and /{id}/variants.
idstring (uuid)handlestringnullabletitlestringnullablebody_htmlstringnullableimagestringnullableavailablebooleancurrencystringnullableprice_minnumber (double)Lowest variant gross price;
0when the product has no priced variant.price_maxnumber (double)Highest variant gross price;
0when the product has no priced variant.variants_countinteger (int32)Number of active, published variants.
manufacturerStorefrontManufacturerDtoratingnumber (double)Average approved rating (0 when unrated).
rating_countinteger (int32)Number of approved ratings.
Tenant-defined custom attributes for the representative variant.
The product's variants (list-level view from the catalog index). Tax rate, alcohol volume and option group are not carried by the index and are left at their defaults here; the single-product detail read returns the fully-resolved variants. For a text search only matching variants appear — see Cloudwawi.Shop.Api.V2026_04_01.Storefront.StorefrontProductDto.VariantsCount for the true total.
StorefrontProductListResponse
Envelope returned by GET /products and GET /collections/{id}/products.
One page of products, in the order requested by order. Paging is cursored: when more products
match, the response carries a Link header of the form
<https://…?…&limit=50&page_info=…>; rel="next". Follow that URL verbatim — the cursor
embeds a fingerprint of the filters that produced the page, and a request whose filters no longer match
is rejected with 422. There is no total count and no last page link.
The page of products. Always present; an empty array when nothing matches.
Filter facets for this result set — identical to what the matching
/facetsendpoint returns for the same filters. Present only when the request passedinclude=facets; the property is omitted entirely otherwise (it is nevernullon the wire).price_rangeStorefrontPriceRangeDto
StorefrontQuantityRuleDto
Storefront variant quantity rule: minimum, optional maximum, and increment (step).
minnumber (double)maxnumber (double)nullableincrementnumber (double)
StorefrontVariantDto
Storefront representation of a product variant, including the product attributes the storefront page
renders. Money fields are in the enclosing response's currency — Cloudwawi.Shop.Api.V2026_04_01.Storefront.StorefrontProductDetailDto.Currency
on the detail read, the sibling currency field on the /{id}/variants envelope.
idstring (uuid)product_idstring (uuid)handlestringnullabletitlestringnullableskustringnullableoption_groupstringnullablepricenumber (double)Display price (gross, tax included).
net_pricenumber (double)compare_at_pricenumber (double)Pre-discount price when on offer;
0otherwise.tax_ratenumber (double)availablebooleaninventory_quantitynumber (double)descriptionstringnullabledescription_additionalstringnullableyearstringnullablesizestringnullableareastringnullablecountrystringnullablealcohol_volumenumber (double)nullablequantity_ruleStorefrontQuantityRuleDtoTenant-defined custom attributes for this variant.
factsheet_urlstringnullableRelative URL of the generated variant factsheet PDF.
StorefrontVariantFacetsDto
A variant's facet membership. Each entry pairs the filter token a client sends back with its localized label, so a card can render attribute chips and a client can filter in-memory without a second call.
regionStorefrontFacetRefDtovintageStorefrontFacetRefDtoTenant-defined dropdown options (grape variety, for tenants that model it). Each carries the owning field's
field_keyso a client can select the field it cares about.
StorefrontVariantOptionDto
A selected tenant-defined option, qualified by the custom field that owns it.
field_keystringnullableStable machine key of the owning custom field (falls back to its numeric code).
field_labelstringnullableLocalized display name of the owning custom field.
idstring (uuid)namestringnullable
UpdateCollectionRequest
Body for PUT /api/{version}/admin/collections/{id}. Every field is optional; only the supplied
ones are applied. collection_type is immutable after creation and is not accepted here.
titlestringnullablehandlestringnullablebody_htmlstringnullablepublishedbooleannullablesort_orderstringnullabletemplate_namestringnullablerules_jsonstringnullablemeta_titlestringnullablemeta_descriptionstringnullable
