
An API penetration testing checklist for 2026 has to start where scanners stop: object-, function- and property-level authorization tested with two or three real identities, plus business-flow abuse and the agent tool surface. These flaws return HTTP 200 with valid JSON, so a tool that tests one identity at a time will report a clean API that is not clean.
The short answer
- An API pentest must test what scanners can't model: BOLA via cross-tenant replay, BFLA across every role and method, and mass assignment on every writable object.
- Those flaws return valid 200 responses, so single-identity DAST and AI scanners report them as clean.
- Add business-logic and single-packet race tests, per-token rate limits, shadow API versions, GraphQL field-level authz, and agent/MCP tool calls.
- Deliver a completed access matrix and convert each finding into a multi-identity CI regression test.
Key takeaways
- BOLA, BFLA and BOPLA return valid 200 responses, so single-identity scanners and AI agents structurally miss them.
- Test with at least three identities across two tenants; same-tenant pairs hide cross-tenant BOLA.
- Unguessable IDs and disabled GraphQL introspection are obstacles, not controls.
- Mass assignment hides in JSON Patch, Merge Patch, duplicate keys, form-encoded bindings and prototype pollution.
- MCP tools and agent tokens belong in the access matrix as another caller that can be prompt-injected.
What should an API penetration test check that automated scanners miss?
An API pentest has to check what only exists relative to a second identity. Can user B read user A’s invoice (BOLA)? Can a member call an admin endpoint (BFLA)? Can a client write fields it should never touch (mass assignment)? It also has to check sequences: what happens when step four runs before step two, or when the same coupon request lands twenty times in one millisecond.
Scanners are good at a different list: injection, missing headers, verbose errors, known CVEs, TLS settings. Keep running them. Just don’t treat their output as the authorization review. The OWASP API Security Top 10 (2023), still the reference taxonomy, puts three authorization classes in its top five: API1 BOLA, API3 BOPLA and API5 BFLA.
The core of the checklist:
- BOLA via two-account differential replay of every request that carries an object ID
- BFLA against a full role × endpoint × method matrix
- Property-level authorization: excessive data exposure on reads, mass assignment on writes
- Business-logic and race-condition abuse on every stateful flow (API6)
- Per-token and per-tenant rate limits on expensive endpoints, not per-IP on login
- Shadow and deprecated versions (API9), and the gateway-versus-service gap
- GraphQL authorization per field and per mutation, plus batching and cost abuse
- Agent and MCP tool endpoints, which are APIs with a caller that can be prompt-injected
What is the difference between BOLA, BFLA, and BOPLA?
They are the same question asked at three levels: which object, which function, which property. BOLA asks whether you may touch this record. BFLA asks whether you may call this operation at all. BOPLA asks whether you may read or write this specific field on a record you are otherwise allowed to access.
| Class | Question | Example | CWE | Fix |
|---|---|---|---|---|
| API1 BOLA | May this identity access this object? | GET /api/v1/invoices/8812 with tenant B’s token returns tenant A’s invoice |
CWE-639 | Ownership check at the data-access layer, scoped by tenant from the token, never from the request |
| API5 BFLA | May this role call this function? | A member sends DELETE /api/users/42, which the UI only shows admins |
CWE-285, CWE-862 | Deny-by-default policy per route and method |
| API3 BOPLA | May this identity read or write this field? | PATCH /me with "role":"admin" sticks; or /me returns ssn the UI never renders |
CWE-915, CWE-863 | Explicit read and write DTO allowlists per role |
BOPLA is the 2023 merger of two 2019 entries, Excessive Data Exposure and Mass Assignment. They are the read side and the write side of one property-level problem, so test them together.
Why do DAST tools and AI scanners fail to detect broken object level authorization?
Because a BOLA request is not malformed. It is syntactically valid, returns 200 and carries a well-formed body. The defect exists only relative to another identity’s entitlements and the business rule about who owns what, and a tool that holds one session at a time has neither.
Three structural gaps explain most misses:
- One identity. Without a second token from a different tenant, there is nothing to diff against.
- No intent model. The scanner cannot know that a support agent should see tickets but not refunds, or that a trial is one per account.
- No multi-step state. Logic flaws live in the order of calls, not in any single request.
AI-driven scanners crawl better and write nicer reports, but they inherit the same blindness unless you feed them identities and rules. In our AI pentest tools benchmark on Juice Shop, a Claude-plus-Burp workflow with an operator in the loop outscored every autonomous agent, and the tools with the largest alert piles scored lowest.
| Approach | Reliably finds | Structurally misses | Where it fits |
|---|---|---|---|
| SAST | Injection sinks, hardcoded secrets, unsafe deserialization | Missing checks that are absent rather than wrong; cross-service policy | Every PR |
| DAST / AI scanner | Injection, misconfiguration, headers, known CVEs, some auth bypass | BOLA, BFLA, BOPLA, business logic, races | Nightly or per release |
| Manual multi-identity testing | Authorization semantics, logic, chained impact | Breadth on huge surfaces without tooling | Per major release, then as CI regression tests |

Where does an API pentest start: discovery and the access matrix?
It starts with inventory, because you cannot authorize-test an endpoint you never found. API9 Improper Inventory Management is usually found outside the spec you were handed.
- Check
/openapi.json,/v3/api-docs,/swagger-uiand GraphQL endpoints on every host. - Grep JavaScript bundles for path strings; decompile the mobile app for hardcoded hosts, keys and its pinned older API version.
- Search public repositories for leaked Postman collections and gateway configs.
- Brute-force versions and route prefixes (
/v1,/beta,/internal,/legacy) with Kiterunner or ffuf; find hidden parameters with Arjun or Param Miner.
Then test the gateway-versus-service gap. A common pattern: the gateway enforces authentication and coarse scopes, while the upstream service trusts anything that reaches it. Where service hostnames are reachable, hit them directly, and check whether internal calls carry the end user’s identity or a blanket service token.
Before the first payload, build the matrix. Minimum identities: tenant A admin, tenant A member, tenant B user, unauthenticated, and any service or agent token. Rows are endpoints and methods; cells are expected versus actual. Load the tokens into Burp Suite Professional with Autorize and AuthMatrix, or into Caido, so every request you browse is replayed under the other identities automatically.

How do you test for BOLA with two accounts, and what if you only have one?
Capture every request that carries an object identifier as identity A, replay it with identity B’s token, and compare. Do it across tenants, not just within one: two accounts in the same tenant hide cross-tenant BOLA entirely.
Swap IDs at every nesting level, because identifiers hide in more places than testers check:
- Path segments and query parameters (
?account_id=) - JSON body fields, including nested objects inside arrays
- Headers such as
X-Tenant-Id, cookies and JWT claims - Batch endpoints that accept a list of IDs (mix one foreign ID into a list of yours)
- Export and report endpoints that take a filter instead of an ID
- GraphQL arguments and variables
Send each request three ways: valid owned ID, nonexistent ID, well-formed foreign ID. A 403 for foreign and 404 for nonexistent confirms existence and enables enumeration. Expect identical responses for both.
UUIDv4, ULIDs and hashes are not authorization. They leak through list and search endpoints, GraphQL node() lookups, PDF exports, email links and integrations, and UUIDv7 and ULID encode timestamps, which makes them partly predictable.
With only one account, push back on scope first: single-identity BOLA testing is a scoping defect, and self-service signup usually gives you a second tenant in minutes. If you truly cannot, create objects and record their IDs, then use another session state (logged out, downgraded role, deleted object) as the second identity, and harvest foreign IDs from shared or public surfaces for low-impact reads. Document the gap in the report. Our broken access control and IDOR testing always runs with multiple tenants for this reason.
How do you test BFLA across roles and methods?
Enumerate every endpoint and every HTTP method, including PUT, PATCH and DELETE variants the UI never calls, then replay each as every lower-privileged role. Test both directions: a member reaching admin functions, and admin-only workflows reachable by an unauthenticated or service-token caller.
Method and format tampering still works in 2026:
- GET to POST, POST to PUT, and
X-HTTP-Method-Override - Switch
application/jsontoapplication/x-www-form-urlencodedor XML; some Rails and Spring setups bind form bodies to models even when JSON binding is locked down - Append
.jsonor.xmlsuffixes to route around path-based rules
The durable fix is architectural: centralized, deny-by-default enforcement with Open Policy Agent (Rego), AWS Cedar, Oso or a Zanzibar-style relationship model, applied at the data layer instead of re-implemented in each controller. Per-controller checks drift, and drift is where BFLA lives.
How do you test a REST API for mass assignment?
Take every writable object and add fields the client never sends, then read the object back to see what stuck. The standard set: role, is_admin, tenant_id, org_id, owner_id, status, verified, email_verified, credit_balance, permissions[], scopes[]. Get candidate names from response bodies, the spec and JS bundles. This is CWE-915.
Then push past the DTO allowlist:
- JSON Patch (RFC 6902):
{"op":"replace","path":"/role","value":"admin"}. Path-based operations often skip field allowlists by design. - JSON Merge Patch (RFC 7396): replace a whole nested object, such as
billingorpermissions. - Duplicate keys:
{"role":"user","role":"admin"}. The validator and the binder may pick different values. - Prototype pollution (CWE-1321):
__proto__,constructorandprototypekeys in bodies or query strings, wherever a Node.js service deep-merges input into a model or config. - Content-type switching, as in the BFLA section.
For the read side, diff every API response against what the UI renders. Internal IDs, full addresses, role lists, soft-delete flags, internal notes and margin data are findings even if the frontend hides them. The API is the trust boundary, not the React component.
How do you test GraphQL APIs for authorization flaws?
Test authorization at the resolver and field level, not only at the query root. A schema that guards Query.invoice but not User.invoices leaks the same data through traversal.
Disabled introspection is not a control. Field suggestions (“Did you mean…”) in error messages, GET-based queries and Clairvoyance can reconstruct much of a schema without it. Fingerprint the engine with graphw00f, then load what you recover into InQL.
- Global IDs: Relay-style IDs are base64 of
Type:id. Decode, swap the ID, re-encode, and callnode(id:). That is encoding, not secrecy. - Traversal: start from an object you own and walk edges to objects you don’t.
- Interfaces and unions: check that authorization holds for every concrete type.
- Mutations: inspect input types for fields the UI never sends; this is mass assignment in GraphQL form.
- Batching and aliasing: hundreds of aliased login or OTP mutations in one HTTP request walk straight past per-request rate limits. CrackQL and BatchQL automate this.
- Cost: deep nesting, recursive fragments and
@include/@skiptricks against depth limits. Depth limiting misses wide queries; cost scoring misses aliasing unless it counts aliases; persisted-query allowlists are the strongest control if the server rejects unknown queries. - Transport: mutations accepted over GET are CSRF-able; subscriptions over WebSockets often skip per-message authorization.
Our GraphQL security testing covers federation and Apollo persisted-query setups, where gateway and subgraph authorization frequently disagree.

How do you test business logic flaws, race conditions, and rate limits?
Map every stateful flow as a state machine, then break the machine. Business logic is API6: the endpoints look like ordinary CRUD, and the flaw is in the sequence.
- Call step 4 before step 2; replay a completed step
- Submit negative or fractional quantities; change currency mid-flow
- Stack discount codes; refund after cancel; reuse a one-time invite
- Complete KYC or verification checks out of order
Races are where the money is. The single-packet attack (HTTP/2 request coalescing, or last-byte synchronization over HTTP/1.1) lands 20 to 30 requests inside the same server window, which makes double-spend, coupon reuse, one-time-token reuse and one-trial-per-account bypasses reliable. Turbo Intruder implements both techniques.
Rate limiting is almost always tested on login, from one IP. Test it on search, export, report generation, SMS and OTP send, password reset and bulk endpoints instead.
| Limit keyed on | Stops | Bypass it invites |
|---|---|---|
| IP | Naive single-host abuse | Rotating IPs, X-Forwarded-For spoofing, IPv6 ranges |
| Token | One session hammering an endpoint | Minting fresh tokens; GraphQL aliasing inside one request |
| Account | Token rotation | Cheap mass signups |
| Tenant | Noisy-neighbor cost abuse | Rarely bypassed; needs per-operation cost weighting |
Which token and authentication checks still produce findings in 2026?
JWT edge cases still turn up in production. Check alg:none and RS256-to-HS256 confusion, kid header injection, JWKS URL spoofing, and missing aud, iss, exp and nbf validation. RFC 9068 defines the JWT access token profile; a token minted for service A should fail at service B.
Then look at scope and lifetime: OAuth scopes far broader than the endpoint needs, long-lived tokens with no rotation, and bearer tokens accepted from query strings, where they land in logs. RFC 9700, the OAuth 2.0 Security Best Current Practice published in January 2025, is the reference to cite. For stolen-token replay, ask whether the API uses sender-constrained tokens: DPoP (RFC 9449) or mTLS-bound tokens (RFC 8705).
Remember that SSO answers who the caller is, not what they may touch. A perfect OIDC flow sitting in front of an API with BOLA is still a breach.
How does API penetration testing change for LLM agents and MCP servers?
It doesn’t change the method. It adds a caller that holds a token and can be steered by data it reads. MCP servers and tool-calling endpoints are APIs, and they fail in familiar ways:
- No auth on the transport: MCP servers exposed over HTTP with no authentication at all.
- BOLA on tools: a tool accepts any resource ID and acts with the agent’s privileges, not the end user’s.
- Over-scoped grants: the agent receives an OAuth token with write access to everything the user can reach (LLM06:2025 Excessive Agency).
- Injection in tool output: a ticket, email or web page returned by one tool steers the next tool call.
- RAG tenant isolation: vector-store queries missing a tenant filter return another customer’s chunks (LLM08:2025 Vector and Embedding Weaknesses).
- Unbounded loops: agents retrying or recursing burn tokens and third-party quotas (API4:2023 and LLM10:2025 Unbounded Consumption).
Add the agent token as a column in the access matrix and replay every tool call as tenant B. Our MCP server and tool-chain security testing does exactly that, plus the injection paths that drive the agent into calls it was never meant to make.
What should an API pentest report contain, and which compliance drivers apply?
The deliverable is the completed access matrix, not a list of payloads. A report you can act on contains:
- The identity × endpoint × method matrix with expected and actual results
- A coverage figure: endpoints tested versus endpoints discovered from the spec plus discovery
- Request and response evidence for each finding using two named accounts
- The realistic attacker: unauthenticated, adjacent tenant, low-privilege insider or compromised agent token
- A remediation pattern (where the check belongs), not just the reproducing request
Argue severity in context. CVSS base scores tend to underrate BOLA and BFLA because the impact is cross-tenant data exposure, not host compromise. A “medium” that exposes every customer’s invoices is a reportable breach.
Close the loop by turning each authorization finding into a multi-identity regression test in pytest with requests, or Newman running a Postman collection in CI, so the same BOLA does not come back next sprint.
Compliance drivers: PCI DSS v4.0 requires internal and external penetration testing at least annually and after significant changes (requirement 11.4). DORA has applied to EU financial entities since 17 January 2025 and requires threat-led penetration testing at least every three years for entities designated by their authority. NIS2 member-state transposition was due 17 October 2024. The EU Cyber Resilience Act entered into force in December 2024, with its main product obligations applying from December 2027.
The consolidated API penetration testing checklist
| OWASP API 2023 | Check | Tooling |
|---|---|---|
| API1 BOLA | Cross-tenant replay of every ID-bearing request; IDs in path, query, body, header, JWT, batch, export | Autorize, AuthMatrix, Caido |
| API2 Broken Authentication | JWT alg, kid, JWKS, aud/iss/exp; token lifetime; tokens in URLs | Burp JWT tooling, manual |
| API3 BOPLA | Response-to-UI diff; extra fields, JSON Patch, Merge Patch, duplicate keys, __proto__ |
Param Miner, Repeater |
| API4 Resource Consumption | Per-token and per-tenant limits on expensive endpoints; GraphQL aliasing and depth; agent loops | Turbo Intruder, BatchQL |
| API5 BFLA | Every method × role; method override; content-type switch; both privilege directions | AuthMatrix |
| API6 Business Flows | Step skipping, replay, negative values, single-packet races | Turbo Intruder |
| API7 SSRF | Webhooks, URL imports, PDF renderers, cloud metadata | Collaborator, manual |
| API8 Misconfiguration | CORS, verbose errors, 403/404 consistency, GraphQL field suggestions | Nuclei, ZAP, graphql-cop |
| API9 Inventory | Old versions, /internal, mobile pinned versions, leaked specs; gateway versus direct service |
Kiterunner, ffuf, Arjun |
| API10 Unsafe Consumption | Trust in third-party and tool output, including MCP and RAG responses | Manual |
Run scanners for the rows they handle well. Staff the rest with people, real identities and a matrix.
FAQ
What is BOLA in API security?
Broken Object Level Authorization (API1:2023) means an API returns or modifies an object without checking that the caller owns it, for example invoice 8812 served to another tenant's token. It maps to CWE-639 and is the classic IDOR applied to APIs.
Can DAST or AI scanners find BOLA?
Not on their own. BOLA only shows up when you compare two identities' entitlements, and scanners typically hold one session and have no model of who should own which record. Some tools can do differential replay if you supply multiple tokens, but the test design still has to come from a person.
Is disabling GraphQL introspection enough to protect the schema?
No. Field suggestions in error messages and tools like Clairvoyance can rebuild much of the schema without introspection. Treat it as hygiene and put the real control in resolver-level authorization.
How many test accounts does an API pentest need?
At least three: an admin and a member in tenant A, and a user in tenant B, plus unauthenticated and any service or agent tokens. Two accounts in the same tenant will not reveal cross-tenant BOLA.
Is the OWASP API Security Top 10 2023 still current?
The 2023 edition remains the reference list for API testing. Pair it with the OWASP Top 10 for LLM Applications (2025) when the API serves or is called by AI agents.
How should rate limiting be tested on an API?
Test per token, per account and per tenant, not just per IP, and test the expensive endpoints like search, export, OTP send and password reset. On GraphQL, check whether aliased operations in one request are counted individually.
How do you stop a fixed BOLA from coming back?
Turn each authorization finding into an automated multi-identity test, for example pytest or Newman replaying the request as tenant B and asserting a denial, and run it in CI. Centralized deny-by-default policy enforcement reduces how often new ones appear.
If your last API report was a scanner export, we run multi-tenant, matrix-driven API penetration testing across REST, GraphQL and agent tool endpoints, and hand back regression tests along with the findings.