Skip to content

Commit 901fe40

Browse files
authored
Add SCAPI and headless development skills (#101)
* add SCAPI and headless development skills New skills for B2C Commerce headless development: - b2c-scapi-shopper: consuming Shopper APIs for PWA/headless storefronts - b2c-scapi-admin: consuming Admin APIs for backend integrations - b2c-slas-auth-patterns: advanced auth (OTP, passkeys, session bridge) - b2c-custom-objects: custom object CRUD and OCAPI search - b2c-ordering: order management patterns Updated b2c-config and b2c-slas skills with cross-references. * fix skill documentation based on docs cross-reference b2c-slas-auth-patterns: - Fix Email OTP endpoint (/passwordless/login not /passwordless/start) - Fix parameter names (pwdless_login_token not passwordless_token) - Add required channel_id to token refresh examples - Add passkey OTP pre-verification requirement - Add session bridge hint parameter (sb-guest/sb-user) - Add TSOB 3-second protection window constraint - Add Hybrid Auth section (B2C 25.3+ replacement for Plugin SLAS) - Update WebAuthn endpoints to correct paths b2c-custom-objects: - Update OCAPI version from v24_1 to v25_6 - Fix Shopper Custom Objects endpoint path format - Add type-specific scope documentation - Document searchable system fields b2c-ordering: - Remove incorrect undoFailOrder() documentation - Document that failed orders cannot be reopened - Add failed_with_reopen SCAPI status (B2C 24.3+) - Add undoCancelOrder() for cancelled order recovery b2c-scapi-admin: - Add gzip compression requirement for IMPEX >100MB - Add critical IMPEX best practices - Note that IMPEX logs don't appear in Log Center - Expand NDJSON example b2c-scapi-admin OAuth scopes: - Add 15+ missing scopes (consents, CORS, customer groups, etc.) - Add Configuration, Experience, and Developer scope categories - Reorganize Customer Management scopes b2c-scapi-shopper: - Note Shopper Baskets v1/v2 support - Expand Shopper Context best practices with quota limits
1 parent 1a3117c commit 901fe40

15 files changed

Lines changed: 4963 additions & 1 deletion

File tree

skills/b2c-cli/skills/b2c-config/SKILL.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,29 @@ If a value comes from an unexpected source:
107107

108108
By default, passwords and secrets show partial values like `admi...REDACTED`. Use `--unmask` to see full values when debugging authentication issues.
109109

110+
## Getting Admin OAuth Tokens
111+
112+
Use `b2c auth token` to get an admin OAuth access token for Account Manager credentials (OCAPI and Admin APIs). This is useful for testing APIs, scripting, or CI/CD pipelines.
113+
114+
```bash
115+
# Get access token (outputs raw token to stdout)
116+
b2c auth token
117+
118+
# Get token with specific scopes
119+
b2c auth token --scope sfcc.orders --scope sfcc.products
120+
121+
# Get token as JSON (includes expiration and scopes)
122+
b2c auth token --json
123+
124+
# Use in curl for OCAPI calls
125+
curl -H "Authorization: Bearer $(b2c auth token)" \
126+
"https://your-instance.dx.commercecloud.salesforce.com/s/-/dw/data/v24_1/sites"
127+
```
128+
129+
The token is obtained using the `clientId` and `clientSecret` from your configuration (dw.json or environment variables). If only `clientId` is configured, an implicit OAuth flow is used (browser-based).
130+
131+
**Note:** This command returns **admin** tokens for OCAPI/Admin APIs. For **shopper** tokens (SLAS), see the [b2c-slas skill](../b2c-slas/SKILL.md).
132+
110133
## More Commands
111134

112135
See `b2c setup --help` for other setup commands including `b2c setup skills` for AI agent skill installation.

skills/b2c-cli/skills/b2c-slas/SKILL.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
---
22
name: b2c-slas
3-
description: Manage SLAS (Shopper Login and API Access Service) clients for B2C Commerce (SFCC/Demandware) with the b2c cli. Use when configuring shopper authentication, creating API clients for PWA/headless, managing OAuth scopes (including custom scopes like c_loyalty), or debugging token issues.
3+
description: Manage SLAS (Shopper Login and API Access Service) clients for B2C Commerce (SFCC/Demandware) with the b2c cli. Use when configuring shopper authentication, creating API clients for PWA/headless, managing OAuth scopes (including custom scopes like c_loyalty), or debugging token issues. SLAS is for shopper (customer) authentication, not admin APIs.
44
---
55

66
# B2C SLAS Skill
77

88
Use the `b2c` CLI plugin to manage SLAS (Shopper Login and API Access Service) API clients and credentials.
99

10+
> **Important:** SLAS is for **shopper** (customer) authentication used by storefronts and headless commerce. For **admin** tokens (OCAPI, Admin APIs), use `b2c auth token` - see [b2c-config skill](../b2c-config/SKILL.md).
11+
1012
> **Tip:** If `b2c` is not installed globally, use `npx @salesforce/b2c-cli` instead (e.g., `npx @salesforce/b2c-cli slas client list`).
1113
1214
## When to Use
Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
---
2+
name: b2c-custom-objects
3+
description: Work with custom objects in B2C Commerce using Script API and OCAPI. Use when storing custom business data, querying custom objects, implementing data persistence, or creating site-scoped or global data stores. Covers CustomObjectMgr, OCAPI Data API, search queries, and Shopper Custom Objects API.
4+
---
5+
6+
# B2C Custom Objects
7+
8+
Custom objects store business data that doesn't fit into standard system objects. They support both site-scoped and organization-scoped (global) data, with full CRUD operations via Script API and OCAPI.
9+
10+
## When to Use Custom Objects
11+
12+
| Use Case | Example |
13+
|----------|---------|
14+
| Business configuration | Store configuration per site or globally |
15+
| Integration data | Cache external system responses |
16+
| Custom entities | Loyalty tiers, custom promotions, vendor data |
17+
| Temporary processing | Job processing queues, import staging |
18+
19+
## Custom Object Types
20+
21+
Custom objects are defined in Business Manager under **Administration > Site Development > Custom Object Types**. Each type has:
22+
23+
- **ID**: Unique identifier (e.g., `CustomConfig`)
24+
- **Key Attribute**: Primary key field for lookups
25+
- **Attributes**: Custom attributes for data storage
26+
- **Scope**: Site-scoped or organization-scoped (global)
27+
28+
## Script API (CustomObjectMgr)
29+
30+
### Getting Custom Objects
31+
32+
```javascript
33+
var CustomObjectMgr = require('dw/object/CustomObjectMgr');
34+
35+
// Get a single custom object by type and key
36+
var config = CustomObjectMgr.getCustomObject('CustomConfig', 'myConfigKey');
37+
38+
if (config) {
39+
var value = config.custom.configValue;
40+
}
41+
```
42+
43+
### Creating Custom Objects
44+
45+
```javascript
46+
var CustomObjectMgr = require('dw/object/CustomObjectMgr');
47+
var Transaction = require('dw/system/Transaction');
48+
49+
Transaction.wrap(function() {
50+
// Create new custom object (type, keyValue)
51+
var obj = CustomObjectMgr.createCustomObject('CustomConfig', 'newKey');
52+
obj.custom.configValue = 'myValue';
53+
obj.custom.isActive = true;
54+
});
55+
```
56+
57+
### Querying Custom Objects
58+
59+
```javascript
60+
var CustomObjectMgr = require('dw/object/CustomObjectMgr');
61+
62+
// Query with attribute filter
63+
var objects = CustomObjectMgr.queryCustomObjects(
64+
'CustomConfig', // Type
65+
'custom.isActive = {0}', // Query (uses positional params)
66+
'creationDate desc', // Sort order
67+
true // Parameter value for {0}
68+
);
69+
70+
while (objects.hasNext()) {
71+
var obj = objects.next();
72+
// Process object
73+
}
74+
objects.close();
75+
```
76+
77+
### Deleting Custom Objects
78+
79+
```javascript
80+
var CustomObjectMgr = require('dw/object/CustomObjectMgr');
81+
var Transaction = require('dw/system/Transaction');
82+
83+
Transaction.wrap(function() {
84+
var obj = CustomObjectMgr.getCustomObject('CustomConfig', 'keyToDelete');
85+
if (obj) {
86+
CustomObjectMgr.remove(obj);
87+
}
88+
});
89+
```
90+
91+
### Getting All Objects of a Type
92+
93+
```javascript
94+
var CustomObjectMgr = require('dw/object/CustomObjectMgr');
95+
96+
// Get all objects of a type
97+
var allConfigs = CustomObjectMgr.getAllCustomObjects('CustomConfig');
98+
99+
while (allConfigs.hasNext()) {
100+
var config = allConfigs.next();
101+
// Process
102+
}
103+
allConfigs.close();
104+
```
105+
106+
## CustomObjectMgr API Reference
107+
108+
| Method | Description |
109+
|--------|-------------|
110+
| `getCustomObject(type, keyValue)` | Get single object by type and key |
111+
| `createCustomObject(type, keyValue)` | Create new object (within transaction) |
112+
| `remove(object)` | Delete object (within transaction) |
113+
| `queryCustomObjects(type, query, sortString, ...args)` | Query with filters |
114+
| `getAllCustomObjects(type)` | Get all objects of a type |
115+
| `describe(type)` | Get metadata about the custom object type |
116+
117+
## OCAPI Data API
118+
119+
### Get Custom Object
120+
121+
```http
122+
GET /s/-/dw/data/v25_6/custom_objects/{object_type}/{key}
123+
Authorization: Bearer {token}
124+
```
125+
126+
**Note:** Use `/s/{site_id}/dw/data/v25_6/custom_objects/...` for site-scoped objects, or `/s/-/dw/data/v25_6/custom_objects/...` for organization-scoped (global) objects.
127+
128+
### Create Custom Object
129+
130+
```http
131+
PUT /s/-/dw/data/v25_6/custom_objects/{object_type}/{key}
132+
Authorization: Bearer {token}
133+
Content-Type: application/json
134+
135+
{
136+
"key_property": "myKey",
137+
"c_configValue": "myValue",
138+
"c_isActive": true
139+
}
140+
```
141+
142+
### Update Custom Object
143+
144+
```http
145+
PATCH /s/-/dw/data/v25_6/custom_objects/{object_type}/{key}
146+
Authorization: Bearer {token}
147+
Content-Type: application/json
148+
149+
{
150+
"c_configValue": "updatedValue"
151+
}
152+
```
153+
154+
### Delete Custom Object
155+
156+
```http
157+
DELETE /s/-/dw/data/v25_6/custom_objects/{object_type}/{key}
158+
Authorization: Bearer {token}
159+
```
160+
161+
### Search Custom Objects
162+
163+
```http
164+
POST /s/-/dw/data/v25_6/custom_object_search/{object_type}
165+
Authorization: Bearer {token}
166+
Content-Type: application/json
167+
168+
{
169+
"query": {
170+
"bool_query": {
171+
"must": [
172+
{ "term_query": { "field": "c_isActive", "value": true } }
173+
]
174+
}
175+
},
176+
"select": "(**)",
177+
"sorts": [{ "field": "creation_date", "sort_order": "desc" }],
178+
"start": 0,
179+
"count": 25
180+
}
181+
```
182+
183+
### Search Query Types
184+
185+
| Query Type | Description | Example |
186+
|------------|-------------|---------|
187+
| `term_query` | Exact match | `{"field": "c_status", "value": "active"}` |
188+
| `text_query` | Full-text search | `{"fields": ["c_name"], "search_phrase": "test"}` |
189+
| `range_query` | Range comparison | `{"field": "c_count", "from": 1, "to": 10}` |
190+
| `bool_query` | Combine queries | `{"must": [...], "should": [...], "must_not": [...]}` |
191+
| `match_all_query` | Match all records | `{}` |
192+
193+
## Shopper Custom Objects API (SCAPI)
194+
195+
For read-only access from storefronts, use the Shopper Custom Objects API. This requires specific OAuth scopes.
196+
197+
### Get Custom Object (Shopper)
198+
199+
```http
200+
GET https://{shortCode}.api.commercecloud.salesforce.com/custom-object/shopper-custom-objects/v1/organizations/{organizationId}/custom-objects/{objectType}/{key}?siteId={siteId}
201+
Authorization: Bearer {shopper_token}
202+
```
203+
204+
### Required Scopes
205+
206+
For the Shopper Custom Objects API, configure these scopes in your SLAS client:
207+
208+
- `sfcc.shopper-custom-objects` - Global read access to all custom object types
209+
- `sfcc.shopper-custom-objects.{objectType}` - Type-specific read access
210+
211+
**Note:** SLAS clients can have a maximum of 20 custom object scopes.
212+
213+
The custom object type must also be enabled for shopper access in Business Manager.
214+
215+
### Searchable System Fields
216+
217+
All custom objects have these system fields available for OCAPI search queries:
218+
219+
- `creation_date` - When the object was created (Date)
220+
- `last_modified` - When the object was last modified (Date)
221+
- `key_value_string` - String key value
222+
- `key_value_integer` - Integer key value
223+
- `site_id` - Site identifier (for site-scoped objects)
224+
225+
## Best Practices
226+
227+
### Do
228+
229+
- Use transactions for create/update/delete operations
230+
- Close query iterators when done (`objects.close()`)
231+
- Use meaningful key values for efficient lookups
232+
- Index frequently queried attributes
233+
- Use site-scoped objects for site-specific data
234+
- Use organization-scoped objects for shared configuration
235+
236+
### Don't
237+
238+
- Store sensitive data without encryption
239+
- Create excessive custom object types
240+
- Use custom objects for high-volume transactional data
241+
- Forget to handle null returns from `getCustomObject()`
242+
- Leave query iterators open (causes resource leaks)
243+
244+
## Common Patterns
245+
246+
### Configuration Store
247+
248+
```javascript
249+
var CustomObjectMgr = require('dw/object/CustomObjectMgr');
250+
var Site = require('dw/system/Site');
251+
252+
function getConfig(key, defaultValue) {
253+
var configKey = Site.current.ID + '_' + key;
254+
var obj = CustomObjectMgr.getCustomObject('SiteConfig', configKey);
255+
256+
if (obj && obj.custom.value !== null) {
257+
return JSON.parse(obj.custom.value);
258+
}
259+
return defaultValue;
260+
}
261+
262+
function setConfig(key, value) {
263+
var Transaction = require('dw/system/Transaction');
264+
var configKey = Site.current.ID + '_' + key;
265+
266+
Transaction.wrap(function() {
267+
var obj = CustomObjectMgr.getCustomObject('SiteConfig', configKey);
268+
if (!obj) {
269+
obj = CustomObjectMgr.createCustomObject('SiteConfig', configKey);
270+
}
271+
obj.custom.value = JSON.stringify(value);
272+
});
273+
}
274+
```
275+
276+
### Processing Queue
277+
278+
```javascript
279+
var CustomObjectMgr = require('dw/object/CustomObjectMgr');
280+
var Transaction = require('dw/system/Transaction');
281+
282+
// Add to queue
283+
function enqueue(data) {
284+
var key = 'job_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
285+
Transaction.wrap(function() {
286+
var obj = CustomObjectMgr.createCustomObject('JobQueue', key);
287+
obj.custom.data = JSON.stringify(data);
288+
obj.custom.status = 'pending';
289+
});
290+
}
291+
292+
// Process queue
293+
function processQueue() {
294+
var pending = CustomObjectMgr.queryCustomObjects(
295+
'JobQueue',
296+
'custom.status = {0}',
297+
'creationDate asc',
298+
'pending'
299+
);
300+
301+
while (pending.hasNext()) {
302+
var job = pending.next();
303+
Transaction.wrap(function() {
304+
job.custom.status = 'processing';
305+
});
306+
307+
try {
308+
var data = JSON.parse(job.custom.data);
309+
processJob(data);
310+
311+
Transaction.wrap(function() {
312+
CustomObjectMgr.remove(job);
313+
});
314+
} catch (e) {
315+
Transaction.wrap(function() {
316+
job.custom.status = 'failed';
317+
job.custom.error = e.message;
318+
});
319+
}
320+
}
321+
pending.close();
322+
}
323+
```
324+
325+
## Detailed References
326+
327+
- [OCAPI Search Queries](references/OCAPI-SEARCH.md) - Full search query syntax and examples

0 commit comments

Comments
 (0)