API security testing is the practice of checking that every endpoint enforces who may do what to which object — not just that it responds. Most of what matters here is invisible to a scanner, because a broken API returns a perfectly valid 200 OK. This guide covers what to test, in what order, and which parts can be automated honestly.
What API security testing actually covers
Classic web scanning looks for injectable parameters and missing headers. API testing is mostly about authorisation: the same request, sent by a different caller, should produce a different answer. The OWASP API Security Top 10 puts the authorisation failures first for exactly that reason. In practice the list of things worth testing is short and stable:
- Object-level authorization (API1 / BOLA / IDOR). Can account B read account A's object by changing an id? Covered in depth in IDOR vulnerabilities.
- Function-level authorization (API5 / BFLA). Can a normal user reach an admin action —
/api/admin/users,/api/export,/internal/config— simply by knowing the path? - Excessive data exposure (API3). The endpoint returns the right object but too many fields: password hashes, internal flags, another user's email inside an embedded relation.
- Authentication weaknesses (API2). Tokens that never expire, JWTs accepted with
alg: noneor an unverified signature, refresh tokens that survive a logout, no rate limit on the login endpoint (see login rate limiting). - Unrestricted resource consumption (API4). An unbounded
?limit=, a GraphQL query nested deeply enough to be a denial of service, an export endpoint with no quota. - Injection and SSRF. Still real, still worth probing on every parameter that reaches a query, a template or an outbound request — but no longer the headline.
- Transport and surface hygiene. The API on plain HTTP, a staging API published on a subdomain nobody tracks, an OpenAPI spec served publicly with every internal route in it. That last one is an attack-surface problem as much as an API one.
A testing process that fits in a day
1. Build the endpoint inventory first
You cannot test what you have not listed, and almost nobody's API documentation is complete. Four sources, in order of reliability:
- the OpenAPI / Swagger document, if one exists — methods, paths and parameter schemas for free;
- GraphQL introspection, which returns the entire schema when it is left enabled in production;
- the front-end JavaScript bundles, which contain the URLs the app actually calls — including the ones the docs forgot;
- proxy traffic from a real session through the app, which catches everything the other three miss.
Merge those into one list of (method, path, params) and de-duplicate. This inventory is the input to every later step; our own engine builds exactly this before any authorisation check runs, because a check without an inventory only tests the endpoints someone remembered.
2. Test authorisation with two accounts
Register two accounts in separate tenants, then replay the inventory as each. Four questions per endpoint: does B get A's object; does an unauthenticated client get it; does a low-privilege account reach a privileged-looking path; does the response body contain fields this caller should not see. Everything here is a read — no writes, no deletes.
# The shape of the test, for every id-scoped endpoint
GET /api/invoices/8842 as account A (owner) → 200, note markers (email, account no.)
GET /api/invoices/8842 as account B → expect 404; a 200 with A's markers = BOLA
GET /api/invoices/8842 with no session → expect 401/404; a 200 = public object
GET /api/admin/users as account B (user) → expect 403; a 200 = BFLA3. Probe input handling — read-only, on parameters you discovered
SQL injection via error strings, boolean differentials and timing; template injection via arithmetic that the server evaluates; SSRF via a URL parameter pointed at a metadata address. Keep probes idempotent and treat anything unconfirmed as a low-severity lead, not a finding. A signal that could be a coincidence — a 500, a slow response — is a reason to look, not a reason to file a critical.
4. Check the perimeter around the API
Is the API reachable over plain HTTP? Does it set HSTS? Are cookies Secure and HttpOnly? Is there a forgotten api-staging. host in the certificate transparency log? These are the parts you can check passively, from outside, without credentials — and the only parts a free scanner can honestly tell you about.
Start with the perimeter: the free subdomain finder reads certificate transparency logs and shows which api./staging./internal. hosts of your domain are publicly visible right now. No signup.
Find your exposed API hosts →API penetration testing vs. automated scanning
These get used interchangeably and should not be. An automated scan replays known patterns: it is fast, repeatable, and finds missing headers, exposed files, weak TLS, obvious injection. A penetration test is a person reasoning about your business rules — that a refund endpoint accepts a negative amount, that step 3 of a checkout can be called without step 2, that a support role can read an audit log. No tool has the context to ask those questions.
The honest split: automate the inventory, the authorisation matrix and the hygiene checks — they are mechanical and they regress every sprint. Pay a human for the business logic. Anyone selling you “automated API penetration testing” that covers both is selling you the first half with the second half's name on it. What a tool can genuinely automate is set out in API security testing tools.
Keeping it from regressing
API authorisation breaks in refactors, not in design reviews. The controls that hold over time are small and boring:
- One ownership helper, used by every id-scoped route. Filter by
(id, current_user)in the same query; re-implementing the check per route guarantees one route eventually skips it. - A cross-account test per route shape, in CI. Two fixtures, one request, expect a 404. It takes ten lines and it is the only check that notices when someone removes the filter.
- Deny by default at the router. New endpoints should require an explicit authorisation decision to become reachable, rather than inheriting public access because nobody added a decorator.
- Serialise responses from an explicit field list, never from the model object. Excessive data exposure is almost always a
return obj.__dict__that grew a new column. - Re-run the perimeter checks on a schedule. A new staging host or an expired certificate is a weekly-cadence problem, not a yearly-pentest one.
For depth on requirement-by-requirement verification, the OWASP ASVS is the reference worth owning; this guide is the operational subset most teams actually need.
Where to start if you have an afternoon
In this order, because each step costs more than the last: list your public hosts and kill the forgotten ones; confirm the API is HTTPS-only with sane cookies; build the endpoint inventory from your own JS bundle; then run the two-account authorisation matrix against the ten endpoints that return customer data. That last step is where the real findings are, and it is the one nobody runs. If you want it as a list you can work down rather than prose, use the API security checklist — same ground, one verification step per line.
If you'd rather have it done: our deep audit builds the endpoint inventory, runs the two-account authorisation matrix against your real API, and reports proof — 'logged in as B, read A's invoice' — not severity labels. Quoted per application, from €900.
Ask about a deep audit →