Every public endpoint lives under /api/storefront/v1/. Most sites never call them by hand: the storefront widget does it for them. Reach for this page when a site builds its own UI, or when a build pipeline reads the catalog server side.
Endpoints
A browser request must come from an origin authorized in Storefront: live, local, or staging.
GET https://wiregum.com/api/storefront/v1/products?organization=workspace-slug&products=wg_product_ref{
"data": [
{
"id": "wg_prod_9f2c",
"slug": "tote-bag",
"name": "Tote bag",
"images": ["https://..."],
"defaultVariantId": "wg_var_31a8",
"variants": [
{
"id": "wg_var_31a8",
"sku": "TOTE-BLK",
"title": "Black",
"optionValues": { "Colour": "Black" },
"unitAmount": 2500,
"currency": "eur",
"inStock": true
}
]
}
],
"page": { "limit": 50, "hasMore": true, "nextCursor": "wg_prod_9f2c" },
"shippingCountries": ["IT", "FR"],
"ui": { "addToCart": "Aggiungi al carrello" }
}A product carries one public identifier, id, plus its slug. Either can be used as a product reference in markup and in the products query parameter. Prefer id when the integration has to survive a slug change.
Variants report inStock, not a count. How many units are left is the merchant's own number, and a public storefront is readable by anyone including a competitor, so the exact figure is only returned to a caller holding the workspace API key, as stock. Overselling is prevented at checkout regardless: a cart that no longer fits the warehouse answers insufficient_stock.
Static site generators fetching products at build time, and server-rendered pages, send no browser origin. Generate a read-only API key in Storefront and pass it as a bearer token. The key only reads products: checkout sessions still require a browser request from an authorized domain. Keyed responses are never cached at the edge, since they are not the same answer everyone else gets.
The key is a secret, and the wg_sk_ prefix says so. It returns stock counts an authorized domain never sees, so it belongs in a server environment variable or a build secret and never in anything the browser downloads. Keys generated before this prefix existed start with wg_pk_; they still work and mean exactly the same thing, and rotating in Storefront issues the new shape.
# Build pipelines and server-rendered pages have no browser origin:
# authenticate with the workspace API key from WireGum Storefront instead.
curl "https://wiregum.com/api/storefront/v1/products?products=wg_product_ref" \
-H "Authorization: Bearer wg_sk_your_key"Paging the catalog
Listing requests return limit products, 50 by default and 100 at most, newest first. page.hasMore says whether the catalog was cut and page.nextCursor says where to carry on. A request that names products with product or products is a lookup rather than a listing and is never paged.
# Walk a catalog larger than one page.
GET /api/storefront/v1/products?organization=workspace-slug&limit=100
GET /api/storefront/v1/products?organization=workspace-slug&limit=100&cursor=wg_prod_9f2c
# Stop when page.hasMore is false.Rate limits
Both endpoints are limited per IP over a one minute window: 120 requests for products, 10 for checkout. Only requests that miss the edge cache count. Going over answers rate_limited with a Retry-After header, which cross-origin JavaScript is allowed to read.
POST https://wiregum.com/api/storefront/v1/checkout
Content-Type: application/json
{
"organization": "workspace-slug",
"locale": "it",
"shippingCountry": "IT",
"successPath": "/thank-you",
"cancelPath": "/cart",
"items": [
{ "variantId": "wg_variant_ref", "quantity": 1 }
]
}successPath and cancelPath are resolved against the storefront origin that created the checkout. Sites that need full control can send absolute successUrl and cancelUrl values instead, as long as they point to a domain authorized in WireGum Storefront. Stripe replaces a literal {CHECKOUT_SESSION_ID} placeholder in the success URL.
Errors
Every failed request answers with a code and an HTTP status. The code is the part to write logic against: it never changes meaning and never changes status. The error string next to it is a readable hint for logs and network tabs, and its wording can change at any time.
HTTP/1.1 403 Forbidden
Content-Type: application/json
{
"code": "domain_not_authorized",
"error": "This storefront domain is not authorized in WireGum settings."
}const response = await fetch(url);
const payload = await response.json();
if (!response.ok) {
// Branch on the code, never on the sentence.
if (payload.code === "domain_not_authorized") {
showSetupHelp();
}
throw new Error(payload.error);
}| Code | Status | Meaning |
|---|---|---|
invalid_request | 422 | The payload does not match the schema. The message names the fields at fault. |
invalid_api_key | 401 | No workspace has this storefront key. Generate a new one in Storefront. |
api_key_organization_mismatch | 403 | The key is valid but belongs to a different workspace than the one requested. |
organization_not_found | 404 | No workspace matches this organization slug. |
domain_not_authorized | 403 | The request origin is not an authorized storefront domain for this workspace. |
invalid_return_url | 422 | successUrl or cancelUrl is not a valid http or https URL. |
return_url_not_authorized | 403 | The return URL points outside the authorized storefront domains. |
unknown_variant | 422 | A cart line references a variant that is not on sale in this workspace. |
insufficient_stock | 409 | Stock ran out between rendering the page and creating the checkout. |
shipping_unavailable | 409 | No shipping rate covers this cart. The customer can change country or cart, so retrying the same cart will not help. |
mixed_currency_cart | 422 | One checkout cannot mix currencies. Split the cart. |
rate_limited | 429 | Too many checkout attempts from this IP. Retry-After says how long to wait. |
internal_error | 500 | Something failed on our side. Details are recorded by WireGum, not returned. |
checkout_unavailable | 503 | The workspace is not ready to sell: no Stripe account, no active plan, no shipping rate. A merchant has to change a setting. |
A 500 never carries the underlying exception. Database and Stripe failures are recorded on our side and answer with internal_error and one fixed sentence, so a public storefront cannot become a window into what runs behind it. checkout_unavailable is masked the same way: the workspace owner sees which setting is missing inside WireGum, while the shop answers its visitors with one sentence that reveals nothing about the account behind it.
Stability and versioning
The current storefront contract is v1, and it is in the URL: every public endpoint lives under /api/storefront/v1/. Each response also carries a WireGum-Storefront-Version header saying which contract answered. The public widget exposes window.WireGum.version as 1.0.0.
The versioned path is the only public address, and the workspace is named with the organization parameter on both endpoints. There is one spelling for each, so an integration written against this page cannot drift onto a second one.
window.WireGum methods, product payload fields used by the widget, checkout request fields, and the error code values with their statuses. Error sentences are not stable: read the code.Additive changes ship on the same contract. A breaking change ships as v2 on its own path, and v1 keeps answering: the widget follows whatever WireGum serves, but a server-side integration moves when it chooses to.Use WireGum public product and variant IDs, not editable slugs or internal database IDs, when CMS integrations must remain stable over time.Read a listing to the end with page.hasMore, never by assuming one response holds the whole catalog. The default page size can grow; a client that pages does not care.