---
name: web-api-flow-capture
description: Reverse-engineer SPA/payment/checkout API flows by capturing network traffic via browser automation (Playwright/Camoufox), then emulating the API calls server-side. Use when the user needs to understand form submissions, multi-step checkouts, membership validations, or any JavaScript-heavy web flow where direct curl fails.
triggers:
  - "capturar flujo"
  - "reverse engineer API"
  - "emular formulario"
  - "interceptar requests"
  - "checkout flow"
  - "SPA form"
  - "bypass client validation"
requirements:
  python_packages:
    - playwright
    - requests
  system_packages:
    - libgtk-3-0
    - libnss3
    - libnspr4
    - libatk-bridge2.0-0
    - libx11-6
    - libxrandr2
    - libxtst6
    - libasound2
  venv: /tmp/camoufox-venv
---

# Web API Flow Capture & Emulation

General technique for reverse-engineering SPA/payment/checkout API flows: use headless browser automation to capture all XHR/fetch traffic while interacting with forms, then replay the captured API calls server-side with Python `requests`.

## Workflow

### Phase 1: Initial reconnaissance

1. **Load the page** in headless Chromium (Playwright) with full network interception
2. **Dump all forms and inputs** — even if the SPA hasn't rendered them yet, wait for `networkidle` + extra timeout
3. **Dump visible text** to understand page state
4. **Take screenshots** at each step for debugging

```python
page.goto(url, timeout=60000, wait_until='networkidle')
page.wait_for_timeout(5000)

# Dump inputs
inputs = page.evaluate("""() => {
    const inputs = document.querySelectorAll('input, select, textarea, button');
    return Array.from(inputs).map(inp => ({
        tag: inp.tagName, type: inp.type || '', name: inp.name || '',
        id: inp.id || '', placeholder: inp.placeholder || '',
        visible: inp.offsetParent !== null,
        parent_text: inp.parentElement?.textContent?.trim()?.slice(0, 100) || ''
    }));
}""")
```

### Phase 2: Network capture setup

Hook into Playwright's `request` and `response` events. **Only log XHR/fetch** — ignore images, fonts, and media:

```python
def log_request(request):
    if request.resource_type in ('xhr', 'fetch', 'preflight'):
        entry = {
            'type': 'request', 'url': request.url,
            'method': request.method, 'post_data': request.post_data,
            'headers': dict(request.headers),
        }
        network_log.append(entry)

def log_response(response):
    if response.request.resource_type in ('xhr', 'fetch', 'preflight'):
        entry = {
            'type': 'response', 'url': response.url,
            'status': response.status, 'body': response.text()[:5000]
        }
        network_log.append(entry)

page.on('request', log_request)
page.on('response', log_response)
```

### Phase 3: Form interaction

Fill fields one by one, handle dropdowns (MUI Select, native select), and submit:

```python
# Text inputs
for name, value in data.items():
    try:
        page.fill(f'[name="{name}"]', value)
    except:
        # Fallback: type character by character
        page.type(f'[name="{name}"]', value, delay=50)

# MUI Select dropdowns
page.click('#mui-component-select-genero')
page.wait_for_timeout(800)
page.click('li[data-value="Femenino"]')  # or keyboard nav

# Checkbox
page.check('[name="acceptTerms"]')

# Submit
page.click('button:has-text("Continuar")')
page.wait_for_timeout(8000)
```

### Phase 4: API emulation

Once the endpoint, payload structure, and response shape are known, replay with `requests`:

```python
import requests

# GET plan/order data (if needed)
r = requests.get(f"{BASE}//plan/{slug}")
order = r.json()

# POST form
payload = {"form": {...}, "order": order}
r = requests.post(f"{BASE}//order/{slug}", json=payload,
                  headers={"Content-Type": "application/json"})
resp = r.json()
```

## Pitfalls

- **SPA rendering delay**: Even after `networkidle`, Vue/React components may not be mounted. Add extra `wait_for_timeout(4000-8000)` and check `page.evaluate('() => document.querySelectorAll("input:not([type=hidden])").length')`.
- **Client-side validation**: Forms may silently block submission (no error XHR). Check screenshot for red outlines. Common failures: dropdown not selected, checkbox unchecked, format validation.
- **MUI Select in React**: These render as non-native inputs. Click the `#mui-component-select-*` div, wait for dropdown, then click the `li[data-value="..."]` option. Keyboard nav (`ArrowDown` + `Enter`) as fallback.
- **Double slashes in URLs**: Some backends tolerate `//` in paths (e.g., `buenclub-checkout-backend.prod.sportclub.com.ar//plan/...`). Don't normalize — replicate exactly.
- **Province/city dropdowns**: Values can be unpredictable. Use screenshot-based verification or test common values ("Ciudad Autónoma de Buenos Aires", "CABA", "Buenos Aires").
- **Google Analytics noise**: 90% of captured XHR may be GA/GTM/ads. Filter by domain: only log requests to the actual API backend, not `google*.com` or `doubleclick.net`.

## Delivery pattern for Chicho

When the user needs to interact with the captured flow from their own computer:
1. Create a minimal HTML page that auto-fetches and POSTs the API calls
2. On success, redirect to the real site's next step
3. Expose via `pipo-deploy <port> <name>` → `https://miopenclaw-vnic.tail9799d2.ts.net/preview/<name>/`

```bash
# Start server
cd /tmp/<project> && python3 -m http.server <port> --bind 127.0.0.1 &
pipo-deploy <port> <name>
```

## Reference cases

- `references/sportclub-checkout.md` — SportClub Argentina checkout API: endpoints, payload structure, DNI split technique, tested promotions (ACA 25%, BAC 50%), T&C analysis.
