Install
$ agentstack add skill-metterian-reverse-api-skill-reverse-api-skill ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Reverse-Engineer Web App Internal API
Overview
Analyze captured network requests from any web app and generate a Python httpx client with API reference documentation. Works with REST, GraphQL, Google batchexecute, gRPC-web, and other protocols.
Core principle: Capture real requests, understand the protocol, generate code that mimics the browser exactly.
When to Use
- Building programmatic access to a site with no official API
- Reverse-engineering internal API endpoints from browser network traffic
- Creating an unofficial Python client for a web app
- Analyzing captured HTTP requests to understand API patterns
Invocation
/reverse-api # Init: scaffold project + analyze first endpoint
/reverse-api add "list applications" # Add: new method to existing client
Mode Detection
digraph mode {
"Client file exists?" [shape=diamond];
"Chrome DevTools MCP available?" [shape=diamond];
"Init Mode\n(scaffold + first endpoint)" [shape=box];
"Add Mode\n(append method to client)" [shape=box];
"Auto Capture\n(navigate + get_network_request)" [shape=box];
"Paste Mode\n(user provides request data)" [shape=box];
"Client file exists?" -> "Init Mode\n(scaffold + first endpoint)" [label="no"];
"Client file exists?" -> "Add Mode\n(append method to client)" [label="yes"];
"Init Mode\n(scaffold + first endpoint)" -> "Chrome DevTools MCP available?";
"Add Mode\n(append method to client)" -> "Chrome DevTools MCP available?";
"Chrome DevTools MCP available?" -> "Auto Capture\n(navigate + get_network_request)" [label="yes"];
"Chrome DevTools MCP available?" -> "Paste Mode\n(user provides request data)" [label="no"];
}
How to detect Chrome DevTools MCP: Check if navigate_page and get_network_request tools are available in the current session.
Init Mode
When no client file exists in the project, run initialization:
Step 1: Gather site info
Ask the user:
- Target URL — the web app URL (e.g.,
https://app.greenhouse.io) - Site name — used for naming (e.g.,
greenhouse)
Step 2: Scaffold project structure
Create:
src/_client/
├── __init__.py # Package with version
├── client.py # BaseClient with auth + HTTP infra
├── constants.py # Endpoint URLs, code mappings
└── types.py # Response dataclasses
docs/
└── api_reference.md # Discovered API documentation
BaseClient template — generate client.py with:
import httpx
class BaseClient:
"""Client for internal API."""
BASE_URL = ""
def __init__(self, cookies: dict[str, str], **auth_kwargs):
self.cookies = cookies
self._client: httpx.Client | None = None
# Store additional auth params (csrf_token, api_key, etc.)
self._auth = auth_kwargs
def _get_client(self) -> httpx.Client:
if self._client is None:
self._client = httpx.Client(
cookies={k: v for k, v in self.cookies.items()},
headers={
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
"Origin": self.BASE_URL,
"Referer": f"{self.BASE_URL}/",
},
timeout=30.0,
)
return self._client
def close(self):
if self._client:
self._client.close()
self._client = None
def __enter__(self):
return self
def __exit__(self, *args):
self.close()
api_reference.md template:
# Internal API Reference
Auto-generated by reverse-api skill. Each endpoint documented as discovered.
---
Step 3: Proceed to Request Capture
After scaffolding, immediately move to capturing the first endpoint.
Request Capture
Auto Capture (Chrome DevTools MCP)
1. navigate_page(url="")
2. Tell user: "Now perform the action you want to capture (e.g., click 'List Applications')"
3. Wait for user confirmation
4. get_network_request() — capture the relevant XHR/fetch request
5. Filter: ignore static resources (.js, .css, images, fonts)
6. Extract: URL, method, headers, body, response
Paste Mode (Manual)
Tell the user:
> Open Chrome DevTools (F12) → Network tab → perform the action → right-click the API request → Copy as cURL. > > Or copy these individually: > 1. Request URL > 2. Request Method > 3. Request Headers (especially Cookie, Content-Type, Authorization, X-CSRF-Token) > 4. Request Body (if POST/PUT/PATCH) > 5. Response Body (first 2000 chars is enough)
Parse the pasted data. If user pastes a cURL command, extract all fields from it.
Protocol Analysis
After capturing a request, analyze it to determine the protocol and patterns.
Protocol Detection
| Signal | Protocol | |--------|----------| | f.req= in body, URL contains batchexecute | Google batchexecute | | /graphql in URL, or body has query + variables fields | GraphQL | | REST verbs on resource paths (GET /api/v1/users/123) | REST | | content-type: application/grpc-web+proto | gRPC-web | | content-type: application/x-www-form-urlencoded with custom structure | Form-encoded RPC | | WebSocket upgrade in headers | WebSocket |
Auth Detection
| Signal | Auth Type | |--------|-----------| | Cookie: header with session tokens | Cookie-based | | Authorization: Bearer | Bearer token | | X-CSRF-Token or X-CSRFToken header | CSRF token (usually with cookies) | | at= parameter in body | Google-style CSRF | | X-API-Key or api_key param | API key | | No auth headers, public endpoint | None / Public |
Response Format Detection
| Signal | Format | |--------|--------| | Starts with )]}'\n | XSSI-prefixed JSON (Google) | | Valid JSON directly | Standard JSON | | Chunked with byte counts between JSON arrays | Google batchexecute response | | data: prefixed lines | Server-Sent Events (SSE) | | Binary content | Protobuf / gRPC |
What to Extract
For each captured request, document:
- Endpoint — URL pattern (replace IDs with
{id}placeholders) - Method — HTTP method
- Auth — which auth mechanism
- Request params — body structure with field names and types
- Response structure — field names, types, nesting
- Pagination — if applicable (cursor, offset, page)
- Rate limiting — response headers indicating limits
Code Generation
For Each Endpoint, Generate:
1. Method in client.py:
def list_applications(self, page: int = 1, per_page: int = 50) -> list[Application]:
"""List all applications.
Discovered: 2026-04-02
Endpoint: GET /api/v1/applications
"""
client = self._get_client()
response = client.get(
f"{self.BASE_URL}/api/v1/applications",
params={"page": page, "per_page": per_page},
)
response.raise_for_status()
data = response.json()
return [Application(**item) for item in data["results"]]
2. Dataclass in types.py:
from dataclasses import dataclass
@dataclass
class Application:
id: str
candidate_name: str
status: str
# ... fields discovered from response
3. Constants in constants.py (if applicable):
# Endpoint paths
APPLICATIONS_LIST = "/api/v1/applications"
# Status code mappings
STATUS_ACTIVE = 1
STATUS_ARCHIVED = 2
4. Entry in docs/api_reference.md:
## list_applications
- **URL:** `GET /api/v1/applications`
- **Auth:** Cookie + CSRF
- **Parameters:**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| page | int | no | Page number (default: 1) |
| per_page | int | no | Results per page (default: 50) |
- **Response:**
```json
{
"results": [{"id": "...", "candidate_name": "...", "status": "..."}],
"total": 150,
"page": 1
}
```
- **Discovered:** 2026-04-02
Protocol-Specific Code Patterns
Google batchexecute:
def _call_rpc(self, rpc_id: str, params: Any) -> Any:
"""Execute a batchexecute RPC call."""
params_json = json.dumps(params, separators=(',', ':'))
f_req = [[[rpc_id, params_json, None, "generic"]]]
body = f"f.req={urllib.parse.quote(json.dumps(f_req))}"
if self._auth.get("csrf_token"):
body += f"&at={urllib.parse.quote(self._auth['csrf_token'])}"
response = self._get_client().post(self.BATCHEXECUTE_URL, content=body)
# Parse XSSI-prefixed response
text = response.text
if text.startswith(")]}'"):
text = text[4:]
return json.loads(text.strip().split('\n')[1])
GraphQL:
def _query(self, query: str, variables: dict | None = None) -> Any:
"""Execute a GraphQL query."""
payload = {"query": query}
if variables:
payload["variables"] = variables
response = self._get_client().post(
f"{self.BASE_URL}/graphql",
json=payload,
)
response.raise_for_status()
data = response.json()
if "errors" in data:
raise APIError(data["errors"])
return data["data"]
REST: Use standard httpx methods directly (get, post, put, delete).
Add Mode
When client file already exists:
- Read existing code — understand current patterns, naming conventions, auth setup
- Capture new request — same as Request Capture section
- Analyze — same as Protocol Analysis section
- Generate method — follow existing code style exactly:
- Same naming convention (snake_case, same prefix patterns)
- Same error handling pattern
- Same return type style (dataclass, dict, etc.)
- Add to existing client class, not a new one
- Update docs — append to existing api_reference.md
- Update types/constants — add new dataclasses and constants as needed
Do NOT:
- Change existing methods
- Refactor the client structure
- Change auth patterns
- Add new dependencies unless absolutely necessary
Common Mistakes
| Mistake | Fix | |---------|-----| | Hardcoding session-specific IDs in client code | Use parameters — extract IDs from URLs into method arguments | | Ignoring response cookie updates | Some sites rotate cookies per request — update cookie jar | | Missing CSRF token refresh | CSRF tokens expire — add refresh logic if site uses them | | Not URL-encoding body params | Use urllib.parse.quote() for form-encoded bodies | | Assuming JSON response | Check Content-Type and response prefix (XSSI, SSE, etc.) | | Copying all headers from capture | Only include required headers — User-Agent, Origin, Referer, Auth | | Not handling pagination | Always check if response indicates more pages |
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: metterian
- Source: metterian/reverse-api-skill
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.