What this covers and who it is for
API penetration testing is the practice of attacking an application’s programmatic interfaces — REST, GraphQL, gRPC and WebSocket endpoints — the way an adversary would, under written authorization, to prove whether authentication, authorization, input handling and business logic hold up when the caller is a script rather than a browser. If you are reading this API penetration testing guide because your last web application test never touched the endpoints your mobile app, partners and internal services actually call, this page is the correction.
The page serves three readers. Penetration testers get a repeatable methodology and a concrete worked example. Developers and AppSec engineers get the defender’s view of each test so findings become fixes and regression tests. Buyers, including small-business owners commissioning their first API test, get the scope terms to insist on and the questions to ask before signing.
Covered here: endpoint inventory, authentication and token handling, object- and function-level authorization, injection, mass assignment, abuse controls and business logic. Not covered: source code review, network penetration testing and mobile client reverse engineering. Those are separate disciplines, and the cluster articles linked at the end take each of them further.
Scope and authorization: never send a single crafted request to a system you do not own or have not been explicitly contracted to test. Written authorization must name the hosts, IP ranges, environments and API versions in scope, the permitted techniques, the test window, rate limits, prohibited actions such as destructive payloads, denial-of-service or data exfiltration, the rules for handling anything you capture, and an emergency contact. If a boundary is unclear, stop and ask. ‘It is probably in scope’ is how testers end up in a legal dispute or a production incident.
The short version
An API penetration test is a sequence of small, evidence-backed experiments, not a scanner run. General-purpose web scanners miss most API-specific flaws because they cannot reason about identity, ownership or business rules.
- Authorize first. Signed rules of engagement before any traffic.
- Inventory endpoints from the specification, live client traffic and history, including deprecated versions and staging hosts.
- Map the authentication model: API keys, OAuth 2.0 and OIDC bearer tokens, JWTs, mutual TLS, HMAC signing, session cookies.
- Build an identity matrix: anonymous, unverified, verified, a second user at the same privilege, a user in another tenant, support-level admin, full admin, service account.
- Test authentication — token lifetime, revocation, issuer and audience validation, signature verification, refresh rotation.
- Test authorization — swap object IDs and tenant IDs, call admin endpoints, change HTTP methods, probe batch routes and GraphQL variations.
- Test input handling — injection, server-side request forgery, XML external entities, deserialization, path traversal, mass assignment.
- Test business logic — negative quantities, currency and rounding, coupon stacking, race conditions, unbounded pagination.
- Validate by hand and report with request and response evidence, impact, and remediation a developer can act on.
- Retest after fixes and turn each finding into an automated regression check.
OWASP’s API Security Top 10 ranks broken object level authorization as the number one API risk, and that ranking matches what testers find: the interesting bugs are almost always about who can reach which object or which function, not about exotic payloads.
Key concepts you need first
- The specification is a starting point, not the truth. OpenAPI documents, GraphQL introspection output, gRPC reflection and Postman collections describe intent. Deployed APIs contain more: legacy versions, debug routes, admin endpoints, batch operations.
- Object references are the attack surface. Sequential IDs, UUIDs and slugs are all just references. Unguessable strings are obscurity, not authorization. Test every reference for an ownership check.
- Two axes of authorization. Object level asks whether this identity may read or modify this specific record. Function level asks whether this identity may call this endpoint at all. Code often enforces one and forgets the other, so test them separately.
- Multi-tenancy multiplies failure modes. Tenant identifiers appear in paths, query strings, headers, JWT claims and request bodies. Every location is a place where a check can be missing or the client can be trusted.
- Excessive data exposure. Endpoints that return full database rows and rely on the client to display a subset leak fields no interface shows: internal notes, cost prices, other users’ email addresses, administrative flags.
- Mass assignment. Binding a JSON body straight into a model lets a caller set fields the UI never sends, such as role, isAdmin, tenantId, balance or verified.
- Abuse controls are security controls. Rate limits, quotas, pagination caps and query complexity limits protect availability and slow enumeration. An API without them has a denial-of-service problem even when every authorization check is correct.
- Environments and data. Test staging where possible. When production is in scope, use dedicated accounts, avoid real customer data, redact evidence, and agree in writing on what happens to captured data at the end.
How to apply this
The sequence below works for a single API or an entire estate. Adjust depth to the engagement, but keep the order — authorization flaws are far easier to find once the identity matrix exists.
- Lock the paperwork. Confirm in-scope hosts and API versions, test window, rate limits, prohibited techniques, data-handling rules and an emergency contact, plus a named client-side person who can answer scope questions in minutes. Confirm that third-party services your target calls, such as payment processors, email providers and identity providers, are excluded unless separately authorized.
- Inventory every endpoint. Diff the published specification against routes seen in real client traffic. Capture a mobile or single-page-app session through a proxy, export the traffic, and enumerate paths that appear in neither source. Look for version drift, and for internal, admin, beta and debug prefixes.
- Baseline normal behaviour. Record clean authenticated requests for each important flow so you can distinguish a real anomaly from noise. Save tokens, headers and expected response schemas.
- Build the identity matrix. Create at least two objects of each interesting type per identity, two identities per role, and one identity in a second tenant. Without a second tenant you cannot test isolation; without a second user you cannot test horizontal access.
- Test authentication. Check token expiry, revocation on logout and password change, issuer and audience validation, signature verification, algorithm handling, whether tokens are accepted from the wrong client, whether API keys travel in query strings where logs capture them, and whether refresh tokens rotate.
- Test authorization. Replay every authenticated request as each identity against both objects. Swap object IDs, tenant IDs and user IDs in paths, query strings, headers and bodies. Change GET to PUT, PATCH and DELETE. Probe nested and bulk routes, batch endpoints, GraphQL aliases and field-level resolvers. Confirm enforcement is server-side, not merely hidden by the interface.
- Test input handling. Probe injection into parameters that reach a data store; server-side request forgery through URL fields, webhooks and image fetchers; XML external entities on XML or SAML endpoints; unsafe deserialization; path traversal in file and export routes. Add unexpected fields to JSON bodies to test mass assignment, and note whether the API accepts, ignores or rejects them.
- Test business logic and abuse. Try negative quantities, zero-price items, currency mismatches, coupon stacking, replaying one-time actions, and concurrency: fire the same limited-resource request in parallel and see whether the control survives. Check pagination caps, filter parameters, and GraphQL depth and complexity limits.
- Validate and report. Reproduce every finding by hand, capture the smallest request that proves it, record the exact identity and object used, and state business impact in the client’s language. Separate what you demonstrated from what you suspect.
- Support the fix and retest. Work with the developer on the root cause, then retest that case plus adjacent routes sharing the same handler or middleware.
Worked example. Consider an orders API exposing GET /api/v2/orders/{orderId}. With a normal customer token, requesting your own order returns 200. Incrementing the order ID by one also returns 200, with another customer’s order — object level authorization is missing. The same handler also exists as PUT and DELETE, so the flaw allows alteration and deletion, not just disclosure. The create endpoint accepts a client-supplied customerId and trusts it, so orders can be placed against another account. The report keeps one minimal request and response pair per case, redacts the other customer’s personal data, and states impact plainly: any authenticated customer can read, change and delete any other customer’s orders. The fix belongs at the data layer rather than in the controller:
-- Vulnerable: trusts the identifier from the URL
SELECT id, total, customer_id
FROM orders
WHERE id = :orderId;
-- Fixed: scopes every read to the authenticated tenant and owner
SELECT id, total, customer_id
FROM orders
WHERE id = :orderId
AND tenant_id = :tenantId
AND customer_id = :currentUserId;
Regression coverage should assert that user A receives 404, not 403, for user B’s order across GET, PUT and DELETE, and that a customerId in the request body is rejected or overwritten rather than trusted.
The defender’s view. The same tests generate detection opportunities. Alert on clusters of 401 and 403 responses grouped by token and source, because enumeration and authorization probing look exactly like that. Log authorization decisions with identity, object, tenant and outcome so an investigation can reconstruct who touched what. Watch for one token used from distant locations, for identifier walking visible as a high ratio of 404s to 200s, and for request bodies containing fields the client never sends — reject unexpected fields with a validation error and alert quietly. Enforce schema validation, per-token and per-tenant rate limits, and request size caps at the gateway. Diff the deployed route table against the published specification on a schedule and alert when an undocumented route appears. Keep a canary record per tenant so that if it is ever returned to an identity that should not see it, you get an alert rather than a breach discovery.
Where people get this wrong
- Testing only the documented specification. The undocumented route is usually the interesting one, because it was added in a hurry and never reviewed.
- Treating 401 and 403 as proof of security. Many handlers check that a token is valid and never check that the caller owns the object. A 200 with someone else’s data is the finding.
- Using a single test account. One identity cannot demonstrate horizontal or cross-tenant access. Two users in two tenants is the minimum viable setup.
- Trusting unguessable identifiers. UUIDs and long slugs stop guessing, not authorization bypass, because IDs leak through exports, logs, notifications, referrer headers and shared links.
- Leading with automated scanners. Proxy-based active scanning finds some injection and header issues, but it cannot infer ownership rules and will not report the most common API flaw.
- Following only the interface. If the flow was not exercised through the UI, testers sometimes never call it. Attack the API directly with a proxy or client; the interface is one consumer among many.
- Careless use of production data. Pulling real customer records to prove a finding creates a second incident. Use synthetic records and redact evidence at capture time.
- Scope creep into dependencies. Chasing a redirect into a payment provider, a SaaS integration or a cloud instance metadata address crosses lines the contract probably does not cover. Confirm authorization before you follow any hop off the target.
- Stopping at the report. Without retest and a regression test, the same authorization gap returns with the next refactor.
Where to go next
Work the adjacent parts of this cluster in order. Reconnaissance first, then testing depth, then how findings get prioritized and fixed.
- OSINT Tools for Penetration Testing: A Practical Hub — the reconnaissance foundation for mapping an organisation before touching its APIs.
- Recon-ng: The Ultimate Guide to Footprinting and Web Reconnaissance — structured footprinting that feeds endpoint discovery.
- SpiderFoot: An Open-Source OSINT Automation Tool for Cybersecurity — automating external asset discovery.
- Port Scanning with Python, Bash, and PowerShell — finding the services that host your APIs.
- Mastering the OWASP Top 10: A Comprehensive Guide to Web Application Security Threats — the web-layer counterpart to OWASP’s API guidance.
- PowerShell for Pentesters: Harnessing Microsoft’s Tool for Advanced Exploitation — useful for Windows-hosted API and service testing.
- Penetration Testing IoT Devices: Challenges and Approaches in the Connected World — APIs behind device management and telemetry.
- Set Up a Successful Bug Bounty Team — turning continuous API testing into a programme.
- Beyond CVSS: Contextual Vulnerability Prioritization — how to rank the authorization findings you just produced.
- Enhancing DevOps Security: Strategies for Securing CI/CD Pipelines — where API tokens and service accounts leak.
- Building a Robust Incident Response Playbook: Step-by-Step Guidelines for Organizations — what to run when an API flaw is exploited rather than reported.