API Testing
Test application programming interfaces directly — sending requests, validating status codes, headers, and response bodies — without going through the UI. This is where most of the interesting business logic actually lives.
1 The Hook
A KiwiSaver provider ships a new mobile app. The team tests it the way most teams test: open the app, log in, tap through the screens, check the balance shows. Everything looks fine in the browser-driven walkthrough, so it goes live.
Two weeks later, support is flooded. Some members are seeing another member’s balance. The bug was never in the UI — it was in the backend endpoint GET /members/{id}/balance, which returned a balance for any id the caller asked for, with no check that the logged-in member actually owned that account. The UI only ever requested the member’s own id, so clicking around the app could never surface it. A single direct request to the endpoint with someone else’s id would have caught it in minutes.
This is the pattern: the business logic, the authorisation rules, the data validation all live in the API, not the screen. Test only through the UI and you are testing a thin layer over the part that actually matters — and the most damaging defects sit underneath it, where a browser click never reaches.
API testing means sending HTTP requests directly to backend endpoints and asserting the status code, response body, and authorisation behaviour — bypassing the UI entirely to reach the business logic that actually matters. Use it whenever you need to verify security rules, data validation, or service integrations, because the UI only ever sends the requests its own screens generate and will never expose an IDOR or a missing auth check. The mistake most testers make is asserting only the status code: a 200 response can still return the wrong data type, a missing required field, or another customer’s data — the status code is the first assertion, never the only one.
The most dangerous API test suite I have ever audited had 340 tests and zero failures. It also had zero assertions. Every request had a status-code check and nothing else — so when a government API quietly started returning another user's data inside a 200 response, the suite went green. The team thought they had coverage. They had expensive HTTP pinging. Real API testing means asserting the response body shape, the data types, the required fields, and — above everything else — that your auth token actually gates access. I now have a rule: the first test against any new endpoint is to remove the Authorization header. If you still get a 200, nothing else matters. Fix that before you write another line.
2 The Rule
Test the API directly — send the request and assert the status code, the response body, and the authorisation behaviour — because the logic that matters lives in the service, not the screen that calls it.
3 The Analogy
Checking a building by talking to the front desk versus walking the wiring.
UI testing is asking the receptionist at a HealthNZ hospital, “Is the building safe?” They smile and say yes — the lobby looks tidy, the lights are on. But the receptionist only sees the front desk. The fire wiring, the locked drug cupboards, the staff-only doors that should never open for a visitor — none of that is visible from the lobby.
API testing is the building inspector who walks the service corridors, opens the panels, and tries each locked door to see whether it actually stops the wrong person. The lobby (the UI) can look perfect while a staff-only door (a protected endpoint) swings open for anyone. You only find that by going behind the front desk and testing the wiring directly.
Common Mistake vs What Works
Testing only the happy path: a valid POST returns 201, so the test passes. Status codes are verified but the response body is never asserted — no check that the required fields are present, that amount is a number and not a string, or that the currency is NZD. Error paths (400, 401, 403, 404, 429) are left to the developers to discover in production.
Test the full status-code map for each endpoint: valid inputs (201/200), invalid inputs (400 with a matching error schema), missing or expired auth (401), wrong-user access such as an IDOR attempt (403), non-existent resource (404), and rate-limit breach (429). Assert the complete response shape on every call — required fields, data types, and NZ-specific formats (NZD currency, 4-digit postcodes, BB-bbbb-AAAAAAA-SS bank accounts). A 200 with an empty body or a wrong data type is still a bug; the status code is the first assertion, never the only one.
What it is
API testing means interacting directly with an application’s backend endpoints — sending HTTP requests and verifying the responses — rather than driving the application through a browser or mobile UI. Because most modern applications are built on top of APIs, testing them directly gives you faster feedback, earlier in the cycle, and with finer-grained assertions than UI testing allows.
An API test asks: given this request (URL, method, headers, body), does the system return the right response (status code, body schema, data values, error messages)? It’s specification-based testing applied to a service contract rather than a user interface.
Why test at the API level? UI tests are slow, brittle, and expensive to maintain. API tests run in milliseconds, don’t break when a button moves, and can be run thousands of times a day in CI. If the API is correct, the UI just needs to call it correctly — a much smaller testing problem.
When to use it
- When the UI is not yet built — test the backend immediately after backend development, without waiting for frontend work
- When you need to test business logic independent of the frontend — price calculations, eligibility rules, data transformations
- When validating integrations between services — does Service A send the right data to Service B?
- For regression testing after backend changes — fast, repeatable, and doesn’t require a browser
- For performance and load testing — drive hundreds of concurrent requests directly at the API
Key concepts
HTTP methods
Each HTTP method has a defined semantic meaning. Verify that your API uses them correctly:
| Method | Purpose | Idempotent? | Example |
|---|---|---|---|
| GET | Retrieve a resource | Yes | GET /orders/123 |
| POST | Create a new resource | No | POST /orders |
| PUT | Replace a resource entirely | Yes | PUT /orders/123 |
| PATCH | Update part of a resource | No | PATCH /orders/123 |
| DELETE | Remove a resource | Yes | DELETE /orders/123 |
Status codes to know
Status codes are the first thing you check. A wrong status code is a bug, even if the response body looks right.
| Code | Meaning | When to expect it |
|---|---|---|
| 200 OK | Success | Successful GET, PUT, PATCH |
| 201 Created | Resource created | Successful POST that creates a new record |
| 400 Bad Request | Client error — malformed input | Missing required field, wrong data type |
| 401 Unauthorised | Not authenticated | No auth token or invalid token |
| 403 Forbidden | Authenticated but not allowed | User doesn’t have permission for this resource |
| 404 Not Found | Resource doesn’t exist | GET /orders/99999 where that order doesn’t exist |
| 422 Unprocessable Entity | Validation failed | Input is syntactically valid but semantically wrong (e.g. invalid NZ postcode) |
| 500 Internal Server Error | Server crashed | Unhandled exception — always a bug |
Anatomy of a request
Every API request has four possible components. Understanding what goes where prevents common test setup errors:
- URL — the endpoint address, including path parameters:
https://api.example.co.nz/v1/orders/123 - Headers — metadata sent with every request. Key ones to test:
Authorization: Bearer <token>(authentication) andContent-Type: application/json(tells the server how to parse the body) - Query parameters — filtering and sorting options appended to the URL:
?status=pending&limit=10 - Request body — the data sent with POST/PUT/PATCH requests, usually as JSON
What to validate in the response
A thorough API test checks more than just the status code:
- Status code — does it match what the spec says?
- Response body schema — are all expected fields present? No extra unexpected fields?
- Data types — is
pricea number (not a string)? IsorderIda string UUID? - Required fields — if the spec says
customerIdis always returned, verify it’s never null or missing - Error messages — on 4xx responses, is the error message clear, actionable, and free of internal implementation details?
- Response time — does the endpoint respond within an acceptable SLA?
Contract testing
If your API has an OpenAPI (Swagger) specification, treat it as your test oracle. The spec defines the contract: what requests the API accepts, what responses it returns, and what each field means. If the live API doesn’t match the spec, that’s a bug — even if the API appears to “work.” Consumers of the API (frontend, mobile app, partner integrations) are coding to the spec, not to whatever the API happens to return today.
NZ worked example
Imagine you’re testing an e-commerce API for a New Zealand retailer. Here’s a sequence of API tests covering the order creation flow:
| Test | Request | Expected status | Key assertions |
|---|---|---|---|
| Create order with valid NZ address | POST /orders body: valid NZ shipping address (e.g. 123 Queen St, Auckland 1010) |
201 Created | Response contains orderId, status: "pending", total in NZD |
| Retrieve the created order | GET /orders/{orderId} | 200 OK | Returned order matches what was posted; orderId matches |
| Create order with invalid NZ postcode | POST /orders body: postcode "9999" (not a valid NZ postcode) |
422 Unprocessable Entity | Error message mentions postcode; no order created in the database |
| Create order without auth token | POST /orders No Authorization header |
401 Unauthorised | No order created; error body does not expose internals |
| Retrieve order belonging to another customer | GET /orders/{otherCustomerOrderId} | 403 Forbidden | System does not return another customer’s order data (IDOR check) |
| Get order that does not exist | GET /orders/00000000-0000-0000-0000-000000000000 | 404 Not Found | Clear error message; no stack trace in body |
Note that the NZ-specific context matters: NZ postcodes are 4-digit numbers (1010 for central Auckland, 6011 for central Wellington, 8011 for central Christchurch). A postcode of “9999” or “12345” is invalid and should be caught at the API layer, not just in the UI.
Common bugs API testing finds
- 200 when it should be 400 — the API accepts clearly invalid input without complaint, then fails silently later
- Stack traces in error responses — a 500 response that includes a Java stack trace or file path is a security risk, not just an aesthetic issue
- Missing required fields — the spec says
customerIdis always returned, but it’s absent for guest orders - Wrong data types —
pricereturned as a string ("49.99") instead of a number (49.99), breaking downstream calculations - No authentication required on protected endpoints — removing the
Authorizationheader and still getting a 200 is a critical security bug - CORS headers missing or too permissive —
Access-Control-Allow-Origin: *on an authenticated endpoint allows cross-site requests from any domain - Inconsistent IDs — POST returns
id: 123but GET expects/orders/00000123(zero-padded) — integration breaks
Tools
- Postman — the most widely used API testing tool; supports collections, environments, and automated test scripts written in JavaScript. Good for manual and automated API testing.
- Insomnia — lighter-weight alternative to Postman; good for individual API exploration
- REST-assured — Java library for writing API tests as code; integrates with JUnit/TestNG; preferred in Java-heavy teams
- k6 — designed for performance and load testing APIs; scripts in JavaScript; can run from CI
- Newman — Postman’s CLI runner; run your Postman collections from the terminal or CI pipeline without opening the GUI
ISTQB mapping
| Syllabus ref | Topic | Level |
|---|---|---|
| CTFL 4.0 — 2.2 | Component integration testing — testing interfaces between components | Foundation |
| CTAL-TA 3.3 | Structural testing at integration level; API contract verification | Advanced / Senior |
| CTAL-TA 4.2 | Test specification for service interfaces and integration points | Advanced / Senior |
Tips
Read the spec before you test. Start by reading the API documentation (OpenAPI/Swagger) before testing. Treat the spec as your test oracle — if the API doesn’t match the spec, that’s a bug even if the API “works.” Teams often discover that the spec and implementation have quietly diverged, causing silent failures in client applications.
- Test auth first — before testing happy paths, verify that removing or corrupting the auth token returns 401. If it doesn’t, stop and raise it immediately.
- Use environments in Postman — store your base URL, tokens, and IDs in environment variables so your test collection runs against dev, staging, and production with a single click.
- Chain requests — use the ID returned from a POST as input to the subsequent GET, PUT, and DELETE tests. This is more realistic than hardcoded IDs and finds sequencing bugs.
- Test the boundaries of optional fields — what happens when an optional field is null? Omitted entirely? An empty string? These three are often handled differently.
- Check idempotency — send the same PUT request twice. The second call should return the same result and not create a duplicate record.
Practice this technique: Try Test Lead Practice 06 — API contract bugs.
NZ example — testing a NZ payments API
A NZ-specific payments API (such as one built on the Payments NZ API Centre standards) has several NZ-specific test cases beyond the generic HTTP contract tests.
- Bank account format validation — NZ bank accounts follow the BB-bbbb-AAAAAAA-SS format (bank-branch-account-suffix). The API should accept
06-0000-0000000-00(Southern Bank) but reject1234567890123456(card number format). Test the API directly with both formats and verify the response codes and error messages. - Currency — the API should only accept NZD amounts. Test with
"USD"in the currency field — expect a 400 or 422 with a clear error. - Amount precision — NZD amounts must have exactly 2 decimal places. Test
100,100.0,100.00,100.000— only100.00and100.0(if normalised) should succeed;100.000should be rejected or normalised. - POLi integration — POLi is a NZ/AU bank-to-bank payment method. If the API supports POLi, test the redirect flow, the callback handling on success/failure/timeout, and what happens if the bank session expires mid-flow.
- IBAN — NZ does not use IBAN. Any API that requires or accepts IBAN for NZ accounts has a design defect — test for this.
4 Industry Reality
- The spec is always out of date. OpenAPI documents are written at design time and rarely updated as the API evolves. Senior testers read the spec as a starting point, then compare it against what the API actually does and raise discrepancies as defects. Treating a stale spec as gospel is how you miss the real behaviour.
- Auth tokens expire mid-test-run. That collection you built in Postman worked fine yesterday. Today every request returns 401 because the OAuth token expired at midnight. Real test suites include a pre-request script that refreshes the token automatically; juniors manually paste a new token every morning.
- Environments are never quite right. Dev has no real payment gateway. Staging has stale data that doesn’t match production schema. Production is off-limits (usually). You learn to test with mocks and stubs for external services, and to maintain your own test data rather than depending on what the environment happens to contain.
- Shared test data causes flaky tests. Five testers running API tests against the same dev environment, all using the same order IDs, means tests fail randomly because someone else deleted or modified the record. The fix is test isolation: each test creates its own data and cleans it up. In NZ government and finance projects this is especially fraught because test environments often contain production-like data that can’t be freely created or deleted.
- Most teams skip negative tests under time pressure. Happy-path API tests get written. The auth-missing, IDOR, and invalid-input scenarios get deferred to “later” and never written. Senior testers push back on this specifically for authorisation tests, because those are the ones that turn into breach headlines. If you only have time for one negative test per endpoint, make it the one where you remove the auth token.
Senior engineer insight
I spent six months on a HealthNZ integration project where every sprint ended the same way: the UI looked perfect, the team was happy, and the API was quietly broken. We had an endpoint that returned a patient’s appointment history — GET /patients/{nhi}/appointments — and nobody had ever sent a request with a different NHI than the logged-in user. One afternoon I swapped the NHI in Postman and got back someone else’s appointments in a 200 response. That was the moment I stopped treating API testing as an optional extra and started treating auth-and-IDOR as the mandatory first tests for every endpoint, before I even look at the happy path. The most serious bugs in NZ health and government APIs are never in the fields the UI fills in — they’re in the ID parameters the UI never varies.
The most common mistake I see from graduates is writing ten happy-path tests and zero negative tests — they prove the API works when everything goes right, which is the only scenario an attacker will never use.
From the field
On a central government project integrating with the AoG API standards gateway, the team assumed every endpoint was protected because the RealMe SAML assertion was validated at the gateway layer. What nobody had checked was whether the downstream services enforced their own authorisation — they just trusted whatever the gateway forwarded. During exploratory testing I replayed a legitimate JWT with a different user's Revenue NZ number in the sub claim and got back that user's tax position in a 200 response. The gateway had authenticated the token correctly; the downstream Revenue NZ integration API had simply never been built to check whether the subject in the token matched the resource being requested. The lesson: gateway auth and endpoint auth are two separate things. In NZ government API architectures where AoG-standardised gateways sit in front of legacy core systems, you must test authorisation at every layer, not just at the edge.
5 When to Use It — and When Not To
✓ Use it when
- The UI is not built yet — API testing lets you verify business logic independently of frontend progress, and find bugs before they compound through layers
- You need to test authorisation at scale — IDOR, cross-tenant access, and privilege escalation are only reliably caught by sending direct requests, not by clicking UI screens
- You’re testing a service integration — does Service A send the payload that Service B expects? Only a direct API test answers this without noise from the UI
- Regression testing after backend changes — a Postman collection or REST-assured suite runs in seconds and gives you instant confidence that existing contracts haven’t broken
- Performance or load testing is required — driving load through a browser UI introduces artificial bottlenecks; hitting the API directly gives you true throughput numbers
✗ Skip it when
- The entire application is a third-party black box with no API access and no spec — you have nothing to test against; use UI-driven exploratory instead
- You’re testing UI-only concerns — layout, accessibility, visual rendering, and navigation flow are not API questions; don’t write an API test to check that a button looks right
- The team has no spec and no contract at all — testing without a defined contract means you’re just observing current behaviour; spend the time getting a spec written first
- You’re already covered by contract tests — if the team runs Pact or a similar contract-testing tool across every consumer/provider pair, duplicating that in a separate API collection wastes maintenance budget
- The endpoint is deprecated and already removed from all call sites — testing dead code is low-value; document the removal and move on
Context guide
How the right level of API Testing effort changes based on project context.
| Context | Priority | Why |
|---|---|---|
| Regulated government or finance system (NZISM, AoG API standards, Privacy Act 2020) | Essential | IDOR and missing-auth bugs in Revenue NZ, Benefits NZ, CoverNZ, or HealthNZ APIs carry Privacy Act 2020 consequences; direct endpoint testing is the only reliable way to catch them before a Privacy Commissioner investigation does. |
| Microservices or third-party integrations (Payments NZ, RealMe, open banking) | Essential | Service boundaries are the riskiest part of a distributed system; API tests are the only way to confirm that payload formats, field names, and error-handling contracts match what each side expects. |
| Mobile backend or SPA with a dedicated API layer | High | The mobile app and web frontend share the same API; a contract break that passes UI automation on one client silently breaks the other — only direct API tests catch schema drift before it reaches both release channels. |
| Agile sprint team with CI/CD pipeline | High | API tests run in milliseconds and give per-PR feedback without a browser; they are the highest-value test layer to wire into CI early in a sprint cycle, well before an E2E suite is stable enough to run reliably. |
| Legacy migration or parallel-run (replacing a monolith with microservices) | Medium | The old and new endpoints must return identical responses; API tests are the right tool for parity checks, but the main risk is that the legacy system has no spec — invest time writing a contract before writing tests against it. |
| Small startup or MVP with a single-developer backend and no OpenAPI spec | Low | Without a defined contract, automated API tests only record today’s behaviour; the highest-value investment at this stage is exploratory API testing to document what the endpoints actually do, then get that signed off as the spec before building a suite. |
Trade-offs
What you gain and what you give up when you choose API Testing.