Guide for developing SCAPI Custom APIs on Salesforce B2C Commerce
/plugin marketplace add SalesforceCommerceCloud/b2c-developer-tooling/plugin install b2c@b2c-developer-toolingThis skill inherits all available tools. When active, it can use any tool Claude has access to.
This skill guides you through developing Custom APIs for Salesforce B2C Commerce. Custom APIs let you expose custom script code as REST endpoints under the SCAPI framework.
A Custom API URL has this structure:
https://{shortCode}.api.commercecloud.salesforce.com/custom/{apiName}/{apiVersion}/organizations/{organizationId}/{endpointPath}
Three components are required to create a Custom API:
api.json file binding endpoints to implementationsCustom APIs are defined within cartridges. Create a rest-apis folder in the cartridge directory with subdirectories for each API:
/my-cartridge
/cartridge
package.json
/rest-apis
/my-api-name # API name (lowercase alphanumeric and hyphens only)
api.json # Mapping file
schema.yaml # OAS 3.0 contract
script.js # Implementation
/scripts
/controllers
Important: API directory names can only contain alphanumeric lowercase characters and hyphens.
The API contract defines endpoints using OAS 3.0 format:
openapi: 3.0.0
info:
version: 1.0.0 # API version (1.0.0 becomes v1 in URL)
title: My Custom API
components:
securitySchemes:
ShopperToken: # For Shopper APIs (requires siteId)
type: oauth2
flows:
clientCredentials:
tokenUrl: https://{shortCode}.api.commercecloud.salesforce.com/shopper/auth/v1/organizations/{organizationId}/oauth2/token
scopes:
c_my_scope: Description of my scope
AmOAuth2: # For Admin APIs (no siteId)
type: oauth2
flows:
clientCredentials:
tokenUrl: https://account.demandware.com/dwsso/oauth2/access_token
scopes:
c_my_admin_scope: Description of my admin scope
parameters:
siteId:
name: siteId
in: query
required: true
schema:
type: string
minLength: 1
locale:
name: locale
in: query
required: false
schema:
type: string
minLength: 1
paths:
/my-endpoint:
get:
summary: Get something
operationId: getMyData # Must match function name in script
parameters:
- $ref: '#/components/parameters/siteId'
- in: query
name: c_my_param # Custom params must start with c_
required: true
schema:
type: string
responses:
'200':
description: Success
content:
application/json:
schema:
type: object
security:
- ShopperToken: ['c_my_scope'] # Global security (or per-operation)
info.version, transformed to URL version (e.g., 1.0.1 becomes v1)ShopperToken for Shopper APIs or AmOAuth2 for Admin APIsc_, contain only alphanumeric/hyphen/period/underscore, max 25 charsc_ prefixsiteId and locale must have type: string and minLength: 1additionalProperties attribute is not allowed in request body schemas| Aspect | Shopper API | Admin API |
|---|---|---|
| Security Scheme | ShopperToken | AmOAuth2 |
siteId Parameter | Required | Must omit |
| Max Runtime | 10 seconds | 60 seconds |
| Max Request Body | 5 MiB | 20 MB |
| Activity Type | STOREFRONT | BUSINESS_MANAGER |
The implementation script exports functions matching operationId values:
var RESTResponseMgr = require('dw/system/RESTResponseMgr');
exports.getMyData = function() {
// Get query parameters
var myParam = request.getHttpParameterMap().get('c_my_param').getStringValue();
// Get path parameters (for paths like /items/{itemId})
var itemId = request.getSCAPIPathParameters().get('itemId');
// Get request body (for POST/PUT/PATCH)
var requestBody = JSON.parse(request.httpParameterMap.requestBodyAsString);
// Business logic here...
var result = {
data: 'my data',
param: myParam
};
// Return success response
RESTResponseMgr.createSuccess(result).render();
};
exports.getMyData.public = true; // Required: mark function as public
// Error response example
exports.getMyDataWithError = function() {
RESTResponseMgr
.createError(404, 'not-found', 'Resource Not Found', 'The requested resource was not found.')
.render();
};
exports.getMyDataWithError.public = true;
type field.public = trueEnable Page Caching for the site, then use:
// Cache for 60 seconds
response.setExpires(Date.now() + 60000);
// Personalized caching
response.setVaryBy('price_promotion');
Include responses from other SCAPI endpoints:
var include = dw.system.RESTResponseMgr.createScapiRemoteInclude(
'custom', 'other-api', 'v1', 'endpointPath',
dw.web.URLParameter('siteId', 'MySite')
);
var response = {
data: 'my data',
included: [include]
};
RESTResponseMgr.createSuccess(response).render();
The mapping file binds endpoints to implementations:
{
"endpoints": [
{
"endpoint": "getMyData",
"schema": "schema.yaml",
"implementation": "script"
},
{
"endpoint": "getMyDataV2",
"schema": "schema_v2.yaml",
"implementation": "script_v2"
}
]
}
Important:
Endpoints are registered when activating the code version containing the API definitions. After uploading your cartridge:
For Shopper APIs, the cartridge must be in the site's cartridge path. For Admin APIs, the cartridge must be in the Business Manager site's cartridge path.
Custom APIs have a circuit breaker that blocks requests when error rate exceeds 50%:
Prevention: Write robust code with error handling and avoid long-running remote calls.
When endpoints return 404 or fail to register:
rest-apis/{api-name}/ contains all filesCustomApiRegistry| Error | Cause | Solution |
|---|---|---|
| 400 Bad Request | Contract violation (unknown/invalid params) | Define all params in schema |
| 401 Unauthorized | Invalid/missing token | Check token validity and header |
| 403 Forbidden | Missing scope | Verify scope in token matches contract |
| 404 Not Found | Endpoint not registered | Check status report, verify structure |
| 500 Internal Error | Script error | Check logs for CustomApiInvocationException |
| 503 Service Unavailable | Circuit breaker open | Fix script errors, wait for reset |
siteId in requestssiteId from requestsTo query the Custom API status report, use an Account Manager token with scope:
sfcc.custom-apis (read-only)sfcc.custom-apis.rw (read-write)rest-apis/{api-name}/ structureadditionalProperties is not allowed$ref references supported in schemas (no remote/URL refs)c_ prefixThis skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.