API penetration testing is treated by many providers as web application penetration testing with a slightly different output format. It is not. Web app pentesting evolved around browser-rendered surfaces with a human user in the loop; API pentesting concerns machine-to-machine interfaces that often have no UI, no rate-limit forgiveness, and authorisation logic that is structurally different from the per-page checks an app does for a logged-in user. The findings shift accordingly — broken object-level authorisation, mass-assignment, rate-limit absence, and business-logic abuse dominate, while the classic injection and XSS findings sit lower in the count. After running API engagements as API penetration testing engagements across many products, a consistent ten-phase methodology produces the densest finding-per-hour ratio. This is that playbook — phase by phase, with the tooling, the techniques, and the failure modes pentesters hit when they skip steps.
Why API pentest methodology differs from web app pentest methodology
The framing difference matters because it changes what gets tested and what gets missed.
Web app pentest assumes a user
A web application penetration test is built around the workflow of a human user clicking through a UI. The tester observes what the browser renders, interacts via Burp, and surfaces vulnerabilities at the points where the UI hands data to the server. Tools like the Burp Suite vs OWASP ZAP ecosystem are optimised for this flow.
API pentest assumes a machine
An API penetration test has no human in the loop. The “client” is another service, a CI pipeline, a mobile app, or a third-party integration. The tester interacts directly with the API surface — without rendered context, without UI-layer input validation hiding broken server logic, and often without the rate-limit forgiveness the web app gives a human. The vulnerability classes that surface most reliably are the ones the UI was hiding: BOLA, mass assignment, broken function-level authorisation, business logic abuse.
What this means for the test plan
Three things change in the methodology:
- Discovery dominates Phase 1. Documented endpoints are a fraction of the real surface; undocumented endpoints, internal endpoints, and version-skewed endpoints often hold the most critical findings.
- Authorisation testing is the deepest phase. Object-level and function-level authorisation flaws in APIs typically eclipse injection findings by volume.
- Business logic and abuse cases get explicit dedicated time. APIs are abused at the logic layer (race conditions, sequence bypass, quota bypass) far more than at the protocol layer.
The methodology below reflects these emphases.
Phase 0 — pre-engagement scoping
Skipped phases produce ambiguous reports. Phase 0 prevents the most common engagement failures.
| Item | What to confirm | Why it matters |
|---|---|---|
| API inventory | List of every API in scope, base URL, version, business owner | Avoids surprise discoveries of out-of-scope APIs during testing |
| Authentication mechanism | OAuth, API key, JWT, mTLS, session cookies, federated identity | Determines test plan for Phase 2 |
| Test accounts | Two or more accounts at every privilege level for authz testing | BOLA/BFLA testing is impossible without horizontal accounts |
| Rate-limit policy | Throttle thresholds, IP allow-list for testing, account that gets exempted | Avoids inadvertently triggering customer-impacting throttles |
| OOB testing allowance | Permission for SSRF probes, Collaborator-style callbacks | SSRF findings are missed without OOB tooling |
| Data classification | Categories of data the API serves (PII, PCI, PHI, internal-only) | Calibrates report severity language |
| Production vs staging | Which environment the test runs against, with explicit risk acceptance | Some findings only reproduce on production scale |
A pre-engagement document covering these items is the difference between an engagement that ships findings and one that gets stuck in dispute over scope. The API penetration testing service walks through the pre-engagement checklist used in practice.
Phase 1 — discovery and surface mapping
Discovery is where the highest-value findings live and where most tests are insufficient. The pattern: testers receive an OpenAPI spec, test against that spec, and miss every endpoint that wasn’t documented.
Sources of API surface
- OpenAPI / Swagger documents: the easy starting point. Often incomplete or out of date.
- Postman collections: developer-facing collections frequently reveal internal endpoints not in public docs.
- GraphQL introspection: if introspection is enabled, the entire schema is queryable.
- Mobile app reverse engineering: APIs called by the mobile app are often a superset of the documented web app API.
- JavaScript file inspection: SPAs frequently reveal endpoint URLs in their bundled JS.
- Source code if available: Git history, particularly old commits, often reveals deprecated endpoints still live.
- Web archive (Wayback Machine): old API documentation pages sometimes reveal endpoints that still work.
Endpoint enumeration
Once the known surface is mapped, enumerate:
# Common API path patterns
ffuf -u https://api.example.com/FUZZ -w api-endpoints.txt -mc 200,401,403
# Versioned endpoints
ffuf -u https://api.example.com/vFUZZ/users -w numbers.txt -mc 200,401,403
# Internal-versus-external endpoint disclosure via verb
for verb in GET POST PUT PATCH DELETE OPTIONS; do
curl -sX $verb https://api.example.com/admin/users -o /dev/null -w "$verb %{http_code}\n"
done
Look for:
- Endpoints returning 401/403 (exist, require auth) versus 404 (don’t exist) — the difference reveals hidden surface
- OPTIONS responses revealing supported methods
- Old version paths still active (
/v1/after/v2/was launched) - Trailing-slash or query-string variants behaving differently
- Same endpoint exposed at multiple subdomains (api.example.com, internal-api.example.com)
The discovery output is the input to every subsequent phase. Investing time here pays compounding returns.
Phase 2 — authentication and session testing
Authentication flaws are protocol-level; session flaws are stateful. Both have well-defined test surfaces.
Authentication tests
| Test | What to check |
|---|---|
| Token forgery via JWT manipulation | alg: none, algorithm confusion, kid injection (see OAuth 2.0 misconfigurations) |
| Token validation across environments | Token from staging accepted on production (audience claim missing) |
| API key in URL | Keys in query string get logged in browser history, server logs, referrer headers |
| API key entropy | Sequential or guessable key format |
| Auth bypass via header injection | X-Forwarded-For, X-Original-URL, X-Rewrite-URL accepted blindly |
| Auth bypass via method override | X-HTTP-Method-Override: GET on a POST-only endpoint |
| Multiple auth mechanisms | Both API key AND JWT accepted — does one bypass restrictions on the other |
| Anonymous access | Unauthenticated endpoints inside an “authenticated API” |
| Pre-authenticated endpoints | Endpoints accessible with expired tokens; token revocation not enforced |
Session tests
| Test | What to check |
|---|---|
| Session token rotation | Login does not invalidate prior session |
| Session token format | Sequential or guessable session IDs |
| Concurrent session policy | Same user logged in multiple times with same token — is this intended |
| Logout behaviour | Logout invalidates server-side session, not just clears client |
| Inactivity timeout | Long-idle sessions still accepted |
| Cookie attributes | Secure, HttpOnly, SameSite set appropriately |
JWT-specific testing uses jwt_tool:
jwt_tool eyJhbGciOiJSUzI1NiI... -T # tampering interface
jwt_tool eyJhbGciOiJSUzI1NiI... -X a # alg=none attack
jwt_tool eyJhbGciOiJSUzI1NiI... -X k -kid "../../../etc/passwd" # kid injection
Phase 3 — authorisation (BOLA, BFLA, scope abuse)
This is the densest finding category in modern API engagements. The 2023 OWASP API Security Top 10 placed BOLA at #1 for a reason.
Broken Object-Level Authorisation (BOLA)
The classic API authorisation bug: the API receives an object ID and returns data without checking whether the authenticated user is permitted to access that object.
Test methodology:
- As User A, perform a normal action — e.g., retrieve “your” order:
GET /orders/12345. - Note the object ID format (numeric, UUID, base64-encoded, opaque token).
- Substitute another known object ID — User B’s order ID, if you have it from the second test account.
- Submit the modified request.
- Expect 403. Observe whether you actually get 200.
Variations that catch teams who think they’ve handled the basic case:
- IDOR in URL path (
/orders/12345) - IDOR in request body (
{"order_id": 12345}) - IDOR in query string (
/orders?id=12345) - IDOR in headers (
X-User-Id: 12345) - IDOR in nested objects (
/users/me/orders/12345— does the API check both ownership ofmeand ownership of the order?) - IDOR via batched requests (
/orders?ids=12345,12346— does the API check each ID individually) - IDOR via aliased fields (
{"orderId": 12345}vs{"order-id": 12345}— both accepted, only one checked)
Broken Function-Level Authorisation (BFLA)
Similar to BOLA but at the function rather than the object level. The classic test: as a low-privilege user, can you call admin endpoints?
Test methodology:
- Log in as admin. Capture the request sequence for admin functions.
- Log out, log in as a regular user.
- Replay the admin request sequence with the regular user’s auth token.
- Expect 403. Observe whether you actually get 200.
Variations:
- HTTP method bypass:
GET /api/admin/usersreturns 403 butPOST /api/admin/usersaccepts a non-admin token - Path traversal in role check:
/admin/../usersbypasses the admin filter - Sibling endpoints with different access policies:
/users/12345(own) vs/users/12346(other) vs/admin/users/12346
OAuth scope abuse
If the API uses OAuth, test scope enforcement at the API layer (not just at the OAuth server). A token with read:profile should not be able to call write endpoints — but APIs often accept any authenticated token regardless of scope.
Phase 4 — input validation and injection
Injection findings are lower-frequency in modern APIs than in legacy web apps, but they still surface. The categories worth dedicated coverage:
| Category | Test patterns |
|---|---|
| SQL injection | ', ' OR '1'='1, time-based payloads, ORM-aware payloads |
| NoSQL injection | {"$ne": null}, {"$gt": ""}, JavaScript injection in MongoDB |
| Command injection | ; ls, | ls, $(ls), backticks |
| LDAP injection | *, *)(uid=*, `*)( |
| XPath injection | ' or '1'='1, ' or count(/)>1 or ' |
| Server-side template injection (SSTI) | {{7*7}}, ${7*7}, #{7*7} based on template engine |
| XML / XXE | External entity injection if the API accepts XML |
| Header injection | CRLF injection in user-controlled response headers |
The reality of API injection testing in 2026: most modern frameworks parameterise queries by default. The findings are in places where the framework was bypassed — raw query construction, ORM where clauses with string concatenation, dynamic table/column name interpolation, or older code paths that predate the framework’s safe default.
Phase 5 — business logic abuse
Business logic flaws are where the highest-impact API findings live. They are not catchable by scanners. They require the tester to understand what the API is supposed to do and then test what happens when the contract is broken.
Patterns that surface in production audits
- Quota bypass: free-tier user creates multiple sub-accounts to multiply free quota
- Sequence bypass: skipping a validation step in a multi-step flow (e.g., creating an order without going through the payment step)
- Race conditions: applying a coupon code, then applying it again before the first application’s “consumed” state is committed
- Time-of-check to time-of-use (TOCTOU): passing a check at request validation time, then doing the action after a state change that should have failed the check
- Negative-quantity abuse: requesting -5 items at $50 each yielding a $250 credit
- Discount stacking: combining discounts that the business rules say cannot be combined
- Workflow short-circuit: jumping from step 1 to step 5 in an approval flow without intermediate sign-offs
- State manipulation: setting
order_status = "paid"directly via API, when the intended path is via the payment webhook
The methodology for this phase is more interview-driven than tool-driven. Ask the business owner: what would be expensive if it happened? Then design specific test cases.
Phase 6 — mass assignment and property abuse
Mass assignment occurs when an API accepts a JSON object and persists all the fields, including ones the user should not control.
Test methodology
- Make a legitimate request, e.g.
PATCH /users/mewith{"name": "New Name"}. - Observe what fields the user object has (from a corresponding
GET /users/me). - Resubmit the PATCH with additional fields:
{"name": "New Name", "is_admin": true, "balance": 999999, "email_verified": true}. - Re-fetch the object and observe which extra fields were accepted.
Production findings typically include:
is_adminor role field updatable via user-profile PATCHcreated_at,updated_at,idupdatable (data integrity issue, not always security)email_verifiedtoggleable to true without verification flow- Currency, balance, or quota fields directly mutable
- Foreign-key fields (like
account_id) changeable, enabling pivot to another tenant
The fix is server-side allow-listing: explicitly enumerate which fields the endpoint accepts, reject the rest. Deny-listing fields is a common but fragile fix.
Phase 7 — rate limit and resource consumption testing
Rate limit absence is the single most common API finding by sheer frequency.
Test methodology
| Test | How to check |
|---|---|
| Unauthenticated rate limit | Send 1000 requests to a public endpoint in 10 seconds. Did the API throttle? |
| Per-user rate limit | Authenticated requests: send 1000 in 10 seconds as one user. Did the API throttle? |
| Per-IP rate limit | Same test, IP basis (matters for unauthenticated endpoints) |
| Distributed rate limit | Send from multiple IPs. Did the throttle still trigger? |
| Resource-intensive endpoint | Submit a query that does expensive work (large list, full-text search, report generation). Does it cap, or does it eat CPU? |
| Pagination abuse | Request ?per_page=10000000 — does the API cap or attempt to serve? |
The findings often surprise development teams: they assumed CloudFront, the load balancer, or the framework was handling rate limiting. None of those tend to handle authenticated per-user rate limiting unless explicitly configured.
Phase 8 — SSRF and out-of-band
Server-Side Request Forgery is one of the highest-impact findings to surface in API testing because it often chains to cloud credential theft.
Test methodology
- Identify any endpoint that takes a URL or hostname as input. Common patterns: webhook configuration, file upload by URL, image processing by URL, integration test endpoints.
- Submit a URL pointing at an out-of-band server (Burp Collaborator, interactsh, your own controlled host) and watch for callbacks.
- If callbacks arrive, escalate: try cloud metadata endpoints (
http://169.254.169.254/latest/meta-data/), internal services, port scanning the internal network.
The cloud metadata endpoint is the highest-impact SSRF target. If the SSRF can reach it, and the application is hosted on a cloud instance with IMDSv1 enabled or with a heavily-privileged IAM role, the SSRF yields cloud credentials. This is documented in detail in our container security audit checklist (check #47 — metadata endpoint blocking).
# Common SSRF payloads
http://169.254.169.254/latest/meta-data/ # AWS IMDS
http://metadata.google.internal/ # GCP
http://169.254.169.254/metadata/instance # Azure (requires header)
http://localhost:9200/ # Elasticsearch
http://localhost:6379/ # Redis
http://localhost:8500/v1/agent/services # Consul
Phase 9 — GraphQL-specific testing
GraphQL APIs have a different attack surface from REST. The testing methodology shifts.
Introspection abuse
If introspection is enabled in production, the entire schema is queryable:
{
__schema {
types {
name
fields { name type { name } }
}
}
}
Production GraphQL APIs should disable introspection unless there’s a specific reason to keep it on.
Query depth and complexity attacks
Without query depth limits, nested queries can produce expensive operations:
{
user(id: 1) {
friends { friends { friends { friends { friends { id } } } } }
}
}
Five levels of nesting on a query that touches a friend-of-friend graph can cause memory exhaustion. Test by submitting increasing depth and observing CPU/response time.
Batching attacks
GraphQL supports aliasing, which enables a single request to perform many operations:
{
attempt1: login(username: "admin", password: "password1") { token }
attempt2: login(username: "admin", password: "password2") { token }
attempt3: login(username: "admin", password: "password3") { token }
}
If the API rate-limits per HTTP request but not per GraphQL operation, this bypasses the rate limit. Test by aliasing the same operation multiple times and observing throttle behaviour.
Field-level authorisation
GraphQL field resolvers run independently. Authorisation must be enforced at the field level, not just at the query level. A query returning user { id name email phone ssn } requires authorisation on every field, not just the user object.
Phase 10 — reporting and re-test
The finding list is half the deliverable. The other half is the report.
Findings structure
For each finding, the report should answer:
- Title and severity (CVSS 3.1 score with justification)
- The attack chain narratively (what the attacker does, in plain language)
- Reproduction steps with exact requests and responses
- Affected endpoints, parameters, and accounts used
- Recommended fix with specific implementation guidance
- Re-test plan once the fix is shipped
Severity calibration
API findings often get under-rated because the tester treated the finding in isolation. The right calibration considers chained impact:
| Finding type | Standalone severity | Chained severity |
|---|---|---|
| BOLA on order endpoint | High (PII access) | Critical (if chained with mass assignment to take over orders) |
| SSRF to internal endpoint | Medium | Critical (if reaches cloud metadata) |
| Missing rate limit on login | Medium | High (enables credential stuffing at scale) |
| Mass assignment on profile | Medium | Critical (if is_admin is among the accepted fields) |
| OAuth scope not validated | Medium | High (if combined with a token leak) |
Report the chained severity when the chain is achievable in the same engagement.
Re-test
The re-test is where engagements often go wrong. Patches that look fine in code review can fail in implementation. Always re-test by attempting the original exploit, then test variations to confirm the fix is structural rather than addressing only the specific payload.
The honest tester’s playbook
The ten-phase methodology produces a high finding-density engagement. The compressed playbook for testers who have done this often enough that the phases are muscle memory:
- Map the surface beyond what’s documented. Every undocumented endpoint is a finding waiting.
- Get two accounts at every privilege level. BOLA testing without horizontal accounts is fiction.
- Authorisation is half the engagement. Spend the time.
- Business logic is where the seven-figure findings live. Interview the business owner.
- Mass assignment is the easiest critical finding to surface in modern APIs. Test it on every PATCH.
- Rate limits are nearly universally missing on at least one critical endpoint. Test the worst-case endpoint first.
- SSRF that reaches cloud metadata is one of the highest-impact findings in the API space. Always check.
- GraphQL has its own attack surface. REST tooling doesn’t cover it.
- Report chained severity, not standalone severity. The customer pays for risk reduction, not finding count.
- Re-test what you found. Implementations regress.
For broader context on where this fits in the testing lifecycle, see our web application pentest methodology and SaaS API security testing guide. Teams pairing this work with source code security review and continuous testing typically converge on a quarterly cadence — every release in scope, every quarter retested.
The argument was never whether APIs need pentesting. Every API does. The argument is whether the testing methodology covers the categories where the real findings live, or whether it follows a scanner’s report through the easier categories and stops there. The phases above are the difference between the two.