HackproofHacks
Penetration Testing 16 min read

API Reconnaissance: How to Map an API Before You Test It

A hands-on API reconnaissance guide with real commands: pull endpoints out of JavaScript, find Swagger and OpenAPI docs, run GraphQL introspection, mine old versions, and map the whole API surface before you test it.

Hassan Ansari

Hassan Ansari

· Updated Jul 8, 2026
A code card showing an API path extracted from a JavaScript bundle with curl and grep

API Reconnaissance: Mapping an API Before You Test It

Modern applications are APIs with a user interface bolted on top. Behind every web and mobile app is a pile of API endpoints doing the real work: authenticating users, moving data, taking payments. That makes the API the true attack surface, and it makes API reconnaissance one of the most valuable skills you can build in security testing.

The principle is the same one that drives all good reconnaissance: you cannot test what you have not found. So before you touch a single vulnerability, you map the API. Its hosts, its endpoints, its versions, its docs, and how it decides who you are. Do that well and the testing that follows covers the whole surface instead of poking at the ten endpoints the UI happens to call.

This is the part beginners skip, and it is exactly why two testers can look at the same app and walk away with completely different results. The difference is almost never a cleverer exploit. It is better recon.

Before you run anything in here: reading a site’s own JavaScript, its public docs, and web archives is passive and low risk. The moment you send requests at a target to enumerate or probe it, that is active testing and it needs authorisation. A bug bounty scope or a signed engagement is what makes it legal. Finding an endpoint is not permission to attack it. Everything below assumes you are working inside a scope you are allowed to touch, or against a lab you own.

Why recon decides the whole engagement

The front-end app you can see might call twenty endpoints. The API behind it might expose two hundred. Old versions, admin functions, internal operations, endpoints for features that were pulled from the UI but never from the server. The tester who only tests the twenty visible endpoints misses the ninety percent that nobody is looking after, which is precisely where the bugs live.

So my goal in recon is simple: build the most complete list of endpoints I can, from as many independent sources as possible, before I start testing any of them. Let me walk through the sources I actually use, with the commands I actually run.

Step 1: Find where the API lives

APIs often sit on their own hosts, so start by finding them. Look for subdomains that smell like an API:

subfinder -d target.com -silent | grep -Ei 'api|graphql|gateway|rest|mobile'

This is a direct application of subdomain enumeration. If you would rather not set up a local toolchain yet, run the domain through the free Subdomain Finder tool and pay attention to anything API-flavoured.

APIs also live on paths under the main host, so check the usual suspects and note the status codes:

for path in api api/v1 api/v2 rest graphql v1 v2; do
  echo -n "/$path -> "
  curl -s -o /dev/null -w "%{http_code}\n" "https://target.com/$path"
done

A 401 or 403 here is not a dead end. It means something is there and it wants a credential. That is a live host worth mapping. While you are at it, fingerprint each API host you find with the HTTP Header Analyzer and check its TLS with the SSL/TLS Checker. Sloppy headers on a host are a strong hint the rest of it was set up carelessly too.

Step 2: How to find hidden API endpoints in the JavaScript

This is the single richest source of endpoints, and most people underuse it. A single-page app has to know every URL it calls, so those URLs are sitting right there in the JavaScript it ships to your browser. You just have to read it.

First, pull the list of script files the page loads:

curl -s https://target.com | grep -oE 'src="[^"]+\.js"' | cut -d'"' -f2

Then grep each bundle for anything path-shaped:

curl -s https://target.com/assets/index-8f2a1c.js \
  | grep -oE '"(/api/|/v[0-9]+/)[a-zA-Z0-9_/{}.-]+"' \
  | sort -u

You will get output like this, and every line is a lead:

"/api/v1/user/profile"
"/api/v1/user/{id}/orders"
"/api/v1/admin/users"
"/api/internal/feature-flags"
"/api/v2/payments/refund"

Notice what that gave you for free: an /admin/ route, an /internal/ route, and a v2 payments/refund the UI may never call. None of those are things you would have found by clicking around.

For big minified bundles, hand-grepping gets old. Dedicated tools do a more thorough job of pulling paths and parameter names out of JavaScript. LinkFinder and xnLinkFinder are the two I reach for, and katana will crawl a site and harvest its JS for you:

katana -u https://target.com -jc -silent | grep -Ei '/api/|/v[0-9]+/'

The grep and filtering habits from the command-line tools guide are exactly what carve the signal out of these bundles.

Step 3: Look for the docs (Swagger and OpenAPI)

Developers document their APIs, and that documentation is exposed far more often than it should be. When you find it, it hands you a near-complete map: every endpoint, every parameter, every response shape. Check the common paths:

for p in api/docs swagger swagger.json swagger/v1/swagger.json \
         openapi.json openapi.yaml api-docs v1/api-docs \
         .well-known/openapi redoc graphql; do
  echo -n "/$p -> "
  curl -s -o /dev/null -w "%{http_code}\n" "https://target.com/$p"
done

Anything that returns 200 is worth opening. If a spec comes back as JSON, jq turns it straight into a clean endpoint list:

curl -s https://target.com/openapi.json | jq -r '.paths | keys[]'

One tip from real engagements: a docs path that returns 404 when you are logged out is worth trying again with a valid session token. Plenty of teams hide Swagger behind auth rather than removing it, so an authenticated request often gets you the whole map anyway.

Step 4: Run GraphQL introspection

If the API is GraphQL, there is a specific check that can hand you the entire schema in one request. GraphQL has a built-in feature called introspection that lets a client ask the API to describe itself. In plain terms: you can ask a GraphQL endpoint “what can you do?” and a lot of them will just tell you.

Check whether it is enabled with a tiny query:

curl -s -X POST https://target.com/graphql \
  -H "Content-Type: application/json" \
  -d '{"query":"{__schema{queryType{name}}}"}'

If you get a schema object back instead of an error, introspection is on. Now pull the full type and field list:

curl -s -X POST https://target.com/graphql \
  -H "Content-Type: application/json" \
  -d '{"query":"{__schema{types{name fields{name}}}}"}' | jq

That gives you every query, mutation, and field the API supports, including operations the front end never touches. Tools like graphql-voyager or clairvoyance will visualise the schema for you, and clairvoyance can even reconstruct a lot of it when introspection is disabled. Always check this first on any GraphQL endpoint. It is the fastest map you will ever get.

Step 5: Mine the API’s history

Websites change, but their old endpoints rarely get properly removed. Web archives remember the paths a domain has served over the years, and a surprising number of those old API routes are still live and less protected than the current ones. This pairs directly with the waybackurls technique. Pull the history and filter it down to API paths:

echo target.com | gau --subs | grep -E '/api/|/v[0-9]+/' | sort -u > api-history.txt

gau (get all URLs) queries several archive providers at once. Read the result looking for versioned paths and parameters that no longer appear on the live site. An old /api/v1/ route that still answers, next to a shiny hardened /api/v3/, is one of the most reliable findings in the whole game.

Step 6: Fill the gaps with ffuf, then expand the map

Passive sources only know about endpoints that showed up somewhere public. Some never did. To find those, brute force API paths with a wordlist, using an API-specific list rather than a generic directory list:

ffuf -w /usr/share/seclists/Discovery/Web-Content/api/api-endpoints.txt \
     -u https://api.target.com/v1/FUZZ \
     -mc 200,201,401,403 -fc 404

The full filtering workflow lives in the ffuf guide, and it applies here too: run once, see what a “nothing” response looks like, filter it out, then read what is left.

Once you have a baseline map, expand it three ways.

Check old versions. If you found /api/v3/, test whether /api/v1/ and /api/v2/ still answer. Deprecated versions are routinely left running with weaker protections than the current one.

Reason about siblings. Real APIs follow naming conventions. If /api/v2/users exists, try /api/v2/admin/users, /api/v2/users/export, and related resources. The endpoints you find by understanding the pattern are often the ones the UI never exposes.

Test every HTTP method. An endpoint might do far more than the UI uses. If the app only ever sends GET /api/v2/orders/1044, check the rest yourself:

for m in GET POST PUT PATCH DELETE OPTIONS; do
  echo -n "$m -> "
  curl -s -o /dev/null -w "%{http_code}\n" -X $m https://api.target.com/v2/orders/1044
done

A DELETE or PUT that answers where the app only ever does GET is exactly the kind of forgotten, less-protected behaviour that turns into a finding.

Step 7: Understand the authentication

Before you test anything, work out how the API decides who you are, because it shapes every attack that follows. What carries identity: a session cookie, a bearer token, an API key, a JWT? Where does it go, a header or a cookie? How are roles encoded? And which endpoints work with no credential at all? Those unauthenticated endpoints deserve close attention. Understanding the auth is what lets you later test whether it is actually enforced, which is the root of every access-control bug.

A worked map: recon on a lab API

Techniques stick when you run them end to end, so set up a deliberately vulnerable API you own and map it for real. Two good, completely legal targets are OWASP crAPI and VAmPI, both of which you run locally with Docker. Here is the shape of it on VAmPI.

Spin it up, then go looking for the spec first, because a vulnerable-by-design API usually ships its docs wide open:

curl -s http://localhost:5000/openapi.json | jq -r '.paths | keys[]'
/createdb
/users/v1
/users/v1/_debug
/users/v1/login
/users/v1/register
/users/v1/{username}
/books/v1
/books/v1/{title}
/me

Look at what that list is telling you before you send another request. There is a /users/v1/_debug endpoint that no normal user should ever hit. There is /users/v1/{username}, an object addressed by an identifier, which is a textbook IDOR and broken object level authorisation candidate. There is /createdb, a state-changing endpoint sitting on a GET.

You have not attacked anything yet. You have just read the map, and the map already told you where the three most interesting doors are. That is the entire point of recon: by the time you start testing, you know exactly where to push.

From recon to testing

A good map feeds straight into the highest-value API tests. Broken object level authorisation, where you reach another user’s object by changing an ID. Broken function level authorisation, where a normal user hits those forgotten admin routes. Mass assignment, where you send an extra field like "role":"admin" and the API binds it without checking. Excessive data exposure, where an endpoint returns more than the UI shows. Every one of those is far easier to find comprehensively when your recon revealed the full endpoint set first. The complete methodology for testing them lives in the API security testing guide.

Key takeaways

  • APIs are the real attack surface of modern apps, and how much of it you actually test comes down to your recon.
  • Find the hosts first (subdomains and paths), then map endpoints from the richest sources: front-end JavaScript, Swagger and OpenAPI docs, GraphQL introspection, and archived URLs.
  • Fill the gaps with ffuf, then expand by checking old versions, reasoning about sibling paths, and testing every HTTP method.
  • Understand the authentication before you test, because it shapes every access-control attack that follows.
  • Keep passive recon passive, and only send active requests at targets you are authorised to test.

Great API testing is built on great API recon. Map the surface completely and the vulnerabilities other people walk straight past become the ones you find. When you are ready to test what you have mapped, continue with the API security testing guide. If you would rather build this whole skill set with guided, hands-on practice, that is what our training is for, and if you want your own API mapped and tested properly, that is what our security assessments do.

#API security #recon #reconnaissance #bug bounty #penetration testing #GraphQL
Free newsletter

Liked this? I write one like it every week.

One practical security lesson in your inbox each week, explained the same simple way. Join 10,000+ readers. Unsubscribe anytime.

From the article

Need a security assessment?

HackproofHacks provides web application and API penetration testing — using the same techniques covered in this article, with your explicit authorisation.

Book a free scoping call

More on Penetration Testing.

All articles →
FAQ

Questions about this topic.

What is API reconnaissance?

API reconnaissance is the process of discovering and mapping an application's APIs before you test them. You find the API hosts, endpoints, versions, documentation, parameters, and authentication so your testing covers the whole surface instead of the handful of endpoints the UI happens to use. You cannot test an endpoint you never found, so the quality of your recon largely decides how thorough your testing can be.

How do you find hidden API endpoints?

Combine several sources, because each one reveals things the others miss. Read the front-end JavaScript, which usually references every endpoint the app calls. Look for Swagger or OpenAPI documents and GraphQL introspection. Mine historical URLs from web archives with tools like gau and waybackurls. Brute force common API paths with ffuf. Proxy a mobile app and watch its traffic. Together these usually surface far more of the API than any single method.

How do you extract API routes from JavaScript?

Single-page apps bundle their API paths straight into the JavaScript that ships to every browser. Download the bundle and grep it for path-shaped strings. A quick starting point is curl the script and pipe it through grep for anything that looks like /api/ or a versioned path, then sort and dedupe. For large or minified bundles, dedicated tools like LinkFinder and xnLinkFinder do a more thorough job of pulling out endpoints and parameter names.

What is GraphQL introspection and why does it matter for recon?

GraphQL introspection is a built-in feature that lets a client ask a GraphQL API to describe its own schema: every type, query, mutation, and field it supports. For a tester an API with introspection enabled is a gift, because it hands over a complete map of the API surface, including operations that are not referenced anywhere in the front end. Many teams disable it in production for exactly that reason, but plenty leave it on, so it is one of the first things to check on any GraphQL endpoint.

Why are older API versions a security risk?

When teams ship a new API version they often leave the old one running for backward compatibility, and those older versions tend to receive less security attention. An endpoint that was hardened in v3 might still be reachable and vulnerable in v1, missing the access-control checks, rate limits, or input validation added later. Because they get forgotten, deprecated API versions are a classic source of findings. Always check whether v1 or v2 are still live when a newer version exists.

Is API reconnaissance legal?

Passive recon that uses public information, like reading a site's own JavaScript, checking public documentation, or reviewing archived pages, is generally low risk. But any active interaction with the target's API, including sending requests to enumerate endpoints or probe authentication, must stay inside assets you are explicitly authorised to test, such as a bug bounty program's scope or a signed penetration testing agreement. Discovering an endpoint does not authorise you to attack it. Confirm it is in scope first.