Creating typed API clients with OpenAPI specs, authentication, and OAuth scopes for SCAPI and similar APIs. Use when adding a new SCAPI client, generating types from an OpenAPI spec, setting up OAuth middleware, or integrating a new Commerce API endpoint.
Creating typed API clients with OpenAPI specs, authentication, and OAuth scopes for SCAPI and similar APIs. Use when adding a new SCAPI client, generating types from an OpenAPI spec, setting up OAuth middleware, or integrating a new Commerce API endpoint.
metadata
{"internal":true}
API Client Development
This skill covers creating typed API clients using OpenAPI specifications, with proper authentication and OAuth scope handling. It builds on the patterns in SDK Module Development.
Overview
API clients in this project use:
openapi-fetch: Type-safe HTTP client generated from OpenAPI specs
openapi-typescript: Generates TypeScript types from OpenAPI specs
Middleware pattern: Auth and logging injected via openapi-fetch middleware
Creating a New API Client
1. Add the OpenAPI Spec
Place the spec in packages/b2c-tooling-sdk/specs/:
The OAuthStrategy class has a method for scope injection:
// Creates a new OAuthStrategy with merged scopesconst scopedAuth = auth.withAdditionalScopes(['sfcc.custom-apis', 'SALESFORCE_COMMERCE_API:zzxy_prd']);
Key behaviors:
Returns a newOAuthStrategy instance (immutable pattern)
Merges scopes with deduplication (uses Set)
The new strategy shares token cache with the original (keyed by clientId)
If cached token doesn't have required scopes, it re-authenticates
When API requests fail, use getApiErrorMessage() to extract clean, user-friendly error messages. This utility handles multiple error formats and ensures HTML response bodies (like error pages from stopped sandboxes) are never shown to users.
Using getApiErrorMessage
import {getApiErrorMessage} from'@salesforce/b2c-tooling-sdk/clients';
const {data, error, response} = await client.GET('/sites', {...});
if (error) {
// Returns structured error message or "HTTP 521 Web Server Is Down"const message = getApiErrorMessage(error, response);
this.error(`Failed to fetch sites: ${message}`);
}
Supported Error Patterns
The utility extracts messages from these patterns in priority order:
ERROR: Failed to fetch sites: HTTP 521 Web Server Is Down
Important: Always Destructure response
When making API calls, always destructure the response object alongside error:
// GOOD: Include response for error handlingconst {data, error, response} = await client.GET('/endpoint', {...});
// BAD: Missing response - can't get clean error messageconst {data, error} = await client.GET('/endpoint', {...});
Troubleshooting
OAuth scope errors (401/403 from SCAPI): Ensure the client factory calls auth.withAdditionalScopes() with both the domain scope (e.g., sfcc.custom-apis) and the tenant-specific scope (SALESFORCE_COMMERCE_API:<tenantId>). Use buildTenantScope() which normalizes any tenant ID form (hyphenated, hostname, org ID) to canonical underscores before building scopes.
Type generation failures: Check that the OpenAPI spec in specs/ is valid YAML/JSON. Run pnpm --filter @salesforce/b2c-tooling-sdk run generate:types and inspect the output. Common issues: spec references external files that aren't present, or uses OpenAPI features not supported by openapi-typescript.
Middleware ordering issues: Auth middleware should be added first (client.use(createAuthMiddleware(...))), then logging. In openapi-fetch, middleware runs in reverse registration order for requests, so auth registered first means it runs last — ensuring the logging middleware sees the final request with auth headers.
organizationId mismatch: SCAPI path parameters need the f_ecom_ prefix (use toOrganizationId()), while OAuth scopes need the raw tenant ID (use normalizeTenantId()). Both functions accept any parseable form (hyphenated, hostname, org ID). Mixing these up causes 404s or scope errors.
Checklist: New SCAPI Client
Add OpenAPI spec to specs/
Update generate:types script in package.json
Run pnpm --filter @salesforce/b2c-tooling-sdk run generate:types
Create client module with:
Config interface including tenantId
Default scopes constant
Factory function with scope injection pattern
Tenant ID utilities (or import from existing)
Export from src/clients/index.ts
Add to main src/index.ts if needed
Write tests with MSW mocks
Build: pnpm --filter @salesforce/b2c-tooling-sdk run build
Test: pnpm --filter @salesforce/b2c-tooling-sdk run test