TestingAPIs is still being built. If something breaks, email support@testingapis.com.

Sending requests with curl and Postman

The two tools you will use every day, and when to pick which.

So far you have sent requests from a browser and from the Send buttons on these pages. That works for simple GET requests and nothing else. To choose the method, set headers or send a body, you need a proper client. Almost every tester ends up using two: curl on the command line, and a graphical client such as Postman. This lesson covers enough of each to get through the rest of the tutorial.

curl

curl is a command-line program that sends a request and prints the response. It is installed by default on macOS, on most Linux systems and on Windows 10 and 11. Check with curl --version. It is also the format developers use to share requests: API documentation, browser developer tools and Postman can all show or export a request as a curl command, so you will need to read them even if you prefer another tool.

Without options, curl prints only the body. Add -i to include the status line and response headers, which you almost always want when testing:

Terminal
curl -i https://api.testingapis.com/api/health
Output
HTTP/1.1 200 OK
content-type: application/json; charset=utf-8
date: Sun, 11 Oct 2026 11:47:45 GMT
server: Kestrel
x-powered-by: TestingAPIs
transfer-encoding: chunked

{"status":"healthy","uptimeSeconds":76890,"timestamp":"2026-10-11T11:47:46Z"}

For more detail, -v (verbose) also prints the request curl sent, marked with >, and the response headers marked with <. Lines starting with *describe the connection. Use it when a request doesn't behave the way you expect and you want to see exactly what went over the wire.

Terminal
curl -v https://api.testingapis.com/api/health
Output (trimmed)
* Host api.testingapis.com:443 was resolved.
* IPv4: 135.13.233.203
* Established connection to api.testingapis.com (135.13.233.203 port 443)
* using HTTP/1.x
> GET /api/health HTTP/1.1
> Host: api.testingapis.com
> User-Agent: curl/8.18.0
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 200 OK
< content-type: application/json; charset=utf-8
< date: Sun, 11 Oct 2026 11:49:47 GMT
< transfer-encoding: chunked
...

curl added three headers you didn't ask for: Host, User-Agent and Accept. Every client adds some defaults, and different clients add different ones. When the same request works in Postman and fails in curl, a default header is often the reason.

Method, headers and body

Three options cover most requests. -X sets the method. -H adds a header and can be repeated. -d sends a body. This sends a POST with a JSON body to an endpoint that echoes back what it received:

Terminal
curl -X POST https://api.testingapis.com/api/methods/post \
  -H "Content-Type: application/json" \
  -d '{"name":"Asha","role":"tester"}'
Output
{"method":"POST","message":"POST request received","path":"/api/methods/post","query":{},"contentType":"application/json","bodyLength":31,"bodyTruncated":false,"body":"{\"name\":\"Asha\",\"role\":\"tester\"}","timestamp":"2026-09-30T18:31:16Z"}

The backslash at the end of each line lets the command continue on the next line in bash and zsh. The body is wrapped in single quotes so the shell leaves the double quotes inside it alone. If you forget the Content-Type header, curl sends application/x-www-form-urlencoded, because -d was designed for HTML forms, and many APIs will reject the body.

curl 7.82 and later have a shortcut, --json, which sends the body, sets Content-Type: application/json and Accept: application/json, and switches the method to POST. This one creates a real todo:

Terminal
curl --json '{"title":"Learn curl","completed":false,"userId":1}' \
  https://api.testingapis.com/api/Todos
Output
{"message":"Todo created successfully","data":{"id":31,"userId":1,"title":"Learn curl","completed":false,"priority":"medium","dueDate":""}}

The ID you get back will be different, because other people create todos too. For PUT or PATCH, add -X PUT or -X PATCH alongside --json.

Timing and scripting

-w prints values curl measured, after the response. Combined with -s (silent, no progress bar) and -o /dev/null (throw the body away), it gives you a one-line summary:

Terminal
curl -s -o /dev/null \
  -w "status %{http_code}  total %{time_total}s  size %{size_download} bytes\n" \
  https://api.testingapis.com/api/Products
Output
status 200  total 0.382512s  size 5846 bytes

On Windows, use -o NUL instead of -o /dev/null. time_total includes DNS lookup, connecting and TLS, so the first request is often slower than the ones after it. The performance lesson breaks this number down further.

curl on Windows

Windows needs its own section, because the same command can behave three different ways depending on the shell.

In Windows PowerShell 5.1 (the version that ships with Windows), curl is not curl. It is an alias for Invoke-WebRequest, a PowerShell command with different options, so curl -i fails with a confusing error. Type curl.exe to run the real program. PowerShell 7 removed the alias, but typing curl.exeworks in both, so it's a good habit.

Quoting is the second problem. Windows PowerShell 5.1 strips double quotes inside arguments when it passes them to programs like curl.exe. The command below looks right, and the server receives broken JSON:

PowerShell 5.1
PS> curl.exe -s -X POST https://api.testingapis.com/api/methods/post -H "Content-Type: application/json" -d '{"name":"Asha"}'
{"method":"POST", ... "bodyLength":11, ... "body":"{name:Asha}", ...}

The body arrived as {name:Asha}, which is not JSON. PowerShell 7.3 and later pass the quotes through correctly. On older versions, the least painful fix is to put the body in a file and point curl at it with @. The quotes around "@body.json" stop PowerShell from treating the @ as its own syntax.

PowerShell
# body.json contains {"name":"Asha","role":"tester"}
curl.exe -s --json "@body.json" https://api.testingapis.com/api/methods/post

In the old Command Prompt (cmd.exe), single quotes mean nothing, so wrap the body in double quotes and escape the ones inside with a backslash. The line continuation character is ^ instead of a backslash:

Command Prompt
curl -X POST https://api.testingapis.com/api/methods/post ^
  -H "Content-Type: application/json" ^
  -d "{\"name\":\"Asha\",\"role\":\"tester\"}"

Postman and other GUI clients

A graphical client does the same job as curl with a form instead of a command: pick the method from a dropdown, type the URL, fill in headers and body in tabs, click Send. The response appears below, formatted, with the status, time and size. Postman is the most widely used. Insomnia and Bruno work the same way, and Bruno stores everything as plain files, which some teams prefer because the requests can live in the same Git repository as the code. The ideas below apply to all of them, with Postman names.

Collections

A collection is a saved group of requests, usually one per API, with folders inside it. It is the reason to pick a GUI client over curl for a project you work on for months. You build up the requests once and share the collection with the team.

You don't have to build it by hand. This API publishes an OpenAPI document, a machine-readable description of every endpoint, at https://api.testingapis.com/openapi/v1.jsonsign in to see. In Postman, choose Import, paste that URL, and it creates a collection with a request for each endpoint. Most APIs you test at work will have a similar file, and it is the fastest way to get started on an unfamiliar one.

Environments and variables

Real projects run the same API in several places: a developer's laptop, a test server, staging, production. The requests are the same apart from the base URL and the credentials. Postman handles this with environments. Create one called "testingapis" with a variable baseUrl set to https://api.testingapis.com, then write your request URLs as {{baseUrl}}/api/Products/1. Switching environment switches every request at once. Put tokens in variables too, so they aren't copied into dozens of requests.

Tests in Postman

Each request has a Tests tab (called Scripts, with a post-response section, in newer versions) where you can write JavaScript that runs after the response arrives. For GET {{baseUrl}}/api/Products/1:

Postman test
pm.test("status is 200", function () {
  pm.response.to.have.status(200);
});

pm.test("product 1 has a price", function () {
  const product = pm.response.json();
  pm.expect(product.id).to.eql(1);
  pm.expect(product.price).to.be.a("number");
});

The results appear in a Test Results tab next to the response. The Collection Runner can then run every request in a collection and report which checks failed. This is a gentle first step into automation. The automation lesson covers writing tests as ordinary code, which scales better once you have more than a few dozen.

Which one to use

Both, for different jobs.

SituationBetter choice
Checking one thing quickly, or reproducing a bug reportcurl
Sharing an exact request in a ticket or chatcurl (anyone can paste and run it)
Exploring an API you don't know yetGUI client with the OpenAPI import
Long JSON bodies you edit repeatedlyGUI client
Switching between test and staging serversGUI client environments
Running inside a script or on a build servercurl, or tests written in code
Looking at raw headers and connection detailscurl -v

One more option for this API specifically: each endpoint page in the API reference has a form for sending the request and shows a ready-made curl command you can copy. That is often the quickest way to try an endpoint before you save it anywhere.

  • When a bug report includes a request, attach it as a curl command. The developer can run it without importing anything, and there is no doubt about which headers were sent.
  • Postman can convert in both directions. Paste a curl command into the Import dialog to get a request, or use the Code button on a request to get curl.

Exercise

Do these with curl, then repeat them in a GUI client.

  • Send GET /api/echo/headers and compare the headers curl sends with the ones your GUI client sends. Which headers did each tool add on its own?
  • Create a todo with POST /api/Todos, then fetch it with GET /api/Todos/ followed by the ID from the response.
  • Send the same POST without the Content-Type header. What status and body come back?
  • Use -w to time /api/Products and /api/Products?delay=2. The delay parameter makes the server wait, so the second should take about two seconds longer.

In your GUI client, create an environment with a baseUrl variable and write one test that fails on purpose, so you know what a failure looks like before you rely on a pass.

Practise and go further