API Testing Without Postman: A Browser-Based Approach
Postman revolutionized API testing, but it is a heavy desktop app with accounts, cloud sync, and a growing list of features most developers never touch. For quick testing and debugging, a browser-based API client gets the job done with zero setup. This guide covers the fundamentals of API testing and the workflow patterns that make you efficient at it.
HTTP methods: more than GET and POST
Every REST API builds on a small set of HTTP methods. Understanding when each applies prevents a category of integration bugs.
GET retrieves a resource. It should be safe (no side effects) and idempotent (calling it twice gives the same result). Never use GET to mutate data.
GET /api/users/42
POST creates a new resource. The server assigns the ID and returns it in the response.
POST /api/users
Content-Type: application/json
{"name": "Alice", "email": "alice@example.com"}
PUT replaces an entire resource. If you send a PUT with a missing field, that field should be cleared. PUT is idempotent -- sending the same request twice produces the same state.
PUT /api/users/42
Content-Type: application/json
{"name": "Alice", "email": "alice@new-domain.com", "role": "admin"}
PATCH partially updates a resource. Only the fields you include are modified. PATCH is not guaranteed to be idempotent.
PATCH /api/users/42
Content-Type: application/json
{"email": "alice@new-domain.com"}
DELETE removes a resource. Most APIs return 204 No Content on success.
DELETE /api/users/42
HEAD is identical to GET but returns only headers, no body. Useful for checking if a resource exists or reading metadata without downloading the payload.
OPTIONS returns the allowed methods and CORS headers for a resource. Browsers send this automatically as a preflight request for cross-origin calls.
Headers you need to know
Request headers
| Header | Purpose | Example |
|---|---|---|
Content-Type |
Format of the request body | application/json |
Accept |
Desired response format | application/json |
Authorization |
Authentication credentials | Bearer eyJhbG... |
X-Request-ID |
Trace ID for debugging | req_abc123 |
Cache-Control |
Caching directives | no-cache |
Response headers to watch
| Header | What it tells you |
|---|---|
Content-Type |
What format the response is in |
X-RateLimit-Remaining |
How many requests you have left |
X-RateLimit-Reset |
When your rate limit resets (often a Unix timestamp) |
Retry-After |
How long to wait before retrying (on 429 or 503) |
Location |
URL of a newly created resource (on 201) |
Authentication patterns
Bearer tokens (JWT)
The most common pattern for modern APIs. You obtain a token (usually by POSTing credentials to a /login or /auth/token endpoint) and include it in subsequent requests:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
When testing, store the token as a variable in your API client so you do not have to paste it into every request.
API keys
Simpler than JWT. The key is included as a header or query parameter:
X-API-Key: sk_live_abc123def456
# or
GET /api/data?api_key=sk_live_abc123def456
Header-based keys are preferred because query parameters appear in server logs and browser history.
Basic auth
The username and password are Base64-encoded and sent in the Authorization header:
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
This is only safe over HTTPS. It is common for internal tools and CI/CD integrations.
OAuth 2.0
OAuth involves a multi-step flow (authorization code, client credentials, etc.). For testing, the client credentials flow is the simplest:
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&client_id=YOUR_ID&client_secret=YOUR_SECRET
The response contains an access_token that you use as a Bearer token.
Status codes: what they actually mean
Memorize these groups:
- 2xx: Success. The request worked.
- 3xx: Redirection. Follow the
Locationheader. - 4xx: Client error. Your request is wrong.
- 5xx: Server error. The API is broken.
The codes you will encounter most often:
| Code | Meaning | What to do |
|---|---|---|
| 200 | OK | Request succeeded |
| 201 | Created | Resource created, check Location header |
| 204 | No Content | Success, no response body (common for DELETE) |
| 400 | Bad Request | Check your request body and parameters |
| 401 | Unauthorized | Your token is missing, expired, or invalid |
| 403 | Forbidden | Your token is valid but lacks permission |
| 404 | Not Found | Check the URL and resource ID |
| 409 | Conflict | Resource already exists or state conflict |
| 422 | Unprocessable Entity | Validation failed, read the error details |
| 429 | Too Many Requests | You hit the rate limit, back off |
| 500 | Internal Server Error | Server bug, not your fault |
| 502 | Bad Gateway | Upstream server failed |
| 503 | Service Unavailable | Server is overloaded or in maintenance |
Debugging CORS
CORS (Cross-Origin Resource Sharing) errors are the most common frustration when testing APIs from a browser. The error looks like this:
Access to fetch at 'https://api.example.com/data' from origin
'http://localhost:3000' has been blocked by CORS policy.
This happens because the API server does not include the right headers to allow your origin. The fix is always on the server side, not the client.
Understanding the preflight
For requests with custom headers, non-simple content types, or methods other than GET/POST, the browser sends an OPTIONS request first. The server must respond with:
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
If the preflight fails, the actual request is never sent.
Common CORS fixes
- Server is not returning CORS headers at all. Add the appropriate middleware (e.g.,
corspackage in Express). - Wildcard with credentials.
Access-Control-Allow-Origin: *does not work withcredentials: 'include'. The server must echo the specific origin. - Missing headers in Allow-Headers. If you send
Authorization, the server must list it inAccess-Control-Allow-Headers.
Testing without CORS
Browser-based API clients that run requests through a proxy or service worker can bypass CORS restrictions for testing purposes. This is legitimate during development -- CORS protects end users in production browsers, not developers testing their own APIs.
A practical testing workflow
1. Start with the happy path
Make a basic request with valid data and confirm you get the expected 200/201 response. This verifies the endpoint is up and your authentication works.
2. Test validation
Send invalid data -- missing required fields, wrong types, values out of range. Verify the API returns 400 or 422 with helpful error messages.
POST /api/users
{"name": ""}
// Expected: 422 with {"errors": [{"field": "name", "message": "..."}]}
3. Test edge cases
Empty arrays, null values, very long strings, Unicode characters, SQL injection attempts, extremely large numbers. These reveal assumptions in the server code.
4. Test authentication and authorization
Send requests without a token (expect 401), with an expired token (expect 401), and with a valid token that lacks permissions (expect 403).
5. Test idempotency
Send the same POST twice. Does the server create duplicates or handle it gracefully? Send the same PUT twice. Does the result remain consistent?
6. Check error responses
Errors should return consistent JSON with a predictable structure. Inconsistent error formats are a sign of poor API design and will cause frontend bugs.
Response time benchmarks
As a rough guide for API response times:
- Under 100ms: Excellent. Simple CRUD operations should aim here.
- 100-500ms: Acceptable. Complex queries or external service calls.
- 500ms-2s: Slow. Consider caching, pagination, or async processing.
- Over 2s: Unacceptable for interactive use. Move to background jobs.
Browser-based API clients typically show response time alongside the status code, making it easy to spot slow endpoints.
Try our API Builder to test REST APIs directly in your browser -- build requests visually, inspect responses, and debug issues without installing any software.