Public APIs
All posts

How to Test an API (Step by Step, With Free APIs)

Quick answer: test an API by sending a request and asserting four things: the status code, the response shape, the error behaviour, and the latency. Start by hand with curl or a REST client, assert the response against a schema, then move the same checks into CI so they run on every deploy. Free public APIs are the fastest way to practise.

API testing has a reputation for being heavier than it is. You do not need a test pyramid diagram or a paid platform to get real value. You need a request, a set of assertions, and somewhere to run them automatically. Everything below builds up in that order.

What are you actually testing?

An API test checks the contract between your service and everyone who calls it. Four assertions cover most of the value:

  1. Status codes. A successful read returns 200, a create returns 201, a missing record returns 404. Getting these wrong breaks every client that trusts them.
  2. Response shape. The fields exist, the types are right, required fields are never null. This is where most real regressions hide.
  3. Error behaviour. Bad input returns a 4xx with a useful message, not a 500 and a stack trace.
  4. Latency. The call completes inside a budget you have actually decided on.

Anything beyond those four (load testing, fuzzing, contract testing between services) is worth adding later, once these run on every commit.

How do you test an API by hand?

Start with curl, because it is everywhere and it forces you to see the raw exchange:

curl -i -X GET "https://ipapi.co/8.8.8.8/json/"

The -i flag prints the response headers, which is where the status code, content type, and rate-limit headers live. Most people skip headers and then wonder why a client breaks.

Once the happy path works, deliberately break it. Send a malformed body, drop a required field, pass a string where a number belongs, and send an expired or missing token. An API that returns 200 with an empty body for a bad request is a bug you want to find yourself.

For anything more than a couple of calls, move to a REST client. Hoppscotch runs in the browser with nothing to install, Bruno keeps collections as plain files in your repo (which means they diff in code review), and Postman remains the most featureful if you want its collection runner. HTTPie is the friendlier curl if you live in the terminal.

What should an automated API test assert?

Move the manual checks into code as soon as they stabilise. The important shift is asserting the schema, not individual fields, so a renamed or newly-nullable field fails loudly:

const res = await fetch("https://api.example.com/users/1")
expect(res.status).toBe(200)
const user = await res.json()
expect(user).toMatchObject({ id: expect.any(Number), email: expect.any(String) })

Any test runner works. The tests are just HTTP calls, so Vitest, Jest, pytest, or Go's standard library are all fine. Run them in CI on every pull request, and run a small subset against production on a schedule as a smoke check.

Two rules save a lot of pain. Keep tests independent, so each one creates the data it needs and cleans up after itself. And never let a test depend on a third-party API being up, or a vendor outage becomes your failing build.

Where can you get APIs to practise on?

This is where a directory helps, and where we can be specific rather than hand-wavy. Of the 1,624 public APIs currently listed on publicapis.dev, 653 (about 40%) require no authentication at all. No signup, no key, no OAuth dance, which makes them ideal for practising requests, writing your first assertions, or demoing a client library.

Three numbers from the same dataset are worth knowing before you pick one:

  • 236 of those keyless APIs also confirm CORS support, meaning you can call them straight from browser JavaScript. Browse the development category or test-data APIs for the most practice-friendly options.
  • 148 listed APIs explicitly do not support CORS. These work perfectly from curl or a server, and fail in the browser with an opaque error that looks like the API is down when it is not. If a browser fetch fails but curl succeeds, CORS is your first suspect.
  • 40 are still HTTP-only. Those will be blocked as mixed content by any page served over HTTPS.

ipapi.co is a good first target: keyless, CORS-enabled, and it returns a predictable JSON object that is easy to assert against.

How do you test an API you are building?

The same assertions apply, with two additions.

Test against a running instance, not mocks, for at least one path per endpoint. Mocks verify that your code calls what you told it to call, which is not the same as verifying the endpoint works. Spin the service up in CI, run the suite against it, tear it down.

Second, treat your OpenAPI spec as the source of truth if you publish one. Tools like Schemathesis and Dredd read the spec and generate requests from it, which catches the classic drift where the documentation promises a field the implementation stopped sending. That drift is invisible to hand-written tests, because you wrote both sides.

Frequently asked questions

What is the difference between API testing and unit testing?

A unit test exercises a function in isolation with no network. An API test sends a real HTTP request to a running service and asserts on the response. Unit tests are faster and catch logic bugs; API tests catch integration and contract bugs that unit tests cannot see.

Do I need Postman to test an API?

No. curl plus your existing test runner covers the large majority of cases, and keeps tests in the same repo and CI pipeline as your code. A GUI client is a convenience for exploring an unfamiliar API, not a requirement.

How do I test an API that requires authentication?

Use a dedicated test account and store its credentials as CI secrets, never in the repo. Test the unauthenticated case too: calling a protected endpoint without a token should return 401, and with a valid token for the wrong user should return 403. Those two assertions catch a surprising number of real access-control bugs.

How often should API tests run?

On every pull request for the full suite, and on a schedule (hourly or daily) for a small smoke subset pointed at production. The scheduled run is what tells you an endpoint broke because of a config change rather than a code change.

What status code should a validation error return?

422 when the request is syntactically valid but semantically wrong, 400 when the request itself is malformed. Either is defensible if you are consistent, and consistency is the part your clients actually depend on.