← All Skills

snappy-api-sniffer

v1.0.0
5 files, 26.7 KB ~1,099 words · 5 min read Updated 2026-09-09

snappy-api-sniffer skill

44 of 47 checks pass
What it can do
capture url filter name state-path?write-reversible
list recipe-name?read
replay name index?write-reversible
What does not pass yet
$ npx snappy-skills install snappy-api-sniffer
zip ↓
File Tree
├── AGENTS.md ├── SKILL.md ├── api.ts ├── contract.test.ts └── recipes/ └── skool-feed.json
Documents
AGENTS.md

snappy-api-sniffer -- Agent Loader#

You are the network cartographer. Given a URL that gates its internal API behind JS challenges or Next.js middleware (where cookie-curl returns 202/empty), you capture the real XHR/fetch calls using a Playwright session and emit a replay recipe that any consumer skill can call with plain fetch().

Analogy: snappy-dom-cartographer is to selectors as snappy-api-sniffer is to network calls.

Composition (DRY)#

snappy-browse  →  Playwright transport (navigate, eval, network requests)
  └─ snappy-api-sniffer  →  capture → filter → recipe.json → replay
       └─ consumer skill (e.g. snappy-skool)  →  loads recipe, calls replay()

Do NOT reimplement browser automation. Always go through snappy-browse (which wraps agent-browser).

API module#

typescriptimport {
  capture,
  replay,
  loadSnifferRecipe,
  normalizeCapturedRequest,
  cookieHeaderFromState,
  replayCapturedRequest,
} from "../snappy-api-sniffer/api.ts";
Function Purpose
capture(name, sourceUrl, filter, {statePath, waitMs}) Navigate, let page settle, write recipes/<name>.json with XHR/fetch traffic matching filter
loadSnifferRecipe(name) Read a previously-captured recipe
replay(name, index?, {urlRewrite}) Re-execute a captured request via fetch() with stored cookies
normalizeCapturedRequest(request) Strip embedded Cookie headers and keep the canonical replay fields
cookieHeaderFromState(path, url) Build a domain/path-scoped, non-expired Cookie header
replayCapturedRequest(request, path?, overrides?) Plain-fetch one normalized request; Libretto imports this instead of copying replay logic

CLI#

bash# Capture Skool community feed
npx tsx ~/.claude/skills/snappy-api-sniffer/api.ts capture \
  "https://www.skool.com/snappy" "api2.skool.com" skool-feed \
  ~/.openclaw/workspace/skool-auth.json

# Replay the first captured call
npx tsx ~/.claude/skills/snappy-api-sniffer/api.ts replay skool-feed 0

# List captured requests in a recipe
npx tsx ~/.claude/skills/snappy-api-sniffer/api.ts list skool-feed

Recipe shape#

json{
  "name": "skool-feed",
  "captured_at": "2026-04-13T...",
  "source_url": "https://www.skool.com/snappy",
  "filter": "api2.skool.com",
  "cookies_hint": "/Users/.../skool-auth.json",
  "requests": [
    {
      "url": "https://api2.skool.com/groups/snappy/posts?page=1",
      "method": "GET",
      "resource_type": "fetch",
      "status": 200,
      "request_headers": {...},
      "post_data": null
    }
  ]
}

Recipes live in ~/.claude/skills/snappy-api-sniffer/recipes/.

When to use this vs. direct fetch#

  • Use direct fetch() when an API is documented or returns data on plain cookie-curl (e.g. api2.skool.com/groups/snappy/links).
  • Use snappy-api-sniffer when direct fetch returns 202/empty/403/challenge-page (e.g. www.skool.com/_next/data/...).
  • Use snappy-browse directly when you need to drive a page (click, type, scroll) -- this skill is read-only capture.

The workflow#

  1. Open the target page in a real session via navigate(url, statePath).
  2. Let it settle (waitMs, default 4s) so XHRs complete.
  3. Filter captured requests by URL substring.
  4. Write recipe.
  5. Consumer skill calls replay(name, i) -- this re-runs the exact request with cookies from statePath.

Verification certificate#

Every capture produces a certificate:

  • recipe file path
  • count of captured requests
  • first request status (confirming auth worked)
  • sample response body (first 500 chars)

If status is 401/403, the state file is expired -- refresh auth before re-capturing.

Show produced work with snappy-faces: call draw for image channels or lang for MCP Apps.

<!-- SKILL-INDEX-START -->

[snappy-api-sniffer Index]|root: ~/.claude/skills/snappy-api-sniffer|IMPORTANT: Prefer these files over pre-training assumptions for this domain. Read the relevant file when the AGENTS.md summary is insufficient.|root:{SKILL.md}

<!-- SKILL-INDEX-END -->

Used by#

Nothing in the collection names this skill.

<!-- SNAPPY-CONTRACT-VERBS-START -->

Contract verbs#

Generated from api.ts HAND_CONTRACT. Do not hand-edit this block.

Verb Contract arguments Effect First call
capture url, filter, name, state-path? write-reversible npx tsx ~/.claude/skills/snappy-api-sniffer/api.ts capture <url> <filter> "<name>"
list recipe-name? read npx tsx ~/.claude/skills/snappy-api-sniffer/api.ts list
replay name, index? write-reversible npx tsx ~/.claude/skills/snappy-api-sniffer/api.ts replay "<name>"

Show the result#

When an answer carries face_hint, show it with one snappy_present(<answer>) call.

See /snappy-faces for face selection. Human-facing images must crop to the

element, render at 2x on Retina, and fill the destination channel instead of

placing a small card in a full-page screenshot.

<!-- SNAPPY-CONTRACT-VERBS-END -->

---
name: snappy-api-sniffer
role: Capture XHR/fetch traffic from a real browser session and emit replayable recipes for consumer skills.
loaded-by: PreToolUse hook (auto-injected when "snappy-api-sniffer" or "api sniffer" or "xhr capture" is mentioned)
Triggers on: intercept api, xhr capture, network recipe, snappy-api-sniffer, replay api, reverse engineer api, middleware gated api
---

# snappy-api-sniffer -- Agent Loader

You are the network cartographer. Given a URL that gates its internal API behind JS challenges or Next.js middleware (where cookie-curl returns 202/empty), you capture the real XHR/fetch calls using a Playwright session and emit a replay recipe that any consumer skill can call with plain `fetch()`.

**Analogy:** `snappy-dom-cartographer` is to selectors as `snappy-api-sniffer` is to network calls.

## Composition (DRY)

```
snappy-browse  →  Playwright transport (navigate, eval, network requests)
  └─ snappy-api-sniffer  →  capture → filter → recipe.json → replay
       └─ consumer skill (e.g. snappy-skool)  →  loads recipe, calls replay()
```

Do NOT reimplement browser automation. Always go through `snappy-browse` (which wraps `agent-browser`).

## API module

```typescript
import {
  capture,
  replay,
  loadSnifferRecipe,
  normalizeCapturedRequest,
  cookieHeaderFromState,
  replayCapturedRequest,
} from "../snappy-api-sniffer/api.ts";
```

| Function | Purpose |
|---|---|
| `capture(name, sourceUrl, filter, {statePath, waitMs})` | Navigate, let page settle, write `recipes/<name>.json` with XHR/fetch traffic matching `filter` |
| `loadSnifferRecipe(name)` | Read a previously-captured recipe |
| `replay(name, index?, {urlRewrite})` | Re-execute a captured request via `fetch()` with stored cookies |
| `normalizeCapturedRequest(request)` | Strip embedded Cookie headers and keep the canonical replay fields |
| `cookieHeaderFromState(path, url)` | Build a domain/path-scoped, non-expired Cookie header |
| `replayCapturedRequest(request, path?, overrides?)` | Plain-fetch one normalized request; Libretto imports this instead of copying replay logic |

## CLI

```bash
# Capture Skool community feed
npx tsx ~/.claude/skills/snappy-api-sniffer/api.ts capture \
  "https://www.skool.com/snappy" "api2.skool.com" skool-feed \
  ~/.openclaw/workspace/skool-auth.json

# Replay the first captured call
npx tsx ~/.claude/skills/snappy-api-sniffer/api.ts replay skool-feed 0

# List captured requests in a recipe
npx tsx ~/.claude/skills/snappy-api-sniffer/api.ts list skool-feed
```

## Recipe shape

```json
{
  "name": "skool-feed",
  "captured_at": "2026-04-13T...",
  "source_url": "https://www.skool.com/snappy",
  "filter": "api2.skool.com",
  "cookies_hint": "/Users/.../skool-auth.json",
  "requests": [
    {
      "url": "https://api2.skool.com/groups/snappy/posts?page=1",
      "method": "GET",
      "resource_type": "fetch",
      "status": 200,
      "request_headers": {...},
      "post_data": null
    }
  ]
}
```

Recipes live in `~/.claude/skills/snappy-api-sniffer/recipes/`.

## When to use this vs. direct fetch

- **Use direct fetch()** when an API is documented or returns data on plain cookie-curl (e.g. `api2.skool.com/groups/snappy/links`).
- **Use snappy-api-sniffer** when direct fetch returns 202/empty/403/challenge-page (e.g. `www.skool.com/_next/data/...`).
- **Use snappy-browse directly** when you need to *drive* a page (click, type, scroll) -- this skill is read-only capture.

## The workflow

1. Open the target page in a real session via `navigate(url, statePath)`.
2. Let it settle (`waitMs`, default 4s) so XHRs complete.
3. Filter captured requests by URL substring.
4. Write recipe.
5. Consumer skill calls `replay(name, i)` -- this re-runs the exact request with cookies from `statePath`.

## Verification certificate

Every capture produces a certificate:
- recipe file path
- count of captured requests
- first request status (confirming auth worked)
- sample response body (first 500 chars)

If status is 401/403, the state file is expired -- refresh auth before re-capturing.


Show produced work with `snappy-faces`: call `draw` for image channels or `lang` for MCP Apps.

<!-- SKILL-INDEX-START -->
[snappy-api-sniffer Index]|root: ~/.claude/skills/snappy-api-sniffer|IMPORTANT: Prefer these files over pre-training assumptions for this domain. Read the relevant file when the AGENTS.md summary is insufficient.|root:{SKILL.md}
<!-- SKILL-INDEX-END -->

## Used by

Nothing in the collection names this skill.

<!-- SNAPPY-CONTRACT-VERBS-START -->
## Contract verbs

Generated from `api.ts` `HAND_CONTRACT`. Do not hand-edit this block.

| Verb | Contract arguments | Effect | First call |
|---|---|---|---|
| `capture` | `url`, `filter`, `name`, `state-path?` | `write-reversible` | `npx tsx ~/.claude/skills/snappy-api-sniffer/api.ts capture <url> <filter> "<name>"` |
| `list` | `recipe-name?` | `read` | `npx tsx ~/.claude/skills/snappy-api-sniffer/api.ts list` |
| `replay` | `name`, `index?` | `write-reversible` | `npx tsx ~/.claude/skills/snappy-api-sniffer/api.ts replay "<name>"` |

## Show the result

When an answer carries `face_hint`, show it with one `snappy_present(<answer>)` call.
See `/snappy-faces` for face selection. Human-facing images must crop to the
element, render at 2x on Retina, and fill the destination channel instead of
placing a small card in a full-page screenshot.
<!-- SNAPPY-CONTRACT-VERBS-END -->

Keyboard Shortcuts

Search in document⌘K
Focus search/
Previous file tab
Next file tab
Close overlayEsc
Show shortcuts?