Public - BevvyTech/BrewskiDocs GitHub Wiki
Brewski exposes a read-only surface for marketing/landing pages. These endpoints do not require authentication, but only return data for teams that have enabled their public site.
| Method | Path | Description |
|---|---|---|
GET |
/public/teams |
List all teams that have a public presence. |
GET |
/public/:identifier |
Fetch the public profile for a team (slug or UUID). |
GET |
/public/:identifier/logo |
Redirect to the team logo asset when it is publicly visible. |
GET |
/public/:identifier/beers/:beerId/gallery |
Return public gallery images for a specific beer. |
GET |
/public/beers/latest |
List the most recently active beers with current stock. |
GET |
/public/beers/:beerId |
Fetch the public details for a single beer. |
GET |
/public/styles |
Retrieve the BJCP style catalogue (optionally including substyles). |
GET |
/public/upsells |
List active upsell accessories (optionally filtered by coupler). |
GET |
/public/groups |
List approved brewery groups with public team summaries. |
GET |
/public/groups/:slug/beers |
Fetch a group hero + all in-stock beers from member breweries. |
GET |
/public/team-promotion |
Fetch the currently promoted team, promotion image, and latest beers. |
GET |
/public/hero |
Return the marketplace hero banners (carousel) with copy, CTA, and images. |
GET |
/public/feature-flags |
Resolve feature flag states for the marketing and shop front-ends. |
GET |
/public/shop/navigation |
List storefront navigation toggles grouped by section/item. |
GET |
/public/health/services |
Aggregate health status for API/Admin/Shop/Show surfaces. |
GET |
/public/game-score/leaderboard |
Return the top saved scores for minigames such as Keg Kong. |
POST |
/public/analytics/events |
Ingest storefront analytics events (hashed identifiers only). |
-
Query Parameters
-
keys[](required, repeatable) — specific flag keys to evaluate. Up to 25 per request. -
defaultEnabled(optionaltrue|false) — fallback state used when a flag needs to be auto-created; defaults tofalse.
-
-
Response 200
{ "flags": [ { "key": "shop.top-nav.partner-links", "enabled": false }, { "key": "shop.product.compare-button", "enabled": false } ] } -
Notes
- Unknown keys are created on the fly so they can be managed later in the admin UI; repeated requests with new keys will mint corresponding
feature_flagsrows. - The endpoint never surfaces descriptions or metadata—only the machine-readable code and evaluated state.
-
shop.product.compare-buttontoggles the compare CTA on the beer detail page of the Indie Brewer storefront.
- Unknown keys are created on the fly so they can be managed later in the admin UI; repeated requests with new keys will mint corresponding
-
Response 200
{ "items": [ { "section": "menu.top", "item": "home", "enabled": true }, { "section": "menu.top", "item": "basket", "enabled": true } ] } -
Notes
- The list is sorted alphabetically by
sectionand thenitem. - Missing rows are auto-seeded server-side so the payload always covers the known navigation surface.
- Clients should treat unknown
section/itemcodes as future expansion and ignore entries they do not understand.
- The list is sorted alphabetically by
-
Response 200
{ "generatedAt": "2025-10-27T11:40:00.000Z", "services": { "api": { "key": "api", "name": "Public API", "url": "https://api.brewskiapp.com/health", "status": "ok", "message": "Healthy (32 ms)", "latencyMs": 32.1, "checkedAt": "2025-10-27T11:39:59.980Z" }, "admin": { "key": "admin", "name": "Admin UI", "url": "https://admin.brewskiapp.com/health.json", "status": "ok", "message": "Healthy (85 ms)", "latencyMs": 84.7, "checkedAt": "2025-10-27T11:39:59.732Z" }, "show": { "key": "show", "name": "Show Site", "url": "https://show.brewskiapp.com/health.json", "status": "ok", "message": "Healthy (64 ms)", "latencyMs": 64.2, "checkedAt": "2025-10-27T11:39:59.645Z" }, "shop": { "key": "shop", "name": "Shop Frontend", "url": "https://indiebrewer.com/health.json", "status": "degraded", "message": "Reported status: maintenance", "latencyMs": 110.5, "checkedAt": "2025-10-27T11:39:59.593Z" } } } -
Notes
- The API performs a fast database connectivity probe (
SELECT 1) to confirm the core service is operational before returningstatus: "ok". - Admin/Show/Shop status checks proxy their respective
health.jsonendpoints with a 5 second timeout and classifystatusstrings ofok,degraded, orerror. Missing or unparsable payloads report asdegraded. - Responses include per-service latency in milliseconds and the precise timestamp each check completed (
checkedAt).
- The API performs a fast database connectivity probe (
- Designed for the static
health.brewskiapp.comdashboard but available to any public client; sensitive metadata is never exposed.
-
Response 200
{ "leaderboard": [ { "id": "8b8510f6-5536-4b39-9c53-3f8d33756f5f", "rank": 1, "playerName": "Barrel Rollers", "score": 1280, "game": "kong", "recordedAt": "2025-07-21T10:00:00.000Z" }, { "id": "0f2e5330-bcda-4a36-9f0f-9d16ca2f1f2b", "rank": 2, "playerName": "Anonymous Driver", "score": 1160, "game": "kong", "recordedAt": "2025-07-20T09:42:00.000Z" } ], "meta": { "totalPlayers": 2 } } -
Notes
- Scores are deduplicated per player by selecting their highest saved score, then ranked in descending order (ties favour the earlier timestamp).
- At most 100 unique players are returned—the list may be shorter while the game is new.
- Player names fall back to “Anonymous Driver” when no storefront display name is configured.
-
Query Parameters
-
region(optional) —all,england,scotland,wales, orni. When omitted orall, returns every eligible group. -
page(optional, default1) — 1-indexed page number. -
pageSize(optional, default18, max50) — number of groups per page.
-
-
Response 200
{ "groups": [ { "id": "6f84c0d5-9a3d-4a67-9fc7-3df1e6d079a4", "slug": "northern-alliance", "name": "Northern Alliance", "description": "Shared distribution across the North.", "regionLabel": "North England", "imageUrl": "https://cdn.brewskiapp.com/groups/northern-alliance.jpg", "teamCount": 3, "createdAt": "2025-02-01T09:00:00.000Z", "updatedAt": "2025-02-05T09:30:00.000Z", "teams": [ { "id": "d43c046a-10a1-4f52-bd0a-9bf16f828ab7", "name": "Lantern Brewery", "slug": "lantern-brewery", "logoUrl": "https://assets.brewskiapp.com/logos/lantern.png", "summary": { "title": "Lantern Brewery", "subtitle": "Wild-fermented saisons" }, "description": "Wild-fermented saisons brewed in Bristol.", "location": { "town": "Bristol", "country": "GB" } }, { "id": "f1c8ad79-3289-4c13-97a8-20096bb48fe3", "name": "Coastline Brewing", "slug": "coastline-brewing", "logoUrl": null, "summary": { "title": "Coastline Brewing", "subtitle": "Island-inspired brews" }, "description": null, "location": null } ] } ], "page": 1, "pageSize": 18, "total": 32, "pages": 2 } -
Notes
- Only brewery groups that are
active, markedshopVisible, and have approved imagery/content are returned. Results are sorted alphabetically by group name. - Group member lists include active teams only. Team metadata is sourced from
public_team_settings_vso the payload mirrors the storefront-safe profile (name, public slug, optional logo, summary, and approximate location). - Teams without a public logo keep
logoUrl: null, and their summary fields may resolve tonullwhen no storefront title/subtitle is configured.
- Only brewery groups that are
-
Path Parameters
-
slug(required) — Case-insensitive group slug.
-
-
Response 200
{ "group": { "id": "6f84c0d5-9a3d-4a67-9fc7-3df1e6d079a4", "slug": "northern-alliance", "name": "Northern Alliance", "description": "Shared distribution across the North.", "regionLabel": "North England", "imageUrl": "https://cdn.brewskiapp.com/groups/northern-alliance.jpg", "createdAt": "2025-02-01T09:00:00.000Z", "updatedAt": "2025-02-05T09:30:00.000Z", "teamCount": 3, "teams": [ { "id": "d43c046a-10a1-4f52-bd0a-9bf16f828ab7", "name": "Lantern Brewery", "slug": "lantern-brewery", "logoUrl": "https://assets.brewskiapp.com/logos/lantern.png", "summary": { "title": "Lantern Brewery", "subtitle": "Wild-fermented saisons" }, "description": "Wild-fermented saisons brewed in Bristol.", "location": { "town": "Bristol", "country": "GB" } } ] }, "beers": [ { "id": "b9af7396-4970-4e5c-83e2-051752198bdc", "breweryId": "d43c046a-10a1-4f52-bd0a-9bf16f828ab7", "name": "Hop Heaven", "style": "IPA", "description": "Hazy IPA bursting with Citra and Mosaic.", "targetAbv": 6.5, "colorHex": "#FADB7A", "badgeImageUrl": "https://cdn.brewskiapp.com/badges/hop-heaven.png", "badgeIconUrl": "https://cdn.brewskiapp.com/badges/hop-heaven-icon.png", "totalAvailable": 24, "lastBatchDate": "2025-02-04T11:00:00.000Z", "containers": [ { "id": "2c9a2a94-58db-46f7-8676-6f379a83661a", "name": "30L Keg", "type": "keg", "volumeMl": 30000, "availableUnits": 12, "storefrontVariantId": "var_12345", "priceMinor": 12500, "price": "£125.00", "currencySymbol": "£", "couplerType": null } ], "team": { "id": "d43c046a-10a1-4f52-bd0a-9bf16f828ab7", "name": "Lantern Brewery", "slug": "lantern-brewery", "logoUrl": "https://assets.brewskiapp.com/logos/lantern.png", "publicSite": { "title": "Wild-fermented saisons", "subtitle": "Small batches, big flavour", "showPrices": false }, "contact": { "email": "[email protected]", "telephone": "+44 20 7946 0992", "website": "https://lantern.example/", "soldByDisclaimer": "Sold and shipped by Lantern Brewery Ltd.", "socialLinks": { "instagram": "https://instagram.com/lanternbrew" } }, "awrsUrn": "XAW12345678901", "awrsVerified": true, "hasOffLicenceShipping": false, "offLicenceApproved": false, "location": { "town": "Bristol", "country": "GB" } } } ] } -
Response 404
{ "message": "Group not found" } -
Notes
- The slug resolves against
brewery_groups.slugand is matched case-insensitively. - Groups must be
active, markedshopVisible, and have approved content + imagery. Pending/disabled groups return404. - Member teams are restricted to active memberships. Public metadata (name, slug, optional logo, summaries, and coarse location) comes from
public_team_settings. - Beer inventory aggregates all storefront-visible variants owned by member breweries.
priceis formatted for display, whilepriceMinorexposes the raw integer value for commerce integrations. - When no active inventory exists, the endpoint still returns the group hero with an empty
beersarray so storefronts can surface “back soon” messaging.
- The slug resolves against
-
Response 200:
{ "teams": [ { "id": "d43c046a-10a1-4f52-bd0a-9bf16f828ab7", "name": "Lantern Brewery", "slug": "lantern-brewery", "locale": "en-GB", "currencySymbol": "£", "description": "Wild-fermented saisons brewed in Bristol.", "logoUrl": "https://assets.brewskiapp.com/logos/d43c046a.png", "location": { "town": "Bristol", "country": "GB" }, "publicSite": { "title": "Wild-fermented saisons", "subtitle": "Small batches, big flavour", "backgroundColor": "#FFFFFF", "textColor": "#111827", "useTeamLogo": true, "logoBackgroundColor": "#FFFFFF", "logoCornerRadius": 5, "showPrices": false, "pricebookId": null }, "contact": { "email": "[email protected]", "telephone": "+44 20 7946 0992", "website": "https://lantern.example/", "soldByDisclaimer": "Sold and shipped by Lantern Brewery Ltd.", "socialLinks": { "instagram": "https://instagram.com/lanternbrew", "tiktok": "https://www.tiktok.com/@lanternbrew" } }, "awrsUrn": "XAW12345678901", "awrsVerified": true, "hasOffLicenceShipping": true, "offLicenceApproved": true, "operatingMode": "shop" } ] }-
locationis included when the team has provided town/country details in Settings. -
descriptionsurfaces the long-form copy from Settings → Shop profile; storefronts can fall back to the subtitle when this isnull. -
contactmirrors the public contact panel (email, optional telephone/website, sold-by disclaimer, and any social links saved withhttps://URLs). -
hasOffLicenceShippingcombined withoffLicenceApprovedtells the storefront whether to render the “Can sell direct” badge.
-
-
Path Parameters:
identifieris either the team UUID or the public slug (case-insensitive). -
Response 200:
{ "team": { "id": "d43c046a-10a1-4f52-bd0a-9bf16f828ab7", "name": "Lantern Brewery", "locale": "en-GB", "currencySymbol": "£", "slug": "lantern-brewery", "location": { "town": "Bristol", "country": "GB" }, "publicSite": { "title": "Wild-fermented saisons", "subtitle": "Small batches, big flavour", "backgroundColor": "#FFFFFF", "textColor": "#111827", "useTeamLogo": true, "logoBackgroundColor": "#FFFFFF", "logoCornerRadius": 5, "showPrices": false, "pricebookId": null }, "logoUrl": "https://assets.brewskiapp.com/logos/d43c046a.png", "description": "Wild-fermented saisons brewed in Bristol.", "contact": { "email": "[email protected]", "telephone": "+44 20 7946 0992", "website": "https://lantern.example/", "soldByDisclaimer": "Sold and shipped by Lantern Brewery Ltd.", "socialLinks": { "instagram": "https://instagram.com/lanternbrew" } }, "awrsUrn": "XAW12345678901", "awrsVerified": true, "hasOffLicenceShipping": true, "offLicenceApproved": true } }-
logoUrlis only populated when the team has chosen to expose their logo publicly. -
contactmirrors the Shop profile fields saved in the Admin; social links are normalised tohttps://URLs. -
hasOffLicenceShippingandoffLicenceApproveddetermine whether storefronts should treat the brewery as able to sell direct.
-
-
Behaviour: Issues a
302redirect to the stored logo asset when one is available and the public site is configured to display it. -
Errors:
404if the team has not enabled their public site or no logo is available.
ℹ️ Public endpoints never surface private billing/payment data—only the fields required by the marketing site (name, localisation, logo, and themed copy).
-
Path Parameters
-
identifier: Team UUID or public slug (case-insensitive). -
beerId: Beer UUID. Only beers marked asshowPublicandactiveare returned.
-
-
Response 200
{ "team": { "id": "d43c046a-10a1-4f52-bd0a-9bf16f828ab7", "name": "Lantern Brewery", "slug": "lantern-brewery", "currencySymbol": "£", "locale": "en-GB" }, "beerId": "9c3b06ff-8e52-4169-b35a-b80fc0e4dab7", "count": 2, "gallery": [ { "id": "5ddf8e2a-7ad9-47c2-8a62-1a68fa3ada72", "imageUrl": "https://assets.brewskiapp.com/public/beer-9c3/gallery/pour.png", "thumbnailUrl": "https://assets.brewskiapp.com/public/beer-9c3/gallery/pour-thumb.png", "iconUrl": "https://assets.brewskiapp.com/public/beer-9c3/gallery/pour-thumb.png", "tag": "keg", "caption": "Freshly poured pint", "position": 0, "createdAt": "2025-03-31T15:07:18.012Z", "updatedAt": "2025-03-31T15:07:18.012Z" } ] } -
Errors
-
404when:- The team identifier does not resolve to a public team.
- The beer does not belong to the team, is inactive, or is not marked
showPublic.
-
-
Notes
- Results are ordered by gallery
positionand creation time. -
thumbnailUrl/iconUrlpoint to a 160×160 rendition suitable for cards or grids;imageUrlremains the full 1024×1024 asset.
- Results are ordered by gallery
-
Query Parameters
-
limit(optional, 1–40, default 8) limits how many beers are returned. -
page(optional, ≥ 1) andpage_size(optional, 1–40) enable cursor-less pagination. -
team_id/team_slug(optional) restrict the listing to a single brewery. -
packaging_group(optionalkeg|cask|smallpack) narrows the inventory slice before facets are calculated. -
packaging(optionalkeg|cask|smallpack|other) applies an additional filter so the response only includes beers that currently have stock within the selected bucket (othercaptures containers without a recognised type). -
style(optional, repeatable) filters the listing to beers whose BJCP style code or mapped label matches the supplied values. Multiplestyleparameters are supported; the legacystylescomma-separated parameter remains accepted for backwards compatibility. -
colour(optionalpale|gold|amber|brown|black|unknown) filters the listing to beers that fall within the requested palette bucket. The US spellingcoloris accepted as an alias. -
abv(optionalunder-4|4-6|6-8|over-8) filters to beers whose ABV falls within the supplied band. Bands are inclusive of the lower bound and exclusive of the upper bound (e.g.4-6covers 4.0% up to but not including 6.0%). -
allergen(optional, repeatable) narrows the listing to beers that match the requested dietary flags. Supported values aregluten(contains gluten),gluten-free,vegan, andvegetarian. Multipleallergenparameters combine with AND semantics. -
facets(optional, repeatable or comma-separated string) requests aggregate data for one or more supported facet groups. Valid values today arestyles,packaging,colour,abv, andallergens.
-
-
Response 200
{ "beers": [ { "id": "9c3b06ff-8e52-4169-b35a-b80fc0e4dab7", "breweryId": "d43c046a-10a1-4f52-bd0a-9bf16f828ab7", "name": "Summer Saison", "style": "Hazy IPA", "description": "Zesty saison fermented warm with Brett.", "targetAbv": 5.4, "colorHex": "#FADB7A", "badgeImageUrl": "https://assets.brewskiapp.com/badges/9c3b06ff.png", "badgeIconUrl": "https://assets.brewskiapp.com/badges/9c3b06ff-icon.png", "totalAvailable": 120, "lastBatchDate": "2025-03-21T12:14:31.000Z", "containers": [ { "id": "f3d8f3dc-42df-4d16-8187-4cc099c6abb7", "name": "30 L Keg", "type": "keg", "volumeMl": 30000, "availableUnits": 12, "storefrontVariantId": "4edd0a8f-7d5a-4a0c-9f5c-5d54f7738d36", "priceMinor": 12900, "price": "£129.00", "currencySymbol": "£", "couplerType": "s" } ], "team": { "id": "d43c046a-10a1-4f52-bd0a-9bf16f828ab7", "name": "Lantern Brewery", "slug": "lantern-brewery", "logoUrl": "https://assets.brewskiapp.com/logos/d43c046a.png", "description": "Wild-fermented saisons brewed in Bristol.", "publicSite": { "title": "Wild-fermented saisons", "subtitle": "Small batches, big flavour", "showPrices": false } } } ] }, "facets": { "styles": [ { "id": "21C", "label": "Hazy IPA", "count": 4, "selected": false } ], "packaging": [ { "id": "keg", "label": "Keg", "count": 6, "selected": false }, { "id": "cask", "label": "Cask", "count": 2, "selected": false }, { "id": "smallpack", "label": "Small Pack", "count": 5, "selected": false }, { "id": "other", "label": "Other", "count": 1, "selected": false } ], "abv": [ { "id": "under-4", "label": "Under 4%", "count": 1, "selected": false }, { "id": "4-6", "label": "4 – 6%", "count": 5, "selected": false }, { "id": "6-8", "label": "6 – 8%", "count": 3, "selected": false }, { "id": "over-8", "label": "Over 8%", "count": 2, "selected": false } ], "colour": [ { "id": "gold", "label": "Golden", "count": 3, "selected": false, "meta": { "swatch": "#E1B955" } }, { "id": "amber", "label": "Amber", "count": 2, "selected": false, "meta": { "swatch": "#C9793A" } } ], "allergens": [ { "id": "gluten", "label": "Contains gluten", "count": 7, "selected": false }, { "id": "gluten-free", "label": "Gluten-free", "count": 2, "selected": false }, { "id": "vegan", "label": "Vegan friendly", "count": 1, "selected": false }, { "id": "vegetarian", "label": "Vegetarian friendly", "count": 3, "selected": false } ] } - Notes
-
breweryIdechoes the owning team ID to simplify client-side filtering. - When a beer stores a BJCP/Brewski style code (for example
21C), the API emits the English category or substyle name (Hazy IPA); custom free-text styles are returned unchanged. - When a
packaging_groupis supplied, only containers that match the requested group are returned; other containers are filtered out. -
couplerTypeis populated for keg variants that specify a coupler compatibility in the admin tooling; consumers can use it to surface matching upsells. -
storefrontVariantIdcan benullwhen the brewery has not published an online-orderable variant. In that case the container still appears so the storefront can surface stock; the API populatesprice/priceMinorfrom the team’s public pricebook when available, otherwise they remainnull. - ABV bands prefer the product’s manual override when supplied on the storefront product record; otherwise they fall back to the beer’s target ABV. Dietary flags (
gluten-free,vegan,vegetarian) rely on the storefront product’s manual allergen string, with the beer’sallergensfield used as a secondary hint. - Facet responses include per-option counts and a
selectedflag that reflects any filters supplied in the query (for example,packaging=smallpackorstyle=21C). Colour facets also surface ameta.swatchhex value suitable for UI swatches. -
teamcontains the lightweight public profile already described inGET /public/teams. -
team.descriptionsurfaces the brewery bio configured in the Admin settings—use it to replace any placeholder copy on brewery detail pages. - When the listing is scoped to a single brewery (
team_id,team_slug, or the/brewery/:slugcatalogue source), themetablock also includesmatchedTeamDescriptionandmatchedTeamSubtitleso page builders can hydrate hero copy without re-fetching the profile.
-
Query Parameters
-
level(optional, integer, default1) — limits how deep the catalogue is expanded.0returns only BJCP categories;1includes the substyles for each category.
-
-
Response 200
{ "level": 1, "categories": [ { "id": "21", "code": "21", "name": "IPA", "substyles": [ { "code": "21A", "name": "American IPA" }, { "code": "21B", "name": "Specialty IPA" }, { "code": "21C", "name": "Hazy IPA" } ] } ] } -
Notes
- Level values above
1are clamped to1until deeper hierarchy levels are introduced. - Styles mirror the BJCP taxonomy used throughout the Admin and API; clients can cache this response safely as it only changes when the underlying standard is updated.
- Level values above
-
Path Parameters
-
beerId: Beer UUID.
-
-
Response 200
{ "beer": { "id": "9c3b06ff-8e52-4169-b35a-b80fc0e4dab7", "breweryId": "d43c046a-10a1-4f52-bd0a-9bf16f828ab7", "name": "Summer Saison", "style": "Hazy IPA", "description": "Zesty saison fermented warm with Brett.", "tastingNotes": "Tangerine zest, white pepper, bone-dry finish.", "ingredients": "Water, barley, wheat, orange peel, coriander, saison yeast", "allergens": "Gluten (barley, wheat)", "targetAbv": 5.4, "colorHex": "#FADB7A", "badgeImageUrl": "https://assets.brewskiapp.com/badges/9c3b06ff.png", "badgeIconUrl": "https://assets.brewskiapp.com/badges/9c3b06ff-icon.png", "totalAvailable": 120, "lastBatchDate": "2025-03-21T12:14:31.000Z", "containers": [ { "id": "f3d8f3dc-42df-4d16-8187-4cc099c6abb7", "name": "30 L Keg", "type": "keg", "volumeMl": 30000, "availableUnits": 12, "storefrontVariantId": "4edd0a8f-7d5a-4a0c-9f5c-5d54f7738d36", "priceMinor": 12900, "price": "£129.00", "currencySymbol": "£", "couplerType": "s" } ], "team": { "id": "d43c046a-10a1-4f52-bd0a-9bf16f828ab7", "name": "Lantern Brewery", "slug": "lantern-brewery", "logoUrl": "https://assets.brewskiapp.com/logos/d43c046a.png", "description": "Wild-fermented saisons brewed in Bristol.", "publicSite": { "title": "Wild-fermented saisons", "subtitle": "Small batches, big flavour", "showPrices": false } } } } -
Errors
-
404when the beer does not exist, is not public, or the owning team has not enabled their public site.
-
-
Notes
-
containersincludes only container types with stock available;totalAvailablereflects the sum of those containers. - Containers without a published storefront variant keep
storefrontVariantIdnull. Pricing is still returned when linked via the public pricebook, but is omitted entirely (null) when the brewery hides prices. - Style codes are converted to their en-GB names using the same mapping as
GET /public/beers/latest. -
couplerTypeexposes the keg coupler enum (when supplied) so storefront clients can highlight compatible hardware. -
tastingNotesandingredientssurface the brewery-authored copy when present; clients should hide the sections when the values arenull. -
allergensmirrors the public allergens copy; hide the row when the payload providesnull. - Asset URLs (
badgeImageUrl,badgeIconUrl,team.logoUrl) respect the incoming host so local development environments receiveliberator.locallinks instead oflocalhost.
-
-
Query Parameters
-
coupler(optional, repeatable) — filter by one or more coupler codes fromkeg_coupler_type. -
limit(optional, 1–50, default 20) — maximum number of upsells to return.
-
-
Response 200
{ "upsells": [ { "id": "1a2b3c4d-1111-2222-3333-444455556666", "slug": "s-type-coupler", "name": "S-Type Keg Coupler", "description": "Stainless S-type coupler supplied with check valve.", "priceMinor": 12900, "currencySymbol": "£", "price": "£129.00", "couplerType": "s", "displayOrder": 10, "currentStock": 6, "isSoldOut": false, "images": [ { "id": "img-01", "url": "https://assets.brewskiapp.com/upsells/1a2b3c4d/img-01-main.webp", "iconUrl": "https://assets.brewskiapp.com/upsells/1a2b3c4d/img-01-icon.webp", "isDefault": true } ] } ] } -
Notes
- Results are ordered by
displayOrderthencreatedAt. -
priceis provided when bothpriceMinorandcurrencySymbolare set; otherwise it isnull. -
currentStockreflects the latest operator-entered stock count.isSoldOutistruewhencurrentStockis zero or lower. -
imagescontain absolute URLs derived from the current request host; only the default image is flagged withisDefault: true.
- Results are ordered by
- Behaviour: Returns the brewery currently scheduled for the shop homepage. The endpoint automatically resolves the active promotion (or the next upcoming one). If no promotions exist, it falls back to the oldest team in the database so the homepage always has content.
-
Response 200:
{ "team": { "id": "d43c046a-10a1-4f52-bd0a-9bf16f828ab7", "name": "Lantern Brewery", "slug": "lantern-brewery", "logoUrl": "https://assets.brewskiapp.com/logos/d43c046a.png", "imageUrl": "https://assets.brewskiapp.com/promotions/d43c046a-home.png", "imageTitle": "Lantern rooftop pour", "imageDescription": "Hazy saison poured at sunset on the taproom roof.", "textNeedsBackground": true, "textBackground": "dark", "textColor": "rainbow", "mainTitle": "Featured brewery", "mainDescription": "Lantern Brewery leads our spring showcase with farmhouse ales and oak-aged specialties.", "startsAt": "2025-04-01T08:00:00.000Z", "isFallback": false, "beers": [ { "id": "9c3b06ff-8e52-4169-b35a-b80fc0e4dab7", "breweryId": "d43c046a-10a1-4f52-bd0a-9bf16f828ab7", "name": "Summer Saison", "style": "Hazy IPA", "description": "Zesty saison fermented warm with Brett.", "badgeImageUrl": "https://assets.brewskiapp.com/badges/9c3b06ff.png", "priceMinor": 580, "customerRating": 4.6, "createdAt": "2025-03-21T12:14:31.000Z", "updatedAt": "2025-03-21T12:14:31.000Z" } ] } }-
imageUrlisnullwhen the record is the fallback team (no explicit promotion on file). -
isFallback=trueindicates the response was derived from the oldest team rather than a scheduled promotion.
-
-
Errors: Always returns
200; the payload carriesteam: nullwhen no public teams exist. -
Notes
-
imageTitle/imageDescriptionstem from the promotional hero card (useful for alt text and captions). -
mainTitle/mainDescriptionsurface the headline copy configured in the admin UI; consumers can safely fall back to the team name if these arenull. -
breweryIdon each beer matches the promoted team’s ID so clients can perform joins without walking back to the parent payload. - Beer styles follow the same en-GB mapping noted above, converting stored BJCP codes to descriptive names.
-
textNeedsBackgroundtoggles an overlay block behind the hero copy; whentrue, pair it withtextBackground(dark,darkdeep,light, orlightdeep- 15% vs 85% overlays) andtextColor(black/white/rainbow) to style the text appropriately. - Each beer includes
priceMinor(default price, smallest currency unit ornull) andcustomerRating(0–5 scale,nullwhen unset).
-
- Behaviour: Returns the hero carousel rendered above the fold on the public shop homepage.
-
Response 200:
{ "heroes": [ { "id": "0f6e1fb2-0fa7-4e07-b994-0a28cf6c26d0", "category": "Marketplace highlight", "title": "Discover independent breweries", "message": "Fresh releases, direct from the source. Explore what’s pouring today.", "showButton": true, "buttonText": "Browse beers", "buttonLink": "/shop", "theme": "dark", "imageUrl": "https://cdn.brewskiapp.com/platform/hero-1738345600.jpg", "imageWidth": 1280, "imageHeight": 720, "displaySeconds": 8, "position": 0, "createdAt": "2025-06-02T10:05:44.123Z", "updatedAt": "2025-06-02T10:05:44.123Z" } ] }- The array is empty when no hero slides have been configured.
-
buttonLinkmay be an absolute URL or a path relative to the storefront origin. -
displaySecondsindicates how long (in seconds) the slide should remain visible before the carousel advances; storefronts should clamp to 3–120 seconds and fall back to 8 when omitted. -
imageUrlpoints to a JPEG already resized to a maximum height of 770 px and aligned to the caller’s host (development requests receiveliberator.local, production requests receive the CDN host). - Heroes are ordered by
positionfollowed bycreatedAt, matching the admin carousel order.
- Behaviour: Returns auxiliary homepage content (services strip, footer social links, and featured brewery groups) so the storefront can mirror the admin-configured layout.
-
Response 200:
{ "services": [ { "id": "89d5f8d7-97b0-4ea2-9ea9-a0d5f3495f3e", "title": "Free UK delivery", "description": "Complimentary shipping on orders over £200.", "iconKey": "icon-delivery-01", "linkUrl": "/shipping", "position": 0 } ], "socialLinks": [ { "id": "a11bb22c-3344-4555-8666-77889900aa11", "label": "Instagram", "iconClass": "fab fa-instagram", "url": "https://instagram.com/indiebrewer", "position": 0 } ], "featuredGroups": [ { "id": "f011b2e4-1234-4a6f-9b7e-0c1d2e3f4a5b", "position": 1, "team": { "id": "d43c046a-10a1-4f52-bd0a-9bf16f828ab7", "name": "Lantern Brewery", "publicSlug": "lantern-brewery", "logoUrl": "https://assets.brewskiapp.com/logos/d43c046a.png" }, "group": { "id": "8cb5b1e0-4321-4af0-9d3a-0e9f7b24c6aa", "name": "Northern Alliance", "slug": "northern-alliance", "imageUrl": null, "description": "Cask-led breweries pooling haulage to reach the Highlands.", "regionLabel": "Scotland" }, "groupTeamCount": 3, "groupTeams": [ { "id": "7b2b6d8c-98f4-4bf2-93a3-1234567890ab", "name": "Lantern Brewery", "slug": "lantern-brewery", "logoUrl": "https://assets.brewskiapp.com/logos/lantern.png", "summary": { "title": "Flagship pale ales", "subtitle": "Award-winning session beers" }, "description": "Proudly independent Manchester brewery specialising in hop-forward pales.", "location": { "town": "Manchester", "country": "England" } } ] } ] } -
Notes
-
logoUrlandimageUrlfields are normalised to the caller’s host (development requests receiveliberator.local, production requests receive CDN URLs). -
servicesandsocialLinksare ordered by their storedposition. -
featuredGroupscontains zero or more records ordered byposition; each entry ships the curated group metadata and the full list of active member breweries (groupTeams).
-
- Behaviour: Returns the configuration of the announcement strip displayed at the very top of the storefront. Values fall back to sensible defaults when the platform setting does not exist yet.
-
Response 200:
{ "enabled": true, "text": "Shipping delays expected due to bank holidays.", "link": "https://shop.brewskiapp.com/notices/shipping-delays" } -
Notes
-
enableddefaults tofalsewhen the toggle is not set in the admin UI. -
textandlinkreturnnullwhen unset; consumers should guard for empty strings before rendering links. - All fields are derived from the
notice_strip_*platform settings managed via the admin homepage tooling.
-
- Auth: Bearer token (storefront client access token).
-
Query Parameters
-
status(optional, repeatable) — restrict the listing to a subset of ticket states (pending,open,resolved,cancelled). -
teamId(optional UUID) — limit results to tickets linked to a specific brewery account that the client belongs to. -
page/pageSize(optional; defaults1/10, maximum50). -
search(optional string ≥ 1 char) — substring match on ticket subject and description.
-
-
Response 200
{ "tickets": [ { "id": "8ca3fd7c-5f87-42d4-a466-6ccb8f4f219d", "subject": "Problem downloading invoices", "description": "Downloads stall at 25%.", "status": "open", "priority": "normal", "originChannel": "shop", "createdAt": "2025-07-20T10:04:00.000Z", "updatedAt": "2025-07-20T10:15:27.000Z", "lastMessageAt": "2025-07-20T10:15:27.000Z", "closedAt": null, "firstResponseAt": "2025-07-20T10:12:03.000Z", "team": { "id": "0a8f6da1-06fe-4bfb-a7f4-4b8c2f7b1fd2", "name": "Indie Brewer Support" }, "metadata": { "requestedBy": { "type": "client", "clientId": "a4e57d2a-7dd9-4c1f-8b0d-3d6c88a0d201" } }, "creditCost": 1 } ], "pagination": { "page": 1, "pageSize": 10, "total": 3, "pages": 1 }, "counts": { "all": 3, "pending": 1, "open": 2, "resolved": 0, "cancelled": 0 } } -
Notes
- Only tickets tied to the authenticated storefront client are returned; brewery-side tickets raised in the Admin remain hidden.
-
teamreflects the brewery currently handling the request (Indie Brewer Supportwhen no specific brewery is selected). -
originChannelis alwaysshopfor tickets raised via this endpoint, allowing the Admin UI to badge the request appropriately. -
countssummarises ticket totals for quick filtering (including anallaggregate) so clients can render filter badges without issuing additional calls.
- Auth: Bearer token (storefront client access token).
-
Body (
application/json)-
subject(string, 3–200 chars) — short summary of the request. -
description(string, 1–20 000 chars) — detailed description or markdown body. -
priority(optional enumlow|normal|high|urgent, defaultnormal). -
teamId(optional UUID) — route straight to a brewery account the client belongs to; omit to contact Indie Brewer’s platform support.
-
-
Response 201
{ "ticket": { "id": "cb3e721d-a78c-4d9b-9d0a-0c74b74f64b1", "subject": "Help updating delivery address", "description": "We need to change the default drop site for next week.", "status": "pending", "priority": "normal", "originChannel": "shop", "createdAt": "2025-07-20T09:42:00.000Z", "updatedAt": "2025-07-20T09:42:00.000Z", "team": { "id": "0a8f6da1-06fe-4bfb-a7f4-4b8c2f7b1fd2", "name": "Indie Brewer Support" }, "metadata": { "requestedBy": { "type": "client", "clientId": "a4e57d2a-7dd9-4c1f-8b0d-3d6c88a0d201", "email": "[email protected]" } }, "creditCost": 1 }, "credits": { "allowance": 20, "remaining": 19, "used": 1, "defaultAllowance": 20, "period": { "id": "period-2025-07", "start": "2025-07-01T00:00:00.000Z", "end": "2025-07-31T23:59:59.999Z" } } } -
Errors
-
401unauthenticated. -
403whenteamIdreferences a brewery the client does not belong to. -
404whenteamIdcannot be resolved.
-
-
Notes
- Storefront-originated tickets leave
requesterIdempty and capture the submitter inmetadata.requestedBy. - Support credits are debited against the chosen brewery; when
teamIdis omitted the ticket consumes Indie Brewer’s platform allowance.
- Storefront-originated tickets leave
- Auth: Bearer token (storefront client access token).
-
Query Parameters
-
page/pageSize(optional; defaults1/20, max100).
-
-
Response 200
{ "ticket": { "id": "cb3e721d-a78c-4d9b-9d0a-0c74b74f64b1", "subject": "Help updating delivery address", "status": "open", "priority": "normal", "originChannel": "shop", "team": { "id": "0a8f6da1-06fe-4bfb-a7f4-4b8c2f7b1fd2", "name": "Indie Brewer Support" }, "createdAt": "2025-07-20T09:42:00.000Z", "updatedAt": "2025-07-20T10:01:00.000Z", "lastMessageAt": "2025-07-20T10:01:00.000Z", "metadata": {} }, "messages": [ { "id": "msg-01", "ticketId": "cb3e721d-a78c-4d9b-9d0a-0c74b74f64b1", "body": "Thanks for sending the details — we’re on it.", "bodyHtml": null, "createdAt": "2025-07-20T10:01:00.000Z", "updatedAt": "2025-07-20T10:01:00.000Z", "author": { "id": "support-user-1", "name": "Indie Brewer Support", "email": "[email protected]" }, "attachments": [] } ], "pagination": { "page": 1, "pageSize": 20, "total": 1, "pages": 1 } } -
Notes
- Only non-internal messages are returned; internal admin notes remain hidden.
- Attachments are returned as direct URLs (identical to admin storage URLs) — callers should treat them as ephemeral and avoid hotlinking outside the support flow.
-
author.idresolves to a support user for admin replies and to the storefront client id for customer replies;name/emailare populated from the available profile data in each case.
- Auth: Bearer token (storefront client access token).
-
Body (
multipart/form-data)-
body(string, required, 1–20 000 chars) — message body. -
attachments(optional file[], ≤ 5 MB each) — supporting files; attach the field multiple times for multiple uploads.
-
-
Response 201
{ "ticket": { "id": "cb3e721d-a78c-4d9b-9d0a-0c74b74f64b1", "status": "open", "priority": "normal", "originChannel": "shop", "team": { "id": "0a8f6da1-06fe-4bfb-a7f4-4b8c2f7b1fd2", "name": "Indie Brewer Support" }, "updatedAt": "2025-07-20T10:05:00.000Z", "lastMessageAt": "2025-07-20T10:05:00.000Z" }, "message": { "id": "msg-02", "ticketId": "cb3e721d-a78c-4d9b-9d0a-0c74b74f64b1", "body": "Here’s the spreadsheet you requested.", "createdAt": "2025-07-20T10:05:00.000Z", "updatedAt": "2025-07-20T10:05:00.000Z", "author": { "id": "client-123", "name": "Lantern Brewery", "email": "[email protected]" }, "attachments": [ { "id": "att-1", "fileName": "delivery-notes.pdf", "contentType": "application/pdf", "fileSizeBytes": 53211, "url": "https://assets.brewskiapp.com/support/cb3e721d/delivery-notes.pdf", "createdAt": "2025-07-20T10:05:00.000Z" } ] } } -
Errors
-
401unauthenticated. -
404ticket not owned by the caller. -
409ticket is closed (resolved/cancelled). -
413attachments too large.
-
-
Notes
- Messages are always created as non-internal entries; status changes must be performed by support staff.
- Attachments reuse the same storage as admin uploads. Do not expose URLs publicly outside authenticated flows.
-
Request Body
{ "events": [ { "name": "shop.checkout.completed", "occurredAt": "2025-11-05T14:21:33.512Z", "source": "shop", "environment": "production", "properties": { "mode": "group", "groupOrderId": "569be9e1-77d9-49fd-8c2c-8ee7fa5ad375", "itemCount": 12, "subtotalMinor": 45800 }, "identifiers": { "sessionId": "cb13901d-9ed5-4a2c-92a0-1e22f687dc42", "groupId": "e3d9baa9-6587-4b0b-8ce9-1725518175cf", "userId": "72d0adc3-65c9-4af6-9f74-566e3b8e6b3b" }, "hashes": { "sessionIdHash": "4f3d6f2c1b...", "groupIdHash": "8a563ef0a7...", "userIdHash": "20b137ea44..." }, "metadata": { "groupSlug": "northern-alliance", "orders": ["ORD-1001", "ORD-1002"], "pageRoute": "groupCheckout" } } ] } -
Limits & Validation
- Batches accept up to 25 events. Each object must supply a
nameand optional identifiers/hashes. - Raw identifiers are never persisted directly—UUIDs are normalised and hashed using the configured
ANALYTICS_HASH_SALTbefore storage. - Requests exceeding the configured rate limit (60/minute per IP) return 429 with
code: "RATE_LIMITED". - If analytics ingestion is disabled (
ANALYTICS_ENABLED=false) the endpoint responds with 204 No Content and discards the batch.
- Batches accept up to 25 events. Each object must supply a
-
Response 202
{ "accepted": 3 } -
Notes
- Unknown events are accepted as-is so new storefront instrumentation can roll out safely; downstream processing enforces schema rules per event name.
-
metadata.ordersshould contain storefront-visible order references only—never internal database IDs. - Missing or weak
ANALYTICS_HASH_SALTvalues cause ingestion to reject the batch with 500. Rotate salts using the documented runbook inInternal/TODO/analytics.md.