APIs
API stands for Application Programming Interface, and they typically come in one of two flavours.
- REST (Representational State Transfer)
- SOAP (Simple Object Access Protocol)
In 2025 you're far more likely to come across REST APIs in the wild than SOAP, but they are still out there. REST API's are built off the back of JSON and there are some common implementation issues we can sometimes take advantage of.
Documentation
Most API's should come with documentation, this could just be a text file with descriptions of each endpoint and the parameters they accept, or it could be a full swagger file which allows you to test the calls against the API directly.
READ THE DOCUMENTATION.
No seriously, read through it, familiarise yourself with the calls, make sure you understand what the application actually is and what it can do.
API's sometimes have Swagger Documentation which can typically be found at api.somesite.com/swagger/ or similar. This documentation typically contains all endpoints and gives an example of the parameters that can be used for each request and gives you a really good place to start an assessment from.
It could also be OpenAPI documentation, either way, check the the following places:
Postman
Variables are fairly important, and should be set in the "Environment" tab.
Syntax: {{myVariable}}
Examples:
Request URL: http://{{domain}}/users/{{userId}}
Headers (key:value): X-{{myHeaderName}}:foo
Request body: {"id": "{{userId}}", "name": "John Doe"}
Tunnel Postman Through Burp
Assuming you have BurpSuite running on port 8000 then head into the settings: File > Settings > Proxy, and configure it as follows.

Now you any requests sent through Postman will be picked up by BurpSuite and you can do your normal intercept and modify type stuff.
Common Checks
- To change versions:
api/v3/loginβapi/v1/login - Check other AuthN endpoints:
/api/mobile/loginβ/api/v3/login/api/magic_link - Verb Tampering:
GET /api/trips/1βPOST /api/trips/1POST /api/tripsDELETE /api/trips/1 - Try Object IDs in HTTP headers and bodies, URLs tend to be less vulnerable.
- Try Numeric IDs when facing a GUID/UUID:
GET /api/users/6b95d962-df38βGET /api/users/1 - Wrap ID with an array:
{"id":111}β{"id":[111]} - Wrap ID with a JSON object:
{"id":111}β{"id":{"id":111}} - HTTP Parameter Pollution:
/api/profile?user_id=legit&user_id=victim/api/profile?user_id=victim&user_id=legit - JSON Parameter Pollution:
{"user_id":legit,"user_id":victim}{"user_id":victim,"user_id":legit} - Wildcard instead of ID:
/api/users/1β/api/users/*/api/users/%/api/users/_/api/users/. - Ruby application HTTP parameter containing a URL β Pipe as the first character and then a shell command.
- Developer APIs differs with mobile and web APIs. Test them separately.
- Change Content-Type to
application/xmland see if the API parse it. - Non-Production environments tend to be less secure (staging/qa/etc.) Leverage this fact to bypass AuthZ, AuthN, rate limiting & input validation.
- Export Injection if you see
Convert to PDFfeature.
Testing JWT Tokens
When you authenticate to an API (assuming it's REST and not SOAP) you'll get given a set of JSON tokens which contain your authorization keys. Typically you'll get an AuthToken of some description, and a ResetToken.
First thing we want to do is throw the AuthToken into jwt.io and see if there are any issues with the token itself. It should support proper encryption, if you happen to have found one that accepts the None algorithm, then you've hit the jackpot but those are becoming more and more rare.
We also want to test the ResetToken, typically there will be an endpoint that you can submit this token to and it will refresh your AuthToken, rather than going through the whole authentication process again. These tokens typically have a timeout applied to them, and they shouldn't work once the time has expired.
Below is an example of an API key I have been using on a recent assessment which has been redacted for privacy:
[
{
"type": "StringField",
"value": "[REDACTED]",
"name": "AccessToken"
},
{
"type": "StringField",
"value": "[REDACTED]",
"name": "RefreshToken"
},
{
"type": "DateTimeField",
"value": "2025-03-18T12:26:52.3281457Z",
"name": "ExpiryTime"
},
{
"type": "StringField",
"value": "Bearer",
"name": "TokenType"
}
]
As you can see we have the AccessToken the RefreshToken and the ExpiryTime, we also have a TokenType field, but that's purely informational and there isn't much we can do with that other than note that this is a "Bearer" token.
HTTP Verb Tampering
Already covered this in more detail over on the Web Hacking Guide but very briefly, web servers respond differently to various HTTP Methods, and sending up a GET when the endpoint is expecting a POST might yield unexpected results.
Always worth firing a bunch of different HTTP methods at a web server just to see how it responds to them, you might find some interesting information leakage that leads to being able to compromise the whole application.
| Method | Safe | Idempotent | Cacheable |
|---|---|---|---|
| GET | Yes | Yes | Yes |
| HEAD | Yes | Yes | Yes |
| OPTIONS | Yes | Yes | No |
| TRACE | Yes | Yes | No |
| PUT | No | Yes | No |
| DELETE | No | Yes | No |
| POST | No | No | Conditional* |
| PATCH | No | No | Conditional* |
| CONNECT | No | No | No |
Rate Limit Testing
APIs typically have rate-limiting imposed to prevent users from abusing the service or causing Denial-of-Service attacks.
The question you should be asking is, does the application actually enforce the rate limit? And if it does, what are the consequences?