Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
finsilabs avatar

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-scapi

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs65
repo stars41
Last updatedMarch 13, 2026
Repositoryfinsilabs/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

SKILL.mdMarkdownGitHub ↗

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

APIAuthUse Case
SCAPI Shopper APIsSLAS guest/customer tokenHeadless storefront, product search, cart, checkout
OCAPI Shop APIBasic/OAuthStorefront operations from trusted server contexts
OCAPI Data APIClient credentialsAdmin operations: product import, order management, promotions
SCAPI Admin APIsClient credentialsModern 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-react for React, commerce-sdk-isomorphic for 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-Key header when submitting orders

Common Pitfalls

ProblemSolution
SLAS authorization returns 400Verify redirect_uri is registered in SLAS configuration in Business Manager; the URI must be an exact match
Guest token doesn't see published productsCheck OCAPI/Shop API permissions for the guest client ID in Business Manager → Site Preferences → OCAPI Settings
Basket merge silently failsThe 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 tokenOCAPI Data API requires the client ID to be registered with data resource permissions, not just shop resources
Product search returns 0 resultsCheck 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

Related skills

Backend & APIsbackendintegrations

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.