Key takeaways
- An API has no screen to bound it, so the endpoint inventory is the scope. An API pentest delivers the inventory as its first output rather than taking a specification on trust.
- No single source is complete. The specification, the gateway configuration, client traffic, compiled bundles, access logs and version probing each miss something another one shows, and the method reconciles all six.
- Every line in the reconciled list records its sources, the roles expected to call it and the highest classification of any field it returns. That classification sets the order the endpoints are tested in.
- An earlier version of an endpoint left routable behind a patched one is the OWASP API category of improper inventory management. It is found by probing, not by reading, so probing is a listed step of the method.
- The closed inventory becomes the rows of the authorisation grid: endpoints against roles, a result in every cell. The retest starts by rebuilding the list, because an endpoint that appeared since is the same finding one release later.
The inventory is the scope, and the specification is only a claim about it
A web application pentest starts from a hostname and a browser. An API pentest starts from a list, because nothing else bounds it. There is no page to click through, no menu that says what exists, and no reason to assume the software calling the endpoints uses all of them. Both earlier articles in this series arrived at the same place. What UAE regulators expect from an API pentest said an examiner reads the evidence pack from the inventory down, and the article on tokens and rate limits assumed one existed. This article is about how that list is built, because the method is what decides whether the test covered the API or only the part of it somebody wrote down.
The specification is the obvious starting point and the wrong stopping point. An OpenAPI document is a claim about what the API does, written by the people who built it, at the time they wrote it. Traffic is a record of what the API accepts. On an estate of any age the two disagree, and the endpoints in the gap are the ones no reviewer has read, no threat model has named and no scanner has been pointed at. An API pentest treats the inventory as its first deliverable rather than as an input, and the rest of the engagement runs from it.
Six places an endpoint can be found, and what each one misses
No single source is complete, and each fails in a way another one covers. The specification omits what was never documented. The gateway configuration omits anything reachable around the gateway. The mobile client only calls what the current release needs. Access logs only show what somebody has called. The table sets out the six sources the method draws on and the blind spot each carries, which is the reason the reconciliation step exists at all.
Two of them deserve a note. The mobile client is the richest source where the API was built for the app, because the app is the reference implementation of the API, and capturing its traffic from a device is the same first step a mobile app pentest takes. Gateway configuration is the most authoritative source when it can be had, and the one to ask for in advance: it is the list of what the gateway will route, whether or not anything documents it, and it belongs on the list of what to have ready before a penetration test starts. A test that waits until day two to ask for it loses day two.
| Source | What it records | What it misses |
|---|---|---|
| OpenAPI or similar specification | What the developers intended to expose, with parameters and schemas | Anything added, renamed or left undocumented since it was written |
| Gateway or router configuration | Every path the gateway will forward, and the service behind each | Services reachable without passing through the gateway |
| Mobile and web client traffic | What the current release actually calls, with real tokens and payloads | Endpoints the client no longer uses, and administrative functions it never had |
| Front-end and mobile bundles | Paths and parameters compiled into the client, including unused ones | Server-side endpoints called only by other services |
| Access logs over a period | What anyone called, including partners and scripts, and how often | Endpoints nobody has called during the period |
| Version and path probing | Older versions, export routes and debug paths still answering | Anything the probe did not guess; it confirms rather than enumerates |
Reconciling six lists into one, and classifying every line
The reconciliation is mechanical, and it should be, because the outcome has to be repeatable when the retest runs. Every endpoint from every source is normalised to a method and a path template, with identifiers replaced by a placeholder, and the sources that produced it are recorded against it. An endpoint present in the specification and in traffic is confirmed. One in traffic and not in the specification is undocumented, and goes in scope with a note saying so. One in the specification and not in traffic is either unused or called by a client the test does not hold, and the client is asked which. That question is how a partner integration nobody named at scoping gets named.
Classification is the second pass, and it is what turns a list into a scope. Each line records the roles that should be able to call it, the object it acts on, and the highest classification of any field it returns. The article on tokens, rate limits and logs set out why that classification matters to an examiner. Here it decides order. An endpoint that returns a clinical record or an account balance is tested before one that returns a list of currencies, and the method has to say so rather than leave it to the tester's day.
Written generically, as an example, two lines of the reconciled list read as follows. Get account by identifier, version three; sources: specification, mobile traffic, gateway; roles expected: the customer owning the account, operations; returns: balance, transactions, personal data. Get account by identifier, version one; sources: gateway only; roles expected: unknown; returns: to be established. The second line is the one the next section is about.
Collect
Specification
What was intended, at the time it was written
Gateway routes
What will be forwarded, documented or not
Client traffic
What the current release actually calls
Logs and bundles
What anyone called, and what was compiled in
Reconcile
Normalise to method and path
Identifiers become placeholders
Mark the sources per line
Confirmed, undocumented, or unused
Question the single-source lines
Which client calls this, and is it still needed
Classify
Roles expected per endpoint
Who should be able to call it, and for which object
Highest field returned
Sets the test order and the bar the examiner reads
The version nobody retired is the endpoint that gets you
The OWASP API Security Top 10 gives this failure a category of its own, improper inventory management, and the reason it earns one is that the flaw is not in any endpoint's code. Version three of an endpoint checks that the account belongs to the caller because somebody found the gap and fixed it. Version one, deployed earlier and left running for a client since retired, was never patched because nobody was tracking that it existed. The gateway still routes it. The mobile app never calls it, so client traffic never shows it. The specification describes version three. Only the gateway configuration, or a probe of the obvious neighbours, ever lists it.
The chain below is the general shape. Every request is well-formed and every response is a normal success, so nothing in it appears in a scanner's output, and the fix is a route removed rather than a line of code changed. The method's answer is that version and path probing is a listed step and not a tester's habit. Every confirmed endpoint is re-requested under each earlier version prefix the gateway answers for, and each path is tried with the export, bulk and debug variants the framework's conventions suggest. Whatever answers goes into the inventory with its source marked as probe, and it is tested to the same standard as the documented line beside it. The role-by-role method set out for web applications in how to test authorisation role by role then applies to both.
- Attacker → API gateway
Captures the mobile app calling accounts, version three
Learns the path template and the identifier shape
- Attacker → API gateway
Requests the same path under the version one prefix
Same token, same identifier, one prefix changed
- API gateway
Vulnerability
Routes it to a service that predates the ownership check
Documented nowhere, retired never
- API gateway → Account records
Reads the account the identifier names
- API gateway → Attacker
Objective reached
Returns another customer's account as a normal success
Version three would have refused it
From the inventory to the authorisation grid
Once the list is closed the test runs from it, and the shape of the work is a grid: endpoints down the side, roles across the top, and in each cell what the endpoint answered when that role's token asked for an object the role does not own. The inventory sets the rows, the roles column from classification sets the expected answer in each cell, and the tester's job is to fill every cell rather than sample the interesting ones. A grid with a result in every cell is what the first article in this series described as the evidence an examiner reads; the inventory is what makes it finite. Seventy endpoints and four roles is two hundred and eighty cells before a single object identifier is varied, and a proposal that quotes the test by hostname rather than by endpoint count has not read the inventory.
Two framing documents fix where this sits in a method. NIST SP 800-115 puts discovery ahead of the attack phase and treats what discovery finds as the bound on everything after it. The OWASP Web Security Testing Guide treats information gathering the same way, and it is the reference the authorisation tests are written against. Neither is specific to APIs and neither needs to be. What is specific to an API is that the discovery phase produces the scope rather than confirming it, which is why the inventory is delivered as a document in its own right, dated, with its sources, and why the retest starts by rebuilding it. An endpoint that appeared between the test and the retest is the same finding as the one in the section above, one release later. How much of the estate goes into the list in the first place is settled the way how to scope a penetration test describes, except that for an API the unit of scope is the endpoint and not the host.
Building the list for a Dubai open finance API and an Abu Dhabi exchange integration
In Dubai the inventory question takes two shapes. For a bank or payment institution licensed by CBUAE, the open finance endpoints are described by the framework's own standards, so the specification for that part of the estate is unusually good and the gap is elsewhere: in the institution's own mobile and partner endpoints that sit on the same gateway and were never part of the framework's list. The inventory has to cover both, because a token issued under the framework is tested against everything the gateway routes, not only the endpoints the framework describes. For a supplier integrating with a Dubai government entity under DESC, the split is different. The entity holds the gateway configuration and the supplier holds the code, and the inventory cannot be closed until both have contributed. A free-zone supplier with no sector regulator of its own is still the party asked for the report, so it is the party that has to ask the entity for its side of the list.
In Abu Dhabi the exchange integrations under ADHICS are defined by the exchange, which means the interface specification is authoritative for the exchange's side and says nothing about the vendor's own endpoints around it: the portal, the mobile application and the reporting exports. Those are where retired versions live. Across the United Arab Emirates the examiners differ and the classification that sets the bar differs, but the first question is the same in each: where did the endpoint list come from, and what did the test do about the lines found in only one source. A web application pentest of the portal in front of these endpoints does not answer it. Only an inventory built the way this article describes does.
Frequently asked questions
How does an API pentest find undocumented endpoints?
By building the inventory from more than the specification. The test collects the gateway or router configuration, captures traffic from the mobile and web clients, reads the paths compiled into the client bundles, reviews access logs over a period, and probes each confirmed endpoint under earlier version prefixes and common variants. Anything found in one source and not the specification is recorded as undocumented and tested to the same standard as the documented endpoints.
Is an OpenAPI specification enough to scope an API pentest?
No. A specification is a claim about what the API does, written at the time it was documented, and it omits anything added, renamed or never written down since. It is the starting point for the inventory, not the scope. A test scoped to the specification alone has not covered the endpoints most likely to be missing an authorisation check, because those are the ones nobody reviewed.
What is improper inventory management in the OWASP API Security Top 10?
It is the category for endpoints that are exposed without anyone tracking that they exist: older API versions left routable behind a patched one, debug and export routes, and environments that should not be reachable. The flaw is usually not in the code of any single endpoint but in the fact that an unpatched version still answers. It is found by probing the gateway rather than by reading the specification.
How many endpoints does an API pentest cover?
Every endpoint in the reconciled inventory, called with every role's token. The work is a grid of endpoints against roles with a result in each cell, so the size of the test is set by the endpoint count and the number of roles, not by the hostname. A proposal for an API pentest should be priced from the inventory, and the inventory should be delivered as a dated document with its sources so the retest can rebuild it.






