
Sfcc Ocapi Scapi
- 65 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Integrate with Salesforce Commerce Cloud's headless OCAPI and Shopper (SCAPI) APIs to build custom storefronts and mobile commerce.
About
Connects to Salesforce Commerce Cloud's OCAPI and Shopper APIs to power headless and mobile commerce experiences. A developer uses it to build custom frontends against SFCC data.
- OCAPI and SCAPI Shopper API integration
- Headless and mobile commerce frontends
Sfcc Ocapi Scapi by the numbers
- 65 all-time installs (skills.sh)
- Ranked #3,110 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill sfcc-ocapi-scapiAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 65 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Integrate with Salesforce Commerce Cloud's headless OCAPI and Shopper (SCAPI) APIs to build custom storefronts and mobile commerce.
Files
SFCC OCAPI and Shopper APIs
Overview
Salesforce B2C Commerce provides two API families: the legacy Open Commerce API (OCAPI) using Basic Auth or OAuth and the modern Commerce API (SCAPI/Shopper APIs) using SLAS (Shopper Login and API Access Service) tokens. SCAPI is the recommended approach for headless storefronts, PWA Kit, and third-party integrations. OCAPI remains the primary Data API for server-side admin operations (product import, order management, promotion management). The Composable Storefront (formerly PWA Kit) is built entirely on SCAPI.
When to Use This Skill
- When building a headless B2C storefront using SFCC as the commerce backend
- When implementing the Salesforce PWA Kit (Composable Storefront) with custom API calls
- When integrating a mobile app with SFCC product catalog, cart, and checkout
- When building server-side order management integrations using OCAPI Data API
- When migrating an existing OCAPI integration to the newer SCAPI endpoints
- When implementing SLAS token management for customer authentication flows
Core Instructions
1. Understand the API landscape
| API | Auth | Use Case |
|---|---|---|
| SCAPI Shopper APIs | SLAS guest/customer token | Headless storefront, product search, cart, checkout |
| OCAPI Shop API | Basic/OAuth | Storefront operations from trusted server contexts |
| OCAPI Data API | Client credentials | Admin operations: product import, order management, promotions |
| SCAPI Admin APIs | Client credentials | Modern admin operations (gradually replacing OCAPI Data API) |
Base URL pattern: https://{shortCode}.api.commercecloud.salesforce.com/
2. Authenticate with SLAS (Shopper Login and API Access Service)
// lib/sfcc-auth.ts
const SLAS_BASE = `https://${process.env.SFCC_SHORT_CODE}.api.commercecloud.salesforce.com/shopper/auth/v1`;
const ORG_ID = process.env.SFCC_ORG_ID!; // f_ecom_xxx format
const CLIENT_ID = process.env.SFCC_SLAS_CLIENT_ID!;
// Step 1: Get PKCE code challenge for guest token
function generateCodeChallenge(): { verifier: string; challenge: string } {
const nodeCrypto = require("crypto");
const verifier = nodeCrypto.randomBytes(32).toString("hex");
const hash = nodeCrypto.createHash("sha256").update(verifier).digest("base64url");
return { verifier, challenge: hash };
}
// Get a guest access token
export async function getGuestToken(): Promise<{ access_token: string; refresh_token: string }> {
const { verifier, challenge } = generateCodeChallenge();
// Step 1: Authorize (get auth code)
const authorizeUrl = new URL(`${SLAS_BASE}/organizations/${ORG_ID}/oauth2/authorize`);
authorizeUrl.searchParams.set("client_id", CLIENT_ID);
authorizeUrl.searchParams.set("channel_id", process.env.SFCC_SITE_ID!);
authorizeUrl.searchParams.set("redirect_uri", process.env.SFCC_SLAS_REDIRECT_URI!);
authorizeUrl.searchParams.set("response_type", "code");
authorizeUrl.searchParams.set("code_challenge", challenge);
authorizeUrl.searchParams.set("hint", "guest");
// SFCC returns a redirect — extract the code from the Location header
const authResponse = await fetch(authorizeUrl.toString(), { redirect: "manual" });
const location = authResponse.headers.get("location") ?? "";
const code = new URL(location).searchParams.get("code") ?? "";
// Step 2: Exchange code for token
const tokenResponse = await fetch(
`${SLAS_BASE}/organizations/${ORG_ID}/oauth2/token`,
{
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "authorization_code_pkce",
code,
code_verifier: verifier,
client_id: CLIENT_ID,
redirect_uri: process.env.SFCC_SLAS_REDIRECT_URI!,
channel_id: process.env.SFCC_SITE_ID!,
}),
}
);
return tokenResponse.json();
}
// Refresh an access token
export async function refreshToken(refreshToken: string) {
const response = await fetch(`${SLAS_BASE}/organizations/${ORG_ID}/oauth2/token`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "refresh_token",
refresh_token: refreshToken,
client_id: CLIENT_ID,
}),
});
return response.json();
}3. Query the product catalog with Shopper Products API
// lib/sfcc-products.ts
const API_BASE = `https://${process.env.SFCC_SHORT_CODE}.api.commercecloud.salesforce.com`;
const ORG_ID = process.env.SFCC_ORG_ID!;
const SITE_ID = process.env.SFCC_SITE_ID!;
// Search products
export async function searchProducts(params: {
q?: string;
categoryId?: string;
start?: number;
count?: number;
sortKey?: string;
}, accessToken: string) {
const url = new URL(`${API_BASE}/search/shopper-search/v1/organizations/${ORG_ID}/product-search`);
url.searchParams.set("siteId", SITE_ID);
if (params.q) url.searchParams.set("q", params.q);
if (params.categoryId) url.searchParams.set("refine", `cgid=${params.categoryId}`);
url.searchParams.set("start", (params.start ?? 0).toString());
url.searchParams.set("count", (params.count ?? 20).toString());
if (params.sortKey) url.searchParams.set("sort", params.sortKey);
url.searchParams.set("expand", "images,prices,availability,variations");
const response = await fetch(url.toString(), {
headers: { Authorization: `Bearer ${accessToken}` },
});
return response.json();
}
// Get product by ID
export async function getProduct(productId: string, accessToken: string) {
const url = `${API_BASE}/product/shopper-products/v1/organizations/${ORG_ID}/products/${productId}?siteId=${SITE_ID}&expand=images,prices,availability,variations,promotions`;
const response = await fetch(url, {
headers: { Authorization: `Bearer ${accessToken}` },
});
return response.json();
}4. Manage cart and checkout with Shopper Baskets API
// lib/sfcc-basket.ts
// Create a basket
export async function createBasket(accessToken: string) {
const response = await fetch(
`${API_BASE}/checkout/shopper-baskets/v1/organizations/${ORG_ID}/baskets?siteId=${SITE_ID}`,
{
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ customerInfo: { email: null } }),
}
);
return response.json(); // { basketId: "xxx", ... }
}
// Add item to basket
export async function addItemToBasket(
basketId: string,
productId: string,
quantity: number,
accessToken: string
) {
const response = await fetch(
`${API_BASE}/checkout/shopper-baskets/v1/organizations/${ORG_ID}/baskets/${basketId}/items?siteId=${SITE_ID}`,
{
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify([{ productId, quantity }]),
}
);
return response.json();
}
// Set shipping address
export async function setShipmentAddress(
basketId: string,
shipmentId = "me",
address: Record<string, string>,
accessToken: string
) {
const response = await fetch(
`${API_BASE}/checkout/shopper-baskets/v1/organizations/${ORG_ID}/baskets/${basketId}/shipments/${shipmentId}/shipping-address?siteId=${SITE_ID}`,
{
method: "PUT",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(address),
}
);
return response.json();
}
// Submit order
export async function submitOrder(basketId: string, accessToken: string) {
const response = await fetch(
`${API_BASE}/checkout/shopper-orders/v1/organizations/${ORG_ID}/orders?siteId=${SITE_ID}`,
{
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ basketId }),
}
);
return response.json(); // { orderNo: "00001234", status: "created" }
}5. Use OCAPI Data API for server-side admin operations
OCAPI Data API uses a client credentials OAuth2 token:
// lib/sfcc-ocapi.ts
// Get admin token via client credentials
async function getAdminToken(): Promise<string> {
const response = await fetch("https://account.demandware.com/dwsso/oauth2/access_token", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Authorization: `Basic ${Buffer.from(
`${process.env.SFCC_OCAPI_CLIENT_ID}:${process.env.SFCC_OCAPI_CLIENT_SECRET}`
).toString("base64")}`,
},
body: new URLSearchParams({ grant_type: "client_credentials" }),
});
const { access_token } = await response.json();
return access_token;
}
// Get order via OCAPI Data API
export async function getOrder(orderNo: string): Promise<Record<string, unknown>> {
const token = await getAdminToken();
const instanceUrl = process.env.SFCC_INSTANCE_URL!; // https://xxxx.dx.commercecloud.salesforce.com
const response = await fetch(
`${instanceUrl}/s/${process.env.SFCC_SITE_ID}/dw/data/v23_2/orders/${orderNo}`,
{
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
}
);
return response.json();
}
// Update order status
export async function updateOrderStatus(orderNo: string, status: string): Promise<void> {
const token = await getAdminToken();
const instanceUrl = process.env.SFCC_INSTANCE_URL!;
await fetch(
`${instanceUrl}/s/${process.env.SFCC_SITE_ID}/dw/data/v23_2/orders/${orderNo}`,
{
method: "PATCH",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ status }),
}
);
}Examples
Customer login and cart merge (SCAPI)
export async function loginCustomer(
email: string,
password: string,
guestToken: string
): Promise<{ access_token: string; customer_id: string }> {
// Step 1: Get login token via Trusted Agent SLAS flow
const response = await fetch(
`${SLAS_BASE}/organizations/${ORG_ID}/oauth2/login`,
{
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Authorization: `Bearer ${guestToken}`,
},
body: new URLSearchParams({
type: "credentials",
username: email,
password,
client_id: CLIENT_ID,
}),
}
);
const { access_token, customer_id } = await response.json();
// Step 2: Merge guest basket into customer basket
if (guestBasketId) {
await fetch(
`${API_BASE}/checkout/shopper-baskets/v1/organizations/${ORG_ID}/baskets?siteId=${SITE_ID}`,
{
method: "POST",
headers: {
Authorization: `Bearer ${access_token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ guestBasketId }), // Triggers merge
}
);
}
return { access_token, customer_id };
}OCAPI product import via Data API
// Import/update products via OCAPI Data API
export async function upsertProduct(product: {
id: string;
name: string;
longDescription?: string;
price: number;
}) {
const token = await getAdminToken();
const instanceUrl = process.env.SFCC_INSTANCE_URL!;
const ocapiProduct = {
id: product.id,
name: { default: product.name },
long_description: { default: product.longDescription ?? "" },
price: product.price,
classification_category: { id: "electronics" },
};
const response = await fetch(
`${instanceUrl}/s/-/dw/data/v23_2/products/${product.id}`,
{
method: "PUT",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify(ocapiProduct),
}
);
if (!response.ok) {
const error = await response.json();
throw new Error(`OCAPI product upsert failed: ${error.message}`);
}
return response.json();
}Best Practices
- Use SCAPI/Shopper APIs for all new headless projects — OCAPI is in maintenance mode; SCAPI has better rate limits, JWT-based auth, and Salesforce's active investment
- Implement token refresh proactively — SLAS access tokens expire in 30 minutes; refresh in the background before expiry rather than waiting for 401 errors
- Store tokens in `httpOnly` cookies — client-side JavaScript should never access SLAS tokens directly; set cookies server-side and use a server-side proxy for API calls
- Use the Salesforce Commerce SDK (
@salesforce/commerce-sdk-reactfor React,commerce-sdk-isomorphicfor Node.js) to get type-safe API clients with automatic token management - Never expose OCAPI client credentials — Data API credentials have admin-level access; use environment variables and server-side-only code paths
- Use `select` parameter to limit fields — SFCC API responses are large;
?select=(id,name,price_min)dramatically reduces response sizes for list endpoints - Implement request idempotency for order submission — SFCC baskets can have race conditions; use the
Idempotency-Keyheader when submitting orders
Common Pitfalls
| Problem | Solution |
|---|---|
| SLAS authorization returns 400 | Verify redirect_uri is registered in SLAS configuration in Business Manager; the URI must be an exact match |
| Guest token doesn't see published products | Check OCAPI/Shop API permissions for the guest client ID in Business Manager → Site Preferences → OCAPI Settings |
| Basket merge silently fails | The guest basket must be created by the same SLAS client ID as the customer session; cross-client basket merges are not supported |
| OCAPI returns 401 despite valid token | OCAPI Data API requires the client ID to be registered with data resource permissions, not just shop resources |
| Product search returns 0 results | Check search index status in Business Manager → Site Preferences → Search; also verify siteId parameter matches the correct site |
| SCAPI rate limit exceeded (429) | Implement exponential backoff; consider server-side caching of catalog data (product lists rarely change in real time) |
Related Skills
- @sfcc-cartridge-development
- @sfcc-business-manager
- @headless-commerce-architecture
- @oauth-implementation
- @shopify-storefront-api
{
"context": "Tests whether the agent correctly uses OCAPI Data API for server-side order management, obtains admin tokens via client credentials from the correct Demandware endpoint, constructs proper OCAPI Data API URLs, secures credentials using environment variables, and implements idempotency for order submission.",
"type": "weighted_checklist",
"checklist": [
{
"name": "OCAPI for admin ops",
"max_score": 8,
"description": "Order management and admin operations use OCAPI Data API endpoints rather than SCAPI Shopper APIs"
},
{
"name": "Demandware token endpoint",
"max_score": 10,
"description": "Admin token is fetched from `https://account.demandware.com/dwsso/oauth2/access_token` — NOT from the shortCode.api.commercecloud.salesforce.com SLAS endpoint"
},
{
"name": "Client credentials grant",
"max_score": 10,
"description": "Admin token request uses grant_type=client_credentials with Basic auth header (base64 encoded client_id:client_secret)"
},
{
"name": "OCAPI URL structure",
"max_score": 10,
"description": "OCAPI Data API URLs follow the pattern `{SFCC_INSTANCE_URL}/s/{siteId}/dw/data/v23_2/...` where SFCC_INSTANCE_URL points to a *.dx.commercecloud.salesforce.com host"
},
{
"name": "Environment variables for credentials",
"max_score": 10,
"description": "OCAPI client ID and client secret are read from environment variables (e.g. SFCC_OCAPI_CLIENT_ID, SFCC_OCAPI_CLIENT_SECRET) — NOT hardcoded in source code"
},
{
"name": "Server-side only code paths",
"max_score": 8,
"description": "OCAPI credential usage and admin token retrieval are in server-side only modules/files (e.g. not in browser-accessible code, marked as server-only, or in an API route)"
},
{
"name": "Idempotency-Key header",
"max_score": 10,
"description": "Order submission requests include an `Idempotency-Key` header to prevent duplicate order creation from race conditions or retries"
},
{
"name": "Order status update via PATCH",
"max_score": 8,
"description": "Order status changes use HTTP PATCH to the OCAPI Data API orders endpoint with the status field in the request body"
},
{
"name": "OCAPI version in URL",
"max_score": 8,
"description": "OCAPI Data API URLs include a version segment matching v23_2 (e.g. /dw/data/v23_2/)"
},
{
"name": "No SCAPI for order management",
"max_score": 8,
"description": "Order retrieval and status management do NOT use SCAPI/Shopper Orders endpoints (e.g. shopper-orders) — these are used only for customer-facing checkout, not admin ops"
},
{
"name": "Bearer token in Authorization header",
"max_score": 10,
"description": "OCAPI Data API requests include the admin access token as `Authorization: Bearer {token}` header"
}
]
}
Order Fulfillment Integration
Problem/Feature Description
A home goods retailer uses Salesforce B2C Commerce Cloud as their commerce platform and has recently contracted a third-party fulfilment warehouse to handle physical order processing. The warehouse system needs to pull new orders from SFCC every few minutes and update order statuses (e.g. "shipped", "cancelled") as fulfilment progresses. This is a server-to-server integration with no customer-facing UI — it runs as a Node.js background service on the retailer's internal infrastructure.
The engineering team has had two problems in production: duplicate orders were created during a brief network outage when the submission script retried without safeguards, and the integration was briefly broken after a security audit forced credential rotation because the client secret had been committed to source control. They want the new implementation to be production-safe from day one.
Write a TypeScript integration module that the fulfillment service can use to interact with SFCC orders. Also include a FULFILLMENT_SETUP.md that describes what permissions the SFCC OCAPI client ID needs and what environment variables must be configured.
Output Specification
Produce the following files:
lib/fulfillment.ts— Integration module with at minimum:getPendingOrders()— retrieve orders in "new" or "open" status ready for fulfilmentupdateOrderStatus(orderNo, status)— update the fulfilment status of an ordersubmitOrder(basketId)— submit a basket as an order, protected against duplicate submissionlib/sfcc-admin-auth.ts— Admin authentication helper for obtaining and caching the admin access tokenFULFILLMENT_SETUP.md— Setup guide covering required OCAPI client permissions, environment variable names, and any configuration notes
Use TypeScript. Assume the following environment variables will be configured in the deployment environment (do not hardcode their values):
SFCC_OCAPI_CLIENT_IDSFCC_OCAPI_CLIENT_SECRETSFCC_INSTANCE_URL— the SFCC instance hostname (e.g.https://xxxx.dx.commercecloud.salesforce.com)SFCC_SITE_ID
{
"context": "Tests whether the agent uses SCAPI Shopper APIs (not OCAPI) for a headless storefront, leverages the Salesforce Commerce SDK for type-safe clients, applies the select parameter to limit response payload sizes, and implements proper resilience patterns for high-traffic catalog serving.",
"type": "weighted_checklist",
"checklist": [
{
"name": "SCAPI over OCAPI Shop",
"max_score": 10,
"description": "Product catalog and search functionality uses SCAPI Shopper APIs (shopper-search, shopper-products endpoints) rather than OCAPI Shop API endpoints"
},
{
"name": "Commerce SDK package",
"max_score": 10,
"description": "Code imports or references `commerce-sdk-isomorphic` (for Node.js) or `@salesforce/commerce-sdk-react` (for React) rather than making raw fetch calls to SCAPI"
},
{
"name": "SCAPI base URL pattern",
"max_score": 8,
"description": "Any manually constructed SCAPI URLs follow the pattern `https://{shortCode}.api.commercecloud.salesforce.com/` using an environment variable for the short code"
},
{
"name": "select parameter usage",
"max_score": 10,
"description": "At least one API call to a list or search endpoint includes a `select` query parameter to limit returned fields (e.g. `?select=(id,name,price_min)` or equivalent)"
},
{
"name": "Product search expand fields",
"max_score": 7,
"description": "Product search calls include `expand=images,prices,availability,variations` (or a subset) in the query parameters"
},
{
"name": "siteId parameter present",
"max_score": 8,
"description": "All SCAPI endpoint calls include a `siteId` query parameter set from an environment variable"
},
{
"name": "Exponential backoff on 429",
"max_score": 10,
"description": "HTTP 429 (rate limit) responses trigger a retry with exponential backoff rather than immediate retry or hard failure"
},
{
"name": "Server-side catalog caching",
"max_score": 10,
"description": "Catalog or product list responses are cached server-side (e.g. in-memory cache, Redis, or HTTP caching headers) to reduce repeated API calls"
},
{
"name": "SLAS guest token for shopper calls",
"max_score": 9,
"description": "Shopper API calls are authenticated with a SLAS Bearer token (Authorization: Bearer ...) rather than Basic auth or no auth"
},
{
"name": "Avoid OCAPI for catalog",
"max_score": 8,
"description": "No OCAPI Shop API URLs (e.g. /s/{siteId}/dw/shop/...) are used for product catalog or search operations"
},
{
"name": "Environment variable config",
"max_score": 10,
"description": "SFCC_SHORT_CODE, SFCC_ORG_ID, and SFCC_SITE_ID are all sourced from environment variables rather than hardcoded"
}
]
}
Mobile Commerce Catalog API Layer
Problem/Feature Description
A sporting goods retailer is building a React Native mobile app backed by Salesforce B2C Commerce Cloud. The app's catalog browsing feature needs to support hundreds of concurrent users during peak sale events without hitting API rate limits or degrading response times. The previous integration built by an external agency made direct calls to legacy SFCC APIs and suffered from slow response times due to large payloads and repeated identical requests from different users. The team has been told to modernize the integration.
The new implementation should serve product search results and individual product detail pages to the mobile app via a Node.js BFF (Backend for Frontend) service. The team wants the implementation to be type-safe, handle traffic spikes gracefully, and keep API response payloads small to improve mobile performance.
Write a Node.js/TypeScript BFF service module with the catalog functions needed for the mobile app. Also produce a short CATALOG_DESIGN.md that describes the resilience and performance strategies you chose and why.
Output Specification
Produce the following files:
lib/catalog.ts— BFF catalog module with at minimum:searchProducts(query, options)— search products by keyword and/or categorygetProductDetail(productId)— fetch full product details
Both functions should handle authentication internally (the caller does not need to manage tokens).
lib/sfcc-auth.ts— SLAS authentication helper (guest token acquisition and refresh)CATALOG_DESIGN.md— A short document describing the resilience patterns and payload optimisation strategy used
Use TypeScript. Assume the following environment variables are available at runtime (do not hardcode their values):
SFCC_SHORT_CODESFCC_ORG_IDSFCC_SITE_IDSFCC_SLAS_CLIENT_IDSFCC_SLAS_REDIRECT_URI
{
"context": "Tests whether the agent correctly implements SLAS-based guest authentication using PKCE, handles token lifecycle management proactively, secures tokens via httpOnly cookies, and correctly handles guest-to-customer session continuity with basket merging.",
"type": "weighted_checklist",
"checklist": [
{
"name": "PKCE code verifier generation",
"max_score": 10,
"description": "Guest token flow generates a code_verifier (random bytes) and derives a code_challenge (SHA-256 hash, base64url encoded) — does NOT skip the PKCE step"
},
{
"name": "Two-step authorize flow",
"max_score": 10,
"description": "Authorization is done in two steps: (1) GET/POST to the oauth2/authorize endpoint with redirect:manual to capture the auth code from the Location header, (2) POST to oauth2/token with grant_type=authorization_code_pkce"
},
{
"name": "SLAS base URL pattern",
"max_score": 8,
"description": "SLAS URL is constructed as `https://{SFCC_SHORT_CODE}.api.commercecloud.salesforce.com/shopper/auth/v1` using an environment variable for the short code"
},
{
"name": "ORG_ID environment variable",
"max_score": 7,
"description": "ORG_ID is read from an environment variable (e.g. SFCC_ORG_ID) and is described or referenced in f_ecom_xxx format"
},
{
"name": "Proactive token refresh",
"max_score": 10,
"description": "Token refresh is triggered proactively before expiry (e.g. on a timer, checking expiry time) rather than only in response to a 401 error"
},
{
"name": "Refresh token grant type",
"max_score": 8,
"description": "Token refresh uses grant_type=refresh_token with the refresh_token value and client_id against the oauth2/token endpoint"
},
{
"name": "httpOnly cookie storage",
"max_score": 10,
"description": "Access tokens are stored in httpOnly cookies set server-side — client-side JavaScript does NOT have direct access to the token value"
},
{
"name": "Server-side proxy for API calls",
"max_score": 8,
"description": "SFCC API calls from the frontend are routed through a server-side proxy or BFF layer rather than made directly from client-side code"
},
{
"name": "Guest basket merge mechanism",
"max_score": 10,
"description": "Login flow passes guestBasketId in the POST /baskets request body (with the customer access token) to trigger basket merge — does NOT attempt to merge baskets from different SLAS client IDs"
},
{
"name": "Customer login endpoint",
"max_score": 9,
"description": "Customer login uses the oauth2/login endpoint with the guest access token in the Authorization header and type=credentials in the body"
},
{
"name": "SLAS channel_id parameter",
"max_score": 10,
"description": "The authorize request includes channel_id set to the site ID (SFCC_SITE_ID environment variable)"
}
]
}
Headless Storefront Authentication Service
Problem/Feature Description
A fashion retailer is launching a new headless storefront built on Salesforce B2C Commerce Cloud. The team has an existing Salesforce Commerce Cloud org and needs a backend authentication service that handles the full customer journey: anonymous browsing, customer login, and persistent shopping carts.
The current prototype allows customers to browse products and add items to a cart, but loses the cart contents when a guest user logs in. The product team wants a seamless "login and keep my cart" experience. Additionally, the security team has flagged that the current prototype stores session tokens in localStorage, which is a vulnerability — they want tokens stored securely so they are not accessible to third-party JavaScript on the page. The engineering team also noticed that customers are intermittently logged out mid-session and suspects the authentication tokens are expiring without being refreshed.
Your task is to implement a TypeScript authentication module (lib/sfcc-auth.ts) and a Next.js API route (pages/api/auth/) that implements the complete authentication layer described above. Include a brief AUTH_DESIGN.md documenting the security choices made.
Output Specification
Produce the following files:
lib/sfcc-auth.ts— Core authentication functions covering:- Guest session initiation
- Token refresh
- Customer login with cart continuity
pages/api/auth/session.ts— Next.js API route that handles session management and authenticated commerce requests on behalf of the clientAUTH_DESIGN.md— A short document (bullet points are fine) explaining the token storage strategy and token lifecycle management approach
Use TypeScript. Assume the following environment variables are available at runtime (do not hardcode their values):
SFCC_SHORT_CODE— the Commerce Cloud short codeSFCC_ORG_ID— the org IDSFCC_SITE_ID— the site/channel IDSFCC_SLAS_CLIENT_ID— the SLAS public client IDSFCC_SLAS_REDIRECT_URI— the registered redirect URI
{
"name": "finsi/sfcc-ocapi-scapi",
"version": "0.1.0",
"summary": "OCAPI and Shopper APIs for headless Salesforce Commerce",
"skills": {
"sfcc-ocapi-scapi": {
"path": "SKILL.md"
}
}
}