# Dsc Endpoint Help

> Look up a spec field on, or diff a failing request against, one named endpoint in a Salesforce API reference published on developer.salesforce.com ("DSC"). Invoke whenever the user's ask is about what one specific endpoint's spec says or why one specific call doesn't match it – OAuth scopes, query params, request body, response schema, auth scheme, HTTP method/path, or a cURL/HTTP request paired…

- **Type:** Skill
- **Install:** `agentstack add skill-j-256-claude-code-skills-dsc-endpoint-help`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [j-256](https://agentstack.voostack.com/s/j-256)
- **Installs:** 0
- **Category:** [Web & Browser](https://agentstack.voostack.com/c/web-and-browser)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [j-256](https://github.com/j-256)
- **Source:** https://github.com/j-256/claude-code-skills/tree/main/skills/dsc-endpoint-help

## Install

```sh
agentstack add skill-j-256-claude-code-skills-dsc-endpoint-help
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# DSC Endpoint Help

Answer one question about one endpoint in a Salesforce API reference on DSC – either by quoting the relevant spec field, or (when a failing request is attached) by diffing the request against the spec and naming the root cause. Every claim is backed by a public `developer.salesforce.com` URL the user can forward to a customer.

## When to use

**Lookup branch** (no failing request attached):

- A direct spec-field question about a named endpoint: "what scopes does shopper-products getProducts need", "which query params does searchOrders take", "what auth scheme guards createOrder".
- A code-generation ask that references a named endpoint: "write me a node script that calls getProduct" – quote the spec the user can write the code against; don't write the code.
- A how-to ask that's really a spec-field question in disguise: "how do I paginate search results", "what limit does X accept" – quote the relevant param.

**Diff branch** (a failing request is attached):

- A cURL command + an error body: "why is this 403ing", "what's wrong with this request".
- A request + an `insufficient_scope` / `invalid_client` / `unauthorized_client` envelope, optionally with a JWT to decode for the scope diff.
- A content-type / body-shape failure: "diff this against the spec", "is this 415 because content-type is wrong".

## Inputs from the user

The user's prompt usually contains some of:

- **A reference name** ("shopper-products", "orders", "scapi shopper baskets") – maybe abbreviated, maybe product-branded ("SCAPI Shopper Products" → `shopper-products`).
- **An operation identifier** ("getProducts", "createOrder", sometimes partial: "the products get endpoint", "that getCustomer call").
- **A concrete spec-field question** (lookup branch): what scopes, what params, what body, what response, what method/path, what auth scheme.
- **A request artifact** (diff branch): cURL, raw HTTP, or `{method + URL}`. Required for the diff branch.
- **An error response** (diff branch): `{status, body}`. Required for the diff branch. Body can be JSON or prose.
- **An access token (JWT)** (diff branch, optional): if provided (or embedded in the request's `Authorization: Bearer` header), the skill decodes the `scp` claim for a high-confidence scope diff.
- **Registered client scopes** (diff branch, optional): if the user has the list from their SLAS/OAuth client config, pass it instead (confidence: medium, with a disclaimer).
- **Reference URL** (diff branch): the developer.salesforce.com URL of the reference containing this endpoint. Usually inferrable from the request path (`/checkout/shopper-baskets/...` → `https://developer.salesforce.com/docs/commerce/commerce-api/references/shopper-baskets`). If the path is ambiguous, ask.

Sometimes one of these is missing. See "Disambiguation" below.

## Choosing the output shape

The diff branch fires when **both** of these conditions hold; the lookup branch fires otherwise.

1. **Request artifact present.** A cURL command, a raw HTTP request line/block, or a `{method, url, body?}` shaped paste. Detection cues: `curl ` followed by a URL; HTTP request-line pattern (`  HTTP/`); an explicit `Authorization:` / `x-dw-client-id:` / similar request-shaped header line.
2. **Error artifact present.** A 4xx or 5xx status code in proximity to the request artifact (`returns 403`, `403ing`, `status: 401`); a recognized error body / fault envelope (SCAPI's `{"error": "..."}` shape, OCAPI's `{"fault": {"type": "...", ...}}` shape, OAuth's `invalid_client` / `insufficient_scope` / `unauthorized_client` strings); or explicit failure framing tied to the request (`why is this failing`, `what's wrong`, `diff this against the spec`).

If both are present, the prompt is asking "why is this specific call broken" – diff branch (`triage.js`). If only one or neither is present, the prompt is asking a spec-field question (possibly with failure-themed phrasing) – lookup branch (`query.js`).

### Boundary cases this resolves cleanly

| Input shape | Branch | Why |
|---|---|---|
| Bare spec-field Q (`"what scopes does X need"`) | lookup | No request, no error |
| cURL pasted, no error mentioned | lookup | Request present, but no failure to diff against |
| Error body / status alone, no request | lookup | Failure-themed, but nothing to compare against |
| cURL + error body together | diff | Both signals present |
| `getCustomer 403 + JWT decode` | diff | cURL implied, JWT-decode is the diff |
| `createBasket 400 missing_parameter` (status only, no body) | lookup-with-nudge | Spec-field framing dominates; brief mention that the 400 likely indicates a required field, paste the body for a real diff |
| `B2C job webhook silently dropping events` (decline) | declined | No endpoint, no request – falls into the decline list |

## Cache location

Use `~/.cache/dsc-scrape/` as the cache root. Create it if it doesn't exist (scrape will create subdirs itself). This is shared across projects – scraping once benefits future sessions.

Per-reference layout inside the cache mirrors `dsc-scrape`'s output:

```
~/.cache/dsc-scrape/
└── /
    ├── _index.json           full slug list + title + siblings
    ├── Summary.json          overview prose (NOT an endpoint)
    ├── .json    one file per endpoint
    └── types/.json one file per named type
```

## Lookup branch flow

1. **Pick reference + slug** from the user's question. If either is missing or ambiguous, disambiguate (see below) before running anything.
2. **Always refresh first.** Run `scrapeRefresh` (from `lib/scrape-refresh.js`) against the reference root before querying. The shared scrape library owns a 1-hour TTL matching DSC's upstream `cache-control: max-age=3600`, so when the cache is fresh this costs one `_index.json` read and zero network round-trips. The returned summary has `refreshed: true` (new data fetched) or `refreshed: false` (cache already fresh).
3. **Query locally** by running `scripts/query.js`. If it exits 0, you have the data. If exit 3 (slug not found / ambiguous), use the returned `candidates` to confirm with the user or narrow.
4. **Write the answer in prose**, quoting only the field the user asked about, and cite the public DSC URL – the `url` field in the JSON returned by `query.js`. Never cite the local cache path in your output. (If the user explicitly asks "where's the local copy?", read the absolute path from `query.js`'s `file` field on demand; don't volunteer it.)

### Step 1: Resolve reference + slug

The user's question may name a reference directly ("shopper-products getProducts"), name it under a brand or rebrand ("SCAPI products" → `shopper-products`; "Data Cloud" → Data 360), or leave it implicit ("how do I paginate searches" – which search?). Resolve to a concrete `/` pair before any other step.

**Default discovery path: bootstrap via the shared scrape library.** When the reference name isn't already concrete in your context, call `scrapeRefresh` against `https://developer.salesforce.com/docs/apis` first. This writes `~/.cache/dsc-scrape/_catalog.json` listing every product DSC publishes, with each product's `referenceUrl`, a `referenceShape` tag (`area-landing` / `reference-root` / `atlas` / `static-html` / `unknown` – only the first two are scrapeable), and a `searchKeys` array (acronyms drawn from the product's landing titles plus hand-curated entries like "SCAPI"). Match the user's hint case-insensitive substring against `title`, `body`, AND `searchKeys` – the third surface is what makes acronyms like "OCI" or "MIAW" resolve cold-cache without relying on the model's training data. Once a product is picked, scrape its `referenceUrl` (a product-area landing) to get `_landing/_.json`, which lists every reference in that area with its `id`, `title`, and `referenceType` (`rest-oa3` / `rest-raml` / `rest-oa2` are scrapeable; `markdown` isn't). Read these files to anchor your slug pick to ground truth instead of guessing. Both list-only modes share the 1-hour TTL with reference scrapes – once `_catalog.json` exists locally, follow-on discovery in this session is free.

**Shortcut: skip the catalog scrape only if the reference name is already concrete.** If the user explicitly named a Commerce SCAPI reference ("shopper-products", "shopper-baskets", "orders") or one you've already cached this session, you can scrape its reference root directly without going through the catalog. The 1-hour TTL absorbs the cost if you're wrong about cache state.

You can list what's already on disk via `node scripts/list.js ~/.cache/dsc-scrape/` to skip a redundant catalog scrape.

Common name drifts to anchor against the catalog/landing:

| User says | Reference slug |
|---|---|
| "Shopper Products", "SCAPI products", "the products API" | `shopper-products` |
| "Orders API", "SCAPI orders" | `orders` (merchant-facing, under commerce-api) **or** `shopper-orders` (shopper-facing) – different references |
| "Customer Groups" | the operations live in `customers`, not `customer-groups` |
| "Data Cloud X", "Data 360 X" | Data 360 references (Salesforce rebranded) |

The **slug** is typically the `operationId` (`getProducts`, `createOrder`). Fuzzy matching is built in – `query.js` will resolve "products" against the index if there's exactly one match.

### Step 2: Refresh the cache

Use `lib/scrape-refresh.js` to warm the cache before every query. The helper owns the subprocess dance, calls into the shared scrape library at `lib/scrape/scrape.js`, and returns a normalized `{refreshed, reference, format, specUrl, files, cacheRoot}` object. When the cache is still within its 1-hour TTL, `scrapeRefresh` returns `refreshed: false` without fetching – calling it unconditionally is effectively free.

```js
const { scrapeRefresh } = require('./lib/scrape-refresh.js');

const result = await scrapeRefresh({
  referenceUrl: 'https://developer.salesforce.com/docs///references/',
  // scrapeScript defaults to lib/scrape/scrape.js (resolved via require.resolve)
  // cacheRoot defaults to ~/.cache/dsc-scrape
});
```

Scraping the **reference root** (no `?meta=`) writes the whole reference in one pass – Summary + every endpoint + every type + `_index.json`. Do this even if the user only asked about one endpoint. The network cost is identical: the scraper downloads a single static spec file that already contains every operation, and writing one slug vs. all of them is just a parse-time decision. Upsides of the whole-reference scrape:

1. **Slug safety** – if the user's slug guess was slightly off, `_index.json` lets you correct it without a second fetch.
2. **Future cache hits** – any later question about any other endpoint in this reference is free.
3. **Type resolution works** – `--resolve-refs` reads `types/.json`. Those files only exist if the whole reference was scraped.
4. **TTL is cheap.**

Only scrape a single slug (`?meta=`) if the user explicitly asked for just that one to land on disk.

**If the scrape exits 1 with a 404 on a reference root** (your shortcut path was wrong – misspelled, rebranded, or not in that product area), fall back to the same cascade Step 1 describes: scrape `/docs/apis` for `_catalog.json`, then the product's `referenceUrl` for `_landing/.json`, then the corrected reference root. Don't guess variations by re-scraping them one at a time.

A few products have `/references/` pages but don't appear in the `/docs/apis` catalog – if the catalog has no match for a product the user named, lowercase the user's hint and substring-match it against the keys in `lib/scrape/aliases.js` (the `CATALOG_MISSING_ALIASES` map) for the area-landing URL. Only ask the user for a DSC URL if neither catalog nor alias map resolves the hint.

If `referenceType` is anything other than `rest-oa3`, `rest-raml`, or `rest-oa2` (for example `markdown`), the reference isn't a machine-readable spec the scrape library can deliver – tell the user and stop.

After a successful scrape, run `query.js`. If it can't find the slug, read `_index.json`'s slug list – the user's operation name may also be off (e.g. `searchCustomerGroups` plural vs. `searchCustomerGroup` singular).

### Step 3: Query locally

```bash
node /scripts/query.js ~/.cache/dsc-scrape/   [--field ]
```

Match the question to the right field – this keeps the output small and focused:

| User asks... | Use `--field` |
|---|---|
| "what scopes...", "what OAuth...", "which permissions..." | `security` |
| "what params...", "what query params...", "required params..." | `parameters` |
| "what's the request body", "what fields in the POST body" | `body` – add `--resolve-refs` |
| "what response schema", "what does it return", "what's the 200 response" | `responses` – add `--resolve-refs` |
| "HTTP method", "path", "endpoint URL" – any of these alone | `all` (the header is included with every field) |
| "show me the whole endpoint" | `all` or `raw` if they want the full JSON untouched |

**`--resolve-refs` matters a lot for `body` and `responses` questions.** Without it you get back `schemaRef: "#/components/schemas/Product"` and you'd have to read `types/Product.json` separately (and every type it nests) to get real fields. `--resolve-refs` inlines the referenced type in one call, so the user's question ("what does it return?") gets a direct answer from a single script run instead of a chain of file reads.

Examples are stripped by default (they can be huge). Pass `--include-examples` only if the user explicitly wants them.

### Step 4: Answer in prose

Lead with the direct answer, then show the evidence (one-line quote of the relevant JSON shape), then the file path. One or two paragraphs for most questions; grouped bullets when there are many related facts (e.g. long parameter list, wide response type).

**Freshness preamble:** If `scrapeRefresh` returned `refreshed: true` *and* there was a prior cache (i.e. this wasn't a first-ever scrape), open with a single short sentence: *"I refreshed the cache first – the upstream spec had changed."* Then answer. If `refreshed: false`, or if this was the first scrape of this reference, skip the preamble and go straight to the answer. You can tell it's a first-ever scrape if `_index.json` didn't exist before your `scrapeRefresh` call.

**Format guidance – this matters because answers are read in a terminal:**

- Use **prose + bullets**. Skip markdown tables – they render fine in a rendered preview but look like walls of `|` characters in the raw terminal most users are actually reading. Bullets degrade gracefully.
- Use **one-line JSON-ish quotes** (`security: [{ scheme: "X", scopes: [...] }]`) for shape, not full pretty-printed blocks. The user can open the cited file if they want the full thing.
- **Group related fields** when a type is wide (e.g. a Product response with 45 fields). "Scalars: ..., Pricing: ..., Merchandising: ..." beats an alphabetical dump.
- **No hedging preamble.** Don't open with "Based on the cached JSON, I can tell you that..." – just answer.
- **Cite the `url` field** from `query.js`'s structured output at the end, not inline mid-sentence. Never cite the local cache path.

**Example: "what scopes does shopper-products getProducts need?"**

> `getProducts`'s spec lists `sfcc.shopper-products` and `sfcc.shopper-standard` under the `ShopperToken` scheme – either grants access. `sfcc.shopper-standard` is a meta-scope that bundles the common shopper feature scopes (including `sfcc.shopper-products`), so a token with `shopper-standard` covers `getProducts` already; see https://developer.salesforce.com/docs/commerce/commerce-api/guide/standard-shopper-scope.html.
>
> `security: [{ scheme: "ShopperToken", scopes: ["sfcc.shopper-products", "sfcc.shopper-standard"] }]`
>
> Source: https://developer.salesforce.com/docs/commerce/commerce-api/references/shopper-products?meta=getProducts

**Note on `security[]` semantics in practice.** OAS says all scopes within a single `security[]` entry are required together (AND); multiple entries in the array are a

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [j-256](https://github.com/j-256)
- **Source:** [j-256/claude-code-skills](https://github.com/j-256/claude-code-skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** yes
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-j-256-claude-code-skills-dsc-endpoint-help
- Seller: https://agentstack.voostack.com/s/j-256
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
