Skip to content

Collections

Admin

Collections 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.

GET

/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.

AuthenticationToken requiredAdmin tier. Requires an RBAC-gated vnstat_ access token, sent as Authorization: Bearer … or X-Vinosoft-Access-Token.

Query parameters

NameTypeDescription
limit
optional
integer (int32)

Responses

curl -X GET \
  'https://<your-shop-domain>/api/2026-04-01/admin/collections' \
  -H 'Accept: application/json' \
  -H 'Authorization: Bearer vnstat_<token>'
Response200example shape
{
  "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"
    }
  ]
}
POST

/api/{version}/admin/collections

Creates a collection and returns it as stored. A missing body and a rejected field both surface as 422.

AuthenticationToken requiredAdmin tier. Requires an RBAC-gated vnstat_ access token, sent as Authorization: Bearer … or X-Vinosoft-Access-Token.

Request body

application/json, text/json, application/*+json · optional

CreateCollectionRequest

Body for POST /api/{version}/admin/collections. title is required; collection_type defaults to manual.

  • titlestringnullable
  • handlestringnullable
  • body_htmlstringnullable
  • collection_typestringnullable
  • publishedboolean
  • sort_orderstringnullable
  • template_namestringnullable
  • rules_jsonstringnullable
  • meta_titlestringnullable
  • meta_descriptionstringnullable

Responses

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"
}'
Response201example shape
{
  "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"
  }
}
GET

/api/{version}/admin/collections/{id}

One collection.

AuthenticationToken requiredAdmin tier. Requires an RBAC-gated vnstat_ access token, sent as Authorization: Bearer … or X-Vinosoft-Access-Token.

Path parameters

NameTypeDescription
id
required
string (uuid)

Responses

curl -X GET \
  'https://<your-shop-domain>/api/2026-04-01/admin/collections/<id>' \
  -H 'Accept: application/json' \
  -H 'Authorization: Bearer vnstat_<token>'
Response200example shape
{
  "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"
  }
}
PUT

/api/{version}/admin/collections/{id}

Updates a collection and returns it as stored.

AuthenticationToken requiredAdmin tier. Requires an RBAC-gated vnstat_ access token, sent as Authorization: Bearer … or X-Vinosoft-Access-Token.

Path parameters

NameTypeDescription
id
required
string (uuid)

Request body

application/json, text/json, application/*+json · optional

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.

  • titlestringnullable
  • handlestringnullable
  • body_htmlstringnullable
  • publishedbooleannullable
  • sort_orderstringnullable
  • template_namestringnullable
  • rules_jsonstringnullable
  • meta_titlestringnullable
  • meta_descriptionstringnullable

Responses

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"
}'
Response200example shape
{
  "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"
  }
}
DELETE

/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.

AuthenticationToken requiredAdmin tier. Requires an RBAC-gated vnstat_ access token, sent as Authorization: Bearer … or X-Vinosoft-Access-Token.

Path parameters

NameTypeDescription
id
required
string (uuid)

Responses

curl -X DELETE \
  'https://<your-shop-domain>/api/2026-04-01/admin/collections/<id>' \
  -H 'Accept: application/json' \
  -H 'Authorization: Bearer vnstat_<token>'
GET

/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.

AuthenticationToken requiredAdmin tier. Requires an RBAC-gated vnstat_ access token, sent as Authorization: Bearer … or X-Vinosoft-Access-Token.

Path parameters

NameTypeDescription
id
required
string (uuid)

Query parameters

NameTypeDescription
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. "price asc" | "created desc" | "title asc".

color_id
optional
string

Colour facet ids (facets[key=colors].values[].id).

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 (facets[key=custom_fields].values[].id) — the dimension grape variety and other tenant-defined dropdowns surface under.

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

true keeps only products that are buyable right now. Omit it — do not send available=false — to list the whole catalog including sold-out products, which is the default. false is accepted and means the same as omitting it; there is no "sold out only" listing.

on_sale
optional
boolean

true keeps only products with at least one variant on offer — a retail price whose compare_at_price is above the price actually charged. Same convention as available: omit for the whole catalog.

include
optional
string

Comma-separated extras to embed in the response. facets adds the facet set for the current filters, saving the separate /facets round trip when first painting a sidebar next to a grid. Ask for it on the first page only. Facets describe the whole filtered catalog, not the page, so they are identical on every page — and recomputing them costs an extra search per filtered dimension.

IncludeFacets
optional
boolean

Whether include asked for the embedded facet set.

Responses

curl -X GET \
  'https://<your-shop-domain>/api/2026-04-01/admin/collections/<id>/products' \
  -H 'Accept: application/json' \
  -H 'Authorization: Bearer vnstat_<token>'
Response200example shape
{
  "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.

  • errorsstringnullable

    Human-readable description of what went wrong, e.g. Not Found, Invalid page_info.

  • codestringnullable

    Machine-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)
  • titlestringnullable

    Display name of the collection.

  • handlestringnullable

    URL slug (the domain Alias); unique within the tenant.

  • body_htmlstringnullable
  • collection_typestringnullable

    Collection kind: manual (hand-picked) or smart (rule-driven).

  • publishedboolean

    Whether the collection is visible on the storefront.

  • sort_orderstringnullable

    Default product sort order, e.g. manual, best-selling, alpha-asc.

  • products_countinteger (int32)
  • template_namestringnullable

    Storefront template override; null for the default.

  • rules_jsonstringnullable

    Smart-collection rule definition (JSON); null for manual collections.

  • meta_titlestringnullable
  • meta_descriptionstringnullable
  • created_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.

  • collectionsCollectionDto[]nullable

    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}.

CreateCollectionRequest

Body for POST /api/{version}/admin/collections. title is required; collection_type defaults to manual.

  • titlestringnullable
  • handlestringnullable
  • body_htmlstringnullable
  • collection_typestringnullable
  • publishedboolean
  • sort_orderstringnullable
  • template_namestringnullable
  • rules_jsonstringnullable
  • meta_titlestringnullable
  • meta_descriptionstringnullable

StorefrontCustomFieldDto

Storefront custom field: a tenant-defined attribute (key, localized label, value, kind).

  • keystringnullable
  • labelstringnullable
  • valuestringnullable
  • typestringnullable

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.

  • keystringnullable

    Dimension 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_fields is 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's name for display and its id as the custom_field_id filter 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.

  • idstringnullable

    Filter 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: categoriescategory_id, colorscolor_id, sizessize_id, yearsyear_id, countriescountry_id, regionsregion_id, custom_fieldscustom_field_id, manufacturersvendor.

  • namestringnullable

    Display label in the request's language; may be null when the tenant left it untranslated.

  • valuestringnullable

    Raw value for custom-field options; null for 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.

  • namestringnullable
  • colorstringnullable

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)
  • srcstringnullable
  • preview_srcstringnullable
  • altstringnullable
  • positioninteger (int32)
  • widthinteger (int32)
  • heightinteger (int32)

StorefrontManufacturerDto

Storefront manufacturer (vendor): id, display name, logo URL.

  • idstring (uuid)
  • namestringnullable
  • logo_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.

  • currencystringnullable

    ISO 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)
  • handlestringnullable
  • titlestringnullable
  • body_htmlstringnullable
  • imagestringnullable
  • availableboolean
  • currencystringnullable
  • price_minnumber (double)

    Lowest variant gross price; 0 when the product has no priced variant.

  • price_maxnumber (double)

    Highest variant gross price; 0 when the product has no priced variant.

  • variants_countinteger (int32)

    Number of active, published variants.

  • ratingnumber (double)

    Average approved rating (0 when unrated).

  • rating_countinteger (int32)

    Number of approved ratings.

  • custom_fieldsStorefrontCustomFieldDto[]nullable

    Tenant-defined custom attributes for the representative variant.

  • variantsStorefrontVariantDto[]nullable

    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.

  • productsStorefrontProductDto[]nullable

    The page of products. Always present; an empty array when nothing matches.

  • facetsStorefrontFacetDto[]nullable

    Filter facets for this result set — identical to what the matching /facets endpoint returns for the same filters. Present only when the request passed include=facets; the property is omitted entirely otherwise (it is never null on the wire).

StorefrontQuantityRuleDto

Storefront variant quantity rule: minimum, optional maximum, and increment (step).

  • minnumber (double)
  • maxnumber (double)nullable
  • incrementnumber (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)
  • handlestringnullable
  • titlestringnullable
  • skustringnullable
  • option_groupstringnullable
  • pricenumber (double)

    Display price (gross, tax included).

  • net_pricenumber (double)
  • compare_at_pricenumber (double)

    Pre-discount price when on offer; 0 otherwise.

  • tax_ratenumber (double)
  • availableboolean
  • inventory_quantitynumber (double)
  • descriptionstringnullable
  • description_additionalstringnullable
  • yearstringnullable
  • sizestringnullable
  • areastringnullable
  • countrystringnullable
  • alcohol_volumenumber (double)nullable
  • custom_fieldsStorefrontCustomFieldDto[]nullable

    Tenant-defined custom attributes for this variant.

  • factsheet_urlstringnullable

    Relative 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.

StorefrontVariantOptionDto

A selected tenant-defined option, qualified by the custom field that owns it.

  • field_keystringnullable

    Stable machine key of the owning custom field (falls back to its numeric code).

  • field_labelstringnullable

    Localized 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.

  • titlestringnullable
  • handlestringnullable
  • body_htmlstringnullable
  • publishedbooleannullable
  • sort_orderstringnullable
  • template_namestringnullable
  • rules_jsonstringnullable
  • meta_titlestringnullable
  • meta_descriptionstringnullable