Reverse-Engineering an API
Reverse-Engineering an API
The Workflow at a Glance
Step 1 — Set Up the Network Panel
Four settings matter before you start: Disable Cache (forces fresh requests), Persist Logs (survives page navigations), XHR/Fetch filter (removes HTML/CSS/image noise), and Clear the log so you start from a clean slate.
Step 2 — Trigger Actions and Observe
Click through the application performing the action you want to understand. Then find the relevant request using these techniques:
| Technique | How | Best for |
|---|---|---|
| URL text filter | Type users or api in the filter box | When you know part of the endpoint path |
| Timing sort | Click Waterfall column → sort by start time | Finding the request that fired right after your click |
| Response body search | Ctrl + F → type a value from the UI | Finding which endpoint returned a specific username, price, or ID |
| Method filter | Click the method column to sort; use filter box POST | Isolating write operations from reads |
Step 3 — Read and Document the Request
Anatomy of the URL
Request detail tabs
meta block above tells you: 847 total items, 20 per page, 43 pages total. To fetch all items, loop page from 1 to 43. You also know the exact field names (id, name, price) to use in your script.
Step 4 — Replay the Request
Copy as cURL
Right-click any request → Copy as cURL. Pastes a complete terminal command with all headers. Modify query params and run it directly.
Console fetch()
Replay directly in the browser. Session cookies are included automatically — no need to copy auth headers for cookie-based APIs.
Edit and Resend
Right-click any request → Edit and Resend. Change URL, headers, or body in a panel, click Send — no code needed.
Copy as cURL output
# Copied directly from Firefox — modify page=1 to iterate curl 'https://api.example.com/v2/products?category=electronics&page=1&limit=20' \ -H 'Authorization: Bearer eyJhbGci…' \ -H 'Content-Type: application/json' \ -H 'X-Client-Version: 3.2.1' \ --compressed
Console replay — GET with Bearer token
const token = localStorage.getItem('access_token'); const r = await fetch('https://api.example.com/v2/products?page=2&limit=20', { headers: { 'Authorization': `Bearer ${token}` } }); const data = await r.json(); console.table(data.data); // renders the array as a sortable table
Console replay — POST request
const token = localStorage.getItem('access_token'); const r = await fetch('https://api.example.com/v2/products', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Test Product', price: 9.99, category_id: 7 }) }); const result = await r.json(); console.log('Created:', result);
Step 5 — Identify Patterns
URL naming conventions
| Pattern | Style | Meaning |
|---|---|---|
/api/v1/users | REST | Resource list — GET=list, POST=create |
/api/v1/users/42 | REST | Single resource — GET=read, PUT=update, DELETE=delete |
/api/v1/users/42/orders | REST nested | Orders belonging to user 42 |
/graphql | GraphQL | Single endpoint — all operations in the POST body |
/rpc/getUsers | RPC | Operation name in the URL path |
?action=listProducts | Old-style RPC | Operation name hidden in query params |
Pagination patterns
Page-based
?page=1&per_page=20
Most common. Predictable — iterate page 1 to last_page.
Offset-based
?offset=0&limit=20
Same as page-based in absolute terms. offset = (page-1) × limit.
Cursor-based
?cursor=eyJpZCI6NDJ9
Copy cursor from response into the next request. Used for large/real-time datasets.
Token-based
next_page_token
Similar to cursor. Common in Google APIs. Token is opaque — just pass it through.
Step 6 — Find Source Clues in the Debugger
The Network panel shows what was sent. The Debugger shows why — the code that builds the request. This reveals all possible parameters, not just the ones the UI exposes.
Using the Initiator column
In the Network panel, click the Initiator column for any request. It shows the JavaScript file and line that called fetch(). Click it — Firefox opens the Debugger at that exact line. Read the function to discover every parameter it can accept.
// What you find at the call site — reveals hidden parameters async function loadProducts(filters) { const params = new URLSearchParams({ page: filters.page || 1, limit: filters.pageSize || 20, category: filters.category, sort: filters.sortField + '_' + filters.sortOrder, // "price_asc" in_stock: filters.showInStockOnly ? 'true' : undefined }); return fetch(`/api/v2/products?${params}`, { headers: { 'Authorization': `Bearer ${getToken()}` } }); }
From reading this function you know: sort takes the format field_direction (e.g. price_asc, name_desc), and in_stock=true is a valid filter — information that the UI may never expose but the API fully supports.
Building an API Map
As you observe requests, document them. This is the deliverable — a reference you can use to write scripts, build integrations, or generate OpenAPI spec.
Exercises
data or results key?
Ctrl + F in the Network panel. Type a value you can see in the UI (a username, a price, a product name). Which request highlighted? Is it the one you expected?
page=1 or offset=0). Replay it in the Console changing the page:
const r = await fetch('URL_WITH_PAGE=2'); const d = await r.json(); console.table(d.data || d);Is the response a different set of items?
fetch() call? What function is making the request? Are there any parameters it accepts that the UI never exposes?
const pages = await Promise.all( [1,2,3].map(p => fetch(`/api/items?page=${p}&limit=20`) .then(r => r.json()) .then(d => d.data) ) ); console.log('Fetched:', pages.flat().length, 'items');
X-RateLimit-Remaining header? If so, what is the limit and how many requests have you used? What happens when it reaches zero?
Key Takeaways
- Filter to XHR/Fetch — removes HTML/CSS/image noise and shows only API calls.
- Ctrl + F in the Network panel searches response bodies — the fastest way to find which endpoint returned a specific value.
- Every request has four tabs: Headers (URL, method, auth), Request (body for writes), Response (data shape), Timings (performance).
- Copy as cURL (right-click) gives you an instantly runnable command with all headers — paste into a terminal or import into Postman.
- Console fetch() is faster for iteration — session cookies are included automatically for cookie-authenticated APIs.
- Edit and Resend (right-click) modifies and re-fires a request without writing any code.
- The Initiator column links to the JavaScript that built the request — click it to find hidden parameters in the source.
- Pagination is discoverable from the response
metablock — look fortotal,last_page,cursor, ornext_page_token. - Rate limit headers (
X-RateLimit-Remaining) tell you how many requests remain — read them before writing loops. - Use responsibly — reverse-engineering your own app's API is normal engineering. Doing it at scale against services you don't own may violate their Terms of Service.
Course Complete
You have worked through all 11 lessons. The scenario lessons (8–11) are designed to be revisited whenever you encounter those real-world situations. The panel lessons (2–7) are reference material — return to them when you need a reminder of what a specific tool can do.