Storefront BFF API
The Storefront BFF API is a REST API that provides a stable, domain-oriented interface for building customer experiences on the Horizon Storefront.
The API uses predictable resource-oriented URLs, accepts JSON request bodies, returns JSON responses, and uses standard HTTP response codes and authentication.
Resource groups
Endpoints are organised by domain:
- Authentication -
/auth/login,/auth/logout,/auth/token - Products - product detail, variants, media
- Categories - navigation, merchandising, collections
- Cart - cart lifecycle, line items, promotions
- Orders - order detail and history
- Customers - profiles, addresses, preferences
- Content - CMS-driven marketing content
- Search - search suggestions and queries
- Health - readiness and liveness endpoints
Just getting started?
Check out our development quickstart guide.
Base URL
All API requests should be made to this base URL. See Environments for staging and development URLs.
Authentication
The Storefront API uses different authentication methods depending on the caller type.
Auth schemes
| Scheme | Header | Format | Used for |
|---|---|---|---|
BFFToken | X-BFF-Token | JWT | Customer sessions |
BearerAuth | Authorization | JWT | Service-to-service |
Public endpoints
Public endpoints require no authentication. These include:
- Product and category browse
- Search suggestions
- CMS content and static pages
These endpoints do not expose PII and can be cached at the edge.
Customer authentication
Customer flows (cart, checkout, orders, profile) require a BFF session token obtained via login.
Login flow
- Call
POST /auth/loginwith credentials and reCAPTCHA response - Receive
accessToken,tokenType,expiresIn, andcustomerobject - Include the token in
X-BFF-Tokenheader on subsequent requests
curl -X POST https://storefront.api.templeandwebster.com/auth/login \
-H "Content-Type: application/json" \
-d '{
"emailAddress": "customer@example.com",
"password": "SecurePassword123!",
"gRecaptchaResponse": "03AGdBq27..."
}'
Response
The login response contains:
- accessTokenstringrequired
JWT token to include in subsequent requests.
- tokenTypestringrequired
Token type, always
"Bearer".- expiresInintegerrequired
Token validity in seconds.
- customerobjectrequired
Customer profile object with
id,emailAddress,firstName,lastName.
{
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"tokenType": "Bearer",
"expiresIn": 3600,
"customer": {
"id": "cust-123",
"emailAddress": "customer@example.com",
"firstName": "Jane",
"lastName": "Smith"
}
}
Authenticated request
Include the token in the X-BFF-Token header for all authenticated requests.
curl https://storefront.api.templeandwebster.com/customers/me \
-H "X-BFF-Token: eyJhbGciOiJIUzI1NiIs..."
Token lifetime
expiresInindicates token validity in seconds401 Unauthorizedindicates an expired or invalid token403 Forbiddenindicates insufficient permissions
Tokens are short-lived. Handle expiry by re-authenticating or using the refresh flow at POST /auth/token.
Server-to-server
Internal services use Google Cloud identity tokens.
These endpoints must not be exposed to the public internet.
curl https://storefront.api.templeandwebster.com/internal/endpoint \
-H "Authorization: Bearer ya29.c.Kp8B..."
Headers summary
| Header | Required | Description |
|---|---|---|
X-BFF-Token | Customer flows | Customer session JWT |
Authorization | S2S calls | GCP identity token |
Correlation-Id | Recommended | Request tracing ID (UUID) |
Errors
The Storefront BFF uses conventional HTTP response codes to indicate the success or failure of an API request. In general: Codes in the 2xx range indicate success. Codes in the 4xx range indicate an error that failed given the information provided (e.g., a required parameter was omitted, a validation failed, etc.). Codes in the 5xx range indicate an error with the server (these should be rare).
Some 4xx errors that could be handled programmatically include an error code that briefly explains the error reported.
Attributes
- codeintegerrequired
HTTP status code (e.g.,
400,401,404,422,429,500).- messagestringrequired
A human-readable message providing more details about the error. These messages can be shown to your users or logged for debugging.
- codestring
For some errors that could be handled programmatically, a short string indicating the error code reported.
- errorsarray
For validation errors, a list of detailed field-level errors. Each error contains
field,code, andmessageproperties.
HTTP Status Code Summary
| Status | Name | Description |
|---|---|---|
| 200 | OK | Everything worked as expected. |
| 201 | Created | Resource was successfully created. |
| 204 | No Content | Request succeeded with no response body. |
| 400 | Bad Request | The request was malformed or semantically invalid. |
| 401 | Unauthorized | No valid authentication credentials provided. |
| 403 | Forbidden | The authenticated user doesn't have permissions. |
| 404 | Not Found | The requested resource doesn't exist. |
| 409 | Conflict | The request conflicts with the current state. |
| 422 | Unprocessable Entity | Validation failed for one or more fields. |
| 429 | Too Many Requests | Too many requests hit the API too quickly. |
| 500 | Internal Server Error | Something went wrong on the server's end. |
| 502 | Bad Gateway | A downstream service returned an invalid response. |
| 503 | Service Unavailable | A downstream service is temporarily unavailable. |
| 504 | Gateway Timeout | A downstream service did not respond in time. |
Detailed Error
Within the errors array, each detailed error contains:
- fieldstring
If the error is field-specific, the field path related to the error. For example, you can use this to display a message near the correct form field (e.g.,
email,shippingAddress.postcode).- codestring
Machine-readable error code for this specific field error (e.g.,
REQUIRED,INVALID_FORMAT,OUT_OF_RANGE).- messagestring
Human-readable explanation for this field error.
Error Types
The type field in the error response indicates the category of error.
| Type | Description |
|---|---|
| api_error | API errors cover any other type of problem, and are extremely uncommon. |
| auth_error | Authentication errors occur when credentials are missing, invalid, or expired. |
| validation_error | Validation errors arise when your request has invalid parameters. |
| not_found_error | Not found errors occur when the requested resource doesn't exist. |
| conflict_error | Conflict errors occur when the request conflicts with current state. |
| rate_limit_error | Rate limit errors occur when too many requests hit the API too quickly. |
Requests
The Storefront BFF is a JSON-over-HTTP API. This page covers the common patterns used across endpoints.
HTTP methods
| Method | Use | Idempotent |
|---|---|---|
GET | Read a resource or collection | Yes |
POST | Create or perform an action | No |
PUT | Replace or set resource state | Yes |
DELETE | Remove a resource | Yes |
Required headers
Any request with a body must set Content-Type: application/json.
Content-Type: application/json
Accept: application/json
Resource IDs
Resources have stable, unique identifiers. Treat IDs as opaque strings.
{
"id": "cust-660e9511-f3ac-42e5-b827-6f7g9b8e0c4d",
"version": 3,
"createdAt": "2024-02-10T14:20:00Z",
"lastModifiedAt": "2024-11-24T09:30:00Z"
}
Money
Monetary values use minor units (cents).
Do not send floating-point dollar values. Always use the structured Money object.
- typestringrequired
Precision type, always
"centPrecision".- currencyCodestringrequired
ISO 4217 currency code (e.g.,
AUD,NZD).- centAmountintegerrequired
Amount in minor units (cents).
- fractionDigitsintegerrequired
Number of decimal places for the currency.
{
"type": "centPrecision",
"currencyCode": "AUD",
"centAmount": 1999,
"fractionDigits": 2
}
Localised strings
Text that varies by locale uses LocalizedString.
Keys are IETF language tags (e.g., en-AU).
{
"en-AU": "Premium 3 Seater Sofa Bed",
"en-NZ": "Premium 3 Seater Sofa Bed"
}
Timestamps
All timestamps use RFC 3339 / ISO 8601 format in UTC.
Always include the trailing Z for UTC. Do not send local time zone offsets.
{
"createdAt": "2024-02-10T14:20:00Z",
"lastModifiedAt": "2024-11-24T09:30:00Z"
}
Paged responses
Collection endpoints return a paged envelope.
See Pagination for details.
- limitintegerrequired
Maximum number of results requested.
- offsetintegerrequired
Number of results to skip.
- countintegerrequired
Number of results in this response.
- totalintegerrequired
Total number of results available.
- resultsarrayrequired
Array of result objects.
{
"limit": 20,
"offset": 0,
"count": 20,
"total": 350,
"results": [...]
}
Error responses
All errors use a standard structure.
See Errors for the full list of status codes and error types.
{
"status": 422,
"message": "Validation error",
"code": "VALIDATION_ERROR",
"errors": [
{
"field": "email",
"code": "REQUIRED",
"message": "Field is required"
}
]
}
Environments
The Storefront API runs in multiple environments with the same contract but different base URLs.
Base URLs
| Environment | Base URL |
|---|---|
| Production | https://storefront.api.templeandwebster.com |
| Staging | https://storefront.staging.api.templeandwebster.com |
| Development | https://storefront.dev.api.templeandwebster.com |
| Local | http://localhost:8080 |
Production
Live customer traffic with real data. Subject to SLOs, monitoring, and change controls.
Use for:
- Validating behaviour with feature flags after deployment
- Investigating production issues with appropriate access
Never run synthetic load tests or use fake customer data against production.
Staging
Mirrors production topology. Used for pre-release verification and regression testing.
Use for:
- End-to-end test scenarios
- QA and UAT flows
- Verifying migrations and contract changes
Data may be synthetic or sanitised production records.
Development
Shared integration environment for feature development.
Use for:
- Testing API changes from feature branches
- Manual testing from local frontend instances
- Early integration testing with other teams
Data and configuration may change frequently.
Local
Runs the BFF on http://localhost:8080.
Typical setup:
- Storefront Next on
http://localhost:3000 - Frontend proxies API calls to
localhost:8080
See the storefront-bff README for setup details.
Choosing an environment
| Task | Environment |
|---|---|
| Building a feature | Local, then Development |
| Cross-team integration | Development, then Staging |
| Preparing a release | Staging |
| Investigating issues | Staging or Development first |
Pagination
All top-level API resources have support for bulk fetches through "list" API methods. These list API methods share a common structure and accept, at a minimum, the following parameters: limit and offset.
The Storefront BFF uses offset-based pagination through the limit and offset parameters.
Parameters
- limitinteger
This specifies a limit on the number of objects to return, ranging between 1 and 100. Default is 20.
- offsetinteger
The number of results to skip before returning results. Use this to page through results. Default is 0.
GET /products?limit=20&offset=40
List Response Format
- limitintegerrequired
The limit that was applied to this request.
- offsetintegerrequired
The offset that was applied to this request.
- countintegerrequired
The number of results in this response (may be less than limit on the last page).
- totalintegerrequired
The total number of results available across all pages.
- resultsarrayrequired
An array containing the actual response elements.
{
"limit": 20,
"offset": 40,
"count": 20,
"total": 350,
"results": [
{
"id": "prod-12345",
"name": "Premium Sofa",
"price": {
"centAmount": 199900,
"currencyCode": "AUD"
}
}
]
}
Pagination example
To paginate through results:
- Make an initial request with your desired
limit - Check if
offset + count < totalto determine if more pages exist - Increment
offsetbylimitfor the next page - Repeat until you've retrieved all results
# First page
curl "https://storefront.api.templeandwebster.com/products?limit=20&offset=0"
# Second page
curl "https://storefront.api.templeandwebster.com/products?limit=20&offset=20"
# Third page
curl "https://storefront.api.templeandwebster.com/products?limit=20&offset=40"
Best practices
- Use reasonable page sizes (20-50 items) for optimal performance
- Cache results where appropriate to reduce API calls
- Handle the case where
totalmay change between requests (items added/removed) - Consider using filtering to reduce result sets before pagination
Filtering
The Storefront BFF provides several filtering mechanisms for collection endpoints.
Query parameters
Many endpoints expose explicit parameters for common filters:
- Boolean flags:
inStock,featured,isDefault - Value ranges:
minPrice,maxPrice(in cents) - Enumerations:
status=active|inactive|discontinued - Identifiers:
category,brand,customerId
curl "https://storefront.api.templeandwebster.com/products?category=sofas&status=active&minPrice=50000&maxPrice=200000"
Full-text search
Endpoints with search use the query parameter.
The query is passed to the search system and may support stemming, synonyms, and fuzzy matching.
curl "https://storefront.api.templeandwebster.com/search?query=sofa%20bed"
Structured filters
For complex conditions, use the filter parameter with operators.
Operators
| Operator | Description |
|---|---|
[eq] | Equal (default) |
[ne] | Not equal |
[gt] | Greater than |
[gte] | Greater than or equal |
[lt] | Less than |
[lte] | Less than or equal |
curl "https://storefront.api.templeandwebster.com/products?filter[status]=active&filter[price[gte]]=5000&filter[price[lte]]=20000"
Filter examples
Combine multiple filter parameters in a single request.
# Active products between $50 and $200
GET /products?filter[status]=active&filter[price[gte]]=5000&filter[price[lte]]=20000
# Orders created after a date
GET /orders?filter[createdAt[gte]]=2025-01-01T00:00:00Z
Locale filtering
Content endpoints support a locale parameter.
Pattern: ^[a-z]{2}-[A-Z]{2}$ (e.g., en-AU). Default: en-AU.
curl "https://storefront.api.templeandwebster.com/content/footer?locale=en-AU"
Error handling
Invalid filters return 400 Bad Request with details about the invalid parameter.
{
"status": 400,
"message": "Invalid filter parameter",
"code": "INVALID_FILTER",
"errors": [
{
"field": "filter[price[gte]]",
"code": "INVALID_VALUE",
"message": "Expected a numeric value"
}
]
}
Headers
Standard HTTP headers used by the Storefront BFF.
Request headers
| Header | Required | Description |
|---|---|---|
Content-Type | Yes (with body) | Must be application/json |
Accept | Recommended | Should be application/json |
X-BFF-Token | Customer endpoints | Customer session JWT |
Authorization | S2S endpoints | Bearer <gcp-identity-token> |
X-Api-Version | Yes | API version (e.g., v1.0) |
Correlation-Id | Recommended | UUID for request tracing |
Content-Type
Required for any request with a body.
If missing or incorrect, returns 415 Unsupported Media Type.
X-BFF-Token
Customer session token from the login flow.
Required for customer endpoints (cart, orders, profile). See Authentication.
Authorization
Google Cloud identity token for service-to-service calls.
Used by internal services and batch jobs.
X-Api-Version
Selects the resource schema version.
Pattern: v{major}.{minor}. Required on requests, echoed in responses.
Correlation-Id
UUID for correlating logs and traces across services.
Optional. If not provided, the BFF generates one. Reuse the same ID for related requests in a single user flow (e.g., checkout).
Response headers
| Header | Description |
|---|---|
Content-Type | application/json |
X-Api-Version | Version used for this response |
Correlation-Id | Request correlation ID |
Sorting
Collection endpoints support sorting via the sort query parameter.
Syntax
Direction must be asc (ascending) or desc (descending).
Examples
Sort by a single field with ascending or descending direction.
Multi-field sorting
Repeat the sort parameter for multiple fields.
Sort is applied in parameter order:
- Featured products first
- Then by popularity
- Then alphabetically by name
Common sort fields
| Endpoint | Typical fields |
|---|---|
GET /products | name, price, createdAt, popularity, featured |
GET /orders | createdAt, status, totalAmount |
GET /categories | name, position |
Error handling
Unsupported sort fields return 400 Bad Request.
With pagination
Sorting is applied before pagination. Always use the same sort parameters when paginating to maintain stable ordering.
Idempotency
The API supports idempotency for safely retrying requests without performing the same operation twice.
HTTP method semantics
| Method | Idempotent | Notes |
|---|---|---|
GET | Yes | No side effects |
HEAD | Yes | No side effects |
PUT | Yes | Sets resource state |
DELETE | Yes | Removes resource |
POST | No | May create duplicates without care |
Safe to retry
These operations can be retried on network failures or 5xx errors:
GET,HEAD,OPTIONS- always safePUT,DELETE- idempotent by designPOST- only where explicitly documented
Good: state-setting
Prefer state-setting operations over action-style operations.
The client sends the desired state. Retries produce the same outcome.
PUT /carts/{cartId}
Content-Type: application/json
{
"discountCode": "WELCOME10"
}
Risky: action-style
Action endpoints are harder to make safe. Prefer state-setting alternatives.
# Action-style (harder to make safe)
POST /orders/{orderId}/cancel
# Better: state-setting alternative
PUT /orders/{orderId}
Content-Type: application/json
{
"status": "CANCELLED"
}
Idempotent actions
When actions are necessary, implement them to be retry-safe:
Server behaviour:
- If confirmable, confirm and return the result
- If already confirmed, return the same result (not an error)
- If in an incompatible state, return
4xx
POST /checkout/{sessionId}/confirm
Idempotency keys
The API does not currently require explicit idempotency keys. If introduced in future, they will:
- Use an
Idempotency-Keyheader - Be scoped to method, path, and principal
- Store and replay original responses for matching requests
Retry guidance
| Scenario | Safe to retry? |
|---|---|
| Network timeout | Yes for idempotent methods |
5xx response | Yes with exponential backoff |
4xx response | No - client error, fix the request |
Non-idempotent POST | Only if documented as safe |
Rate Limits
The API applies rate limiting to protect downstream systems and ensure consistent performance.
429 Too Many Requests
When you exceed a rate limit, the API returns a 429 status code.
Retry-After header
The response may include a Retry-After header indicating how many seconds to wait before retrying.
Limit scopes
Rate limits may be applied by:
- IP address - protects against abusive traffic
- Customer session - prevents UI loops
- Service account - for server-to-server integrations
- Endpoint - expensive operations (e.g., search) may have stricter limits
Exact thresholds are environment-specific and may change without notice.
Browser clients
- Debounce search and autocomplete requests
- Cache static data lookups
- On
429, wait perRetry-Afterand show a friendly message - Don't retry aggressively
Server clients
- Implement exponential backoff with jitter: 1s, 2s, 4s, 8s (capped)
- Reuse HTTP clients with connection pooling
- Stagger retries after outages to avoid thundering herd
Example backoff
Implement exponential backoff with jitter for production clients.
Non-production environments
Rate limits in development and staging may be less strict but can still apply to expensive endpoints like search.
Design your clients to handle 429 gracefully in all environments.
Versioning
The API uses versioning to evolve without breaking existing integrations.
Base URL
The base URL is versionless. Versioning is applied at the resource level or via headers.
Resource versioning
Individual resources can include a version suffix.
Version format: v{major} or v{major}.{minor}.
Omitting a version returns the latest supported version for that resource.
Header versioning
The X-Api-Version header controls cross-cutting behaviour.
Resource URL versions take precedence over the header for resource-specific behaviour.
Breaking changes
These require a new major version:
- Removing fields
- Changing field types or formats
- Changing field semantics
- Changing status code behaviour
These are backwards-compatible and don't require a new version:
- Adding new optional fields
- Adding new endpoints
- Adding new parameters with defaults
Version lifecycle
- Introduce v1 - Resource becomes generally available
- Add v1.x - Optional improvements requiring explicit opt-in
- Introduce v2 - Breaking changes; v1 enters deprecation window
- Deprecate v1 - Removed after the agreed period
We limit live major versions to two at a time.
Client guidance
- Default to unversioned URLs unless you need a specific version
- For stable integrations, use explicit versions (e.g.,
/orders/v1) - Track deprecation notices and plan upgrades
- Test across environments before production deployment