Back to BlogQuality & Testing

API Testing: What to Test, Which Tools, and Contract Tests

Rupak Amin

Founder & Lead Engineer, RAITHub

10 min read

API testing checks each endpoint directly, without the UI: the status code, the response shape against your schema, the business rules, who is allowed to call it, how it fails, and whether retries are safe. Write these as code in your repository and run them in CI. Add contract tests when separate teams or services depend on the same API.

If you would rather have the test suite built and run for you, see how RAITHub would test this near the end.

Why test the API instead of only the UI?

Because API tests are faster, more stable and closer to the bug. An end-to-end browser test that fails on "order not created" could be the button, the network, the server or the database. An API test that posts an order and checks the response narrows it to one layer, and runs in milliseconds rather than seconds. That is why API tests make up the middle of a healthy test pyramid; see the test pyramid for a SaaS for the ratios.

The API is also what attackers call directly. The OWASP API Security Top 10 2023 puts broken object level authorisation first: one user reaching another user's records by changing an ID (OWASP API Security Top 10 2023). The UI can hide that button; only an API test proves the server refuses.

What should you test on every API endpoint?

CheckExampleWhy it matters
Happy pathPOST /orders with valid data returns 201 and the new orderThe basic promise of the endpoint
Response schemaEvery field matches the OpenAPI schema: types, required fields, enumsClients break on a renamed or missing field
ValidationMissing, wrong-type and out-of-range input returns 400 or 422 with a useful message, never 500A 500 on bad input usually means unchecked data reached the database
AuthenticationNo token, an expired token and a malformed token all return 401Protected data must never leak to anonymous callers
AuthorisationUser B asking for user A's order returns 403 or 404; a viewer cannot deleteThe commonest serious API flaw
Business rulesCannot refund more than was paid; stock cannot go negativeWhere money and data go wrong
IdempotencyRepeating a payment request with the same idempotency key creates one chargeNetworks retry; customers double-click
Errors and limitsRate limits return 429 with Retry-After; pagination and large payloads behavePredictable failure keeps clients stable
Side effectsThe webhook, email or queue message fires once, with the right dataBugs hide in what happens after the response

Two related guides go deeper: idempotency in API design and API rate limiting. Payment callbacks have their own traps, covered in testing payments and webhooks.

What does a good API test look like in code?

Short, independent, and creating its own data. Playwright can send HTTP calls without a browser, through its request fixture or a separate request context per user (Playwright API testing docs). This example checks the happy path and the authorisation rule that matters most:

// tests/api/orders.spec.ts
import { test, expect, request } from '@playwright/test'

// One API context per user, each with its own token.
const asUser = (token: string) =>
  request.newContext({
    baseURL: process.env.API_URL,
    extraHTTPHeaders: { Authorization: 'Bearer ' + token },
  })

test('a user can create and read their own order', async () => {
  const alice = await asUser(process.env.ALICE_TOKEN!)
  const created = await alice.post('/api/orders', { data: { sku: 'TEST-1', qty: 2 } })
  expect(created.status()).toBe(201)
  const order = await created.json()
  expect(order).toMatchObject({ sku: 'TEST-1', qty: 2, status: 'pending' })

  const read = await alice.get('/api/orders/' + order.id)
  await expect(read).toBeOK()
})

test("a user cannot read someone else's order", async () => {
  const alice = await asUser(process.env.ALICE_TOKEN!)
  const bob = await asUser(process.env.BOB_TOKEN!)
  const { id } = await (await alice.post('/api/orders', { data: { sku: 'TEST-1', qty: 1 } })).json()

  const res = await bob.get('/api/orders/' + id)
  expect([403, 404]).toContain(res.status())
})

Write the second kind of test for every resource and role. It is dull, and it is the test that stops the data leak. Reading the base URL and tokens from environment variables lets the same tests run against local, staging and preview deployments.

Postman, Playwright, REST clients or Pact: which API testing tool?

ToolGood forLess good for
PostmanExploring an API, sharing collections, quick checks written as pm.test scripts; collections run in CI with the Postman CLI (Postman test scripts docs)Large suites with shared setup and code reuse; reviewing changes in pull requests
Playwright (or another code test runner)API tests in TypeScript next to your UI tests, sharing fixtures, auth and CINon-developers editing tests
Schema validation (OpenAPI with a validator)Catching shape drift on every response automaticallyBusiness rules and permissions
Pact (contract testing)Several services or apps owned by different teams that depend on one APIA single app with one frontend and one backend in the same repository
k6Load and latency on the same endpointsFunctional correctness

A common setup: Postman for exploration and manual triage, code tests for the regression suite, schema checks inside those tests, and Pact only where independent deploys make it worth it. For load, see load and performance testing before launch.

What are contract tests, and do you need them?

A contract test checks that two sides of an integration agree, without running both together. Pact describes it as testing an integration point by checking each application in isolation against a shared contract, and in its consumer-driven model the contract is generated by the consumer's tests (Pact docs). The provider then verifies it can meet every contract its consumers published. Only the fields and calls consumers actually use are locked in, so the provider can change everything else freely.

A minimal consumer test with Pact JS, using the V4 interface the docs recommend (Pact JS consumer docs):

import { Pact, Matchers } from '@pact-foundation/pact'
import { getOrder } from '../src/orders-client'

const provider = new Pact({ consumer: 'web-app', provider: 'orders-api' })

it('reads an order', async () => {
  await provider
    .addInteraction()
    .given('order 42 exists')
    .uponReceiving('a request for order 42')
    .withRequest('GET', '/orders/42')
    .willRespondWith(200, (builder) => {
      builder.jsonBody(Matchers.like({ id: 42, status: 'paid', total: 1999 }))
    })
    .executeTest(async (mockServer) => {
      const order = await getOrder(mockServer.url, 42)
      expect(order.status).toBe('paid')
    })
})

You need contract tests when a mobile app, a partner, or another team's service calls your API and deploys on its own schedule. You probably do not when one team ships the frontend and backend together from one repository: an ordinary API test plus shared TypeScript types catches the same breaks with less machinery. For a public API with unknown consumers, contracts do not fit well either; versioning and schema tests do that job, covered in designing a public API for your SaaS.

How do you run API tests in CI without flakiness?

  • Each test creates its own data with unique IDs and does not depend on another test's leftovers.
  • Test users are seeded, one per role, with tokens issued at the start of the run, not hard-coded long-lived secrets.
  • Third parties are stubbed at the boundary (payment provider, email, SMS), and tested separately in sandbox mode.
  • Run on every pull request against a fresh database or preview environment, and block the merge on failure.
  • Quarantine, then fix, flaky tests rather than retrying until green; fixing flaky tests explains the method.

If you know TypeScript and the API, a first suite covering the happy path, validation and authorisation for 10 to 15 endpoints takes about 2 to 4 days. The main risk of doing it yourself is testing only the happy path: the permission and error cases are where the expensive bugs are, and they are the ones teams skip under time pressure.

Buy, build or hire?

OptionChoose this whenWatch out for
A tool or SaaS testing platformYou want API monitors and collections with little codeTests that live outside your repository and drift from the code
Freelancers or crowdtestingYou need a one-off pass on a small APINo one maintains the suite afterwards
An in-house QA hireYou have several services and release weeklyNeeds an engineer who codes, not only a manual tester
A managed QAaaS teamYou want the suite built, wired into CI and kept current monthlyMake sure the tests live in your repository and you own them

Why RAITHub for API testing?

RAITHub builds APIs as well as testing them, and its test counts are in real repositories. TheSkinProof, the founder's own venture, has 217 API endpoints and 750+ tests. Sundor Skin, a B2B wholesale platform, has 530+ tests and 88 permission codes across 12 staff roles, the kind of matrix where authorisation tests earn their keep. If your API itself needs work, see API and backend development. For attack-focused testing of the same endpoints, see security testing; that is application-level testing against OWASP guidance, not a certified penetration test.

How RAITHub would test this

Through QA and test automation, part of QA as a service, RAITHub would:

  • map endpoints, roles and the money and data paths from your OpenAPI spec or the code
  • write API tests for the happy path, validation, authentication, an authorisation matrix per role, idempotency and error handling
  • add schema checks, and contract tests where independent consumers justify them
  • wire the suite into your CI so it blocks merges, with tests and configuration in your repository

Buy it as a monthly QA plan, a fixed-price one-off audit, or a dedicated QA team that RAITHub manages and bills monthly. Testers are never placed under your management. You own the tests and the IP, and an NDA is signed first. Start with a free 15-minute call, then a written fixed quote. Talk to RAITHub about API testing.

Frequently asked questions

What is API testing?

Testing an application's endpoints directly, without the user interface: sending requests and checking status codes, response data, business rules, permissions and error handling.

What is the difference between API testing and contract testing?

API testing checks that an endpoint behaves correctly. Contract testing checks that a consumer and a provider agree on the requests and responses between them, so each can deploy independently without breaking the other.

Is Postman enough for API testing?

For exploration and small suites, often yes, and collections can run in CI with the Postman CLI. Larger suites usually move into code next to the application, where they share setup and go through code review.

How many API tests do I need?

At least one happy-path, one validation and one authorisation test per endpoint and role, plus tests for every business rule that moves money or data. Count risks covered, not tests.

Should API tests hit a real database?

Yes, a disposable test database or preview environment. Mocking the database hides the bugs API tests exist to catch. Stub third-party services at the boundary instead.

Does RAITHub do API security testing?

Yes, as application-level testing against OWASP guidance, including authorisation tests per role. It is not a CREST- or PCI-certified penetration test and produces no compliance attestation.

API testingcontract testingPactPostmanPlaywright API testingOWASP API Security

Ready to discuss your project?

Book a free 15-minute technical audit with our engineering team.