API documentation speaks curl. Almost every reference page has a command you can copy, and the quickest way to learn what an endpoint really returns is to run it. But the raw response is the least useful view of it: a wall of JSON where the structure - which fields exist, how deep they sit, which are arrays - has to be worked out by eye.
This walkthrough runs three real, public, keyless APIs through JSON Keyper's online cURL runner and extracts the keys from each response. Each one turns out to have a shape worth knowing about before you write code against it, and in each case the key list shows it in seconds.
The workflow
- Open the cURL runner and paste the command. A preview shows the method, URL, headers, and body that will be sent.
- Click Execute. A proxy makes the request - browsers cannot call most APIs directly - and returns the status, headers, and body.
- Click Extract Keys. Every key path in the response appears in the output, and the Format menu switches it to typed paths, JSONPath, a tree, or a TypeScript type.
The proxy only reaches public addresses, gives up after 5 seconds, and handles responses up to 2 MB. The cURL runner page lists every limit and exactly what the API receives; the examples below all fit comfortably.
Example 1: a weather API that returns columns, not rows
Open-Meteo is a free forecast API that needs no key. This asks for current conditions and an hourly forecast for Berlin:
curl "https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41¤t=temperature_2m,wind_speed_10m&hourly=temperature_2m&forecast_days=1"
It returns about 1 KB of JSON. Extracted as typed paths, with array indices collapsed, it comes to 23 lines:
latitude number
longitude number
generationtime_ms number
utc_offset_seconds number
timezone string
timezone_abbreviation string
elevation number
current_units object
current_units.time string
current_units.interval string
current_units.temperature_2m string
current_units.wind_speed_10m string
current object
current.time string
current.interval number
current.temperature_2m number
current.wind_speed_10m number
hourly_units object
hourly_units.time string
hourly_units.temperature_2m string
hourly object
hourly.time array
hourly.temperature_2m array
Two design decisions jump out of that list that are easy to miss in the raw response:
- The hourly forecast is columnar. There is no array of hour objects.
hourly.timeandhourly.temperature_2mare two parallel arrays of 24 values each, and the reading for an hour is the value at the same index in both. Code that expectshourly[0].temperaturewill not find it; you zip the arrays instead. - Units live beside the data, not in it. Every object has a twin with
_unitson the end and the same keys. That explains a pair that otherwise looks like a mistake:current.intervalis a number (900) whilecurrent_units.intervalis a string - it is the word"seconds".
Switching the format to TypeScript interface turns the same response into a type you can paste into a client. For a deeper walk through reading structure this way, see understanding nested JSON.
Example 2: sending JSON with a POST
Reading is half of most integrations. JSONPlaceholder is a public test API that accepts writes, which makes it a safe place to try one. curl's --json option sends a JSON body and sets both the Content-Type and Accept headers in one go:
curl --json '{"title": "Hello", "body": "First post", "userId": 1}' https://jsonplaceholder.typicode.com/posts
The preview shows it becoming a POST with both headers set, before anything is sent. The response is 201 Created:
{
"title": "Hello",
"body": "First post",
"userId": 1,
"id": 101
}
The new field is id - the server assigned it, which is exactly what a client needs to read back after creating something. (JSONPlaceholder only pretends to save: the same request returns 101 every time.) The same pattern works with -X POST, -H "Content-Type: application/json", and -d '...', which is how older documentation writes it.
Example 3: a search, and why one response is not enough
Open Library's search API takes a query string. curl's -G option turns -d data into URL parameters, and --data-urlencode encodes the spaces in the query for you:
curl -G https://openlibrary.org/search.json \
--data-urlencode 'q=the lord of the rings' \
-d limit=3 \
-d fields=key,title,author_name,first_publish_year,ratings_average,subject
The preview shows the URL that will actually be requested, ending in ?q=the+lord+of+the+rings&limit=3&... - exactly what curl itself sends. The response has 26 key paths, and two of them are worth a second look:
- The total appears twice. The response has both
numFoundandnum_found, holding the same number - over a thousand results, at the time of writing. When an API carries two spellings of one field, pick one and use it everywhere; mixing them is how two parts of one codebase end up disagreeing. offsetisnull. Generate a TypeScript type from this response and you getoffset: null- a field that can apparently never hold a value.
That second point is the classic trap of building a type from one sample. So fetch the second page - the same command with -d offset=3 added - and paste both responses into JSON Diff. Its Structure view reports exactly one change:
offset No longer null null → number
offset was only null because the first page had no offset. The Changes view also gets the rest right: start and offset changed, and three books left the page while three others arrived - matched by Open Library's own key field rather than by position, so no book is mistaken for a changed version of another.
The lesson generalises: a type generated from one response describes that response. Two responses with different content - a first and a later page, an empty and a full record - tell you far more. The JSON to TypeScript page covers how merging several records marks fields optional and nullable.
Copying a command from your browser
The best source of a curl command is often the site you are already using. In Chrome, Edge, or Firefox, open DevTools, go to the Network tab, right-click a request, and choose Copy → Copy as cURL (Firefox lists it under Copy Value). You get the exact request the page made, headers and all.
Read it before you paste it anywhere. A request copied from a signed-in session includes your cookies and any authorization header, which together are enough to act as you. Delete the Cookie header (or the -b option) and any token you do not want to share; most public endpoints work without them. Anything left in the command passes through the proxy on its way to the API.
When a command fails
Most failures fall into a few cases, and the message tells you which:
- A
localhost, private, or VPN-only address is refused by design - run curl locally instead. - A slow endpoint times out after 5 seconds.
- An API that limits requests per IP address may already have been reached by other traffic through the proxy's shared address. Send your own token, if the API offers one.
- A command that reads files -
-F,-T,-d @file- cannot work from a web page, and is refused with an explanation rather than sent in some other form.
Doing the same in a terminal
If you have curl and jq installed, the same key list is one pipe away:
curl -s "https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41¤t=temperature_2m,wind_speed_10m&hourly=temperature_2m&forecast_days=1" \
| jq -r 'paths | select(last | type == "string")
| map(if type == "number" then "[\(.)]" else "." + . end)
| join("") | ltrimstr(".")'
On the weather response it prints the same 23 paths. That is the better choice in scripts and CI; the browser route wins when you want types, a TypeScript interface, or a comparison without writing any of it. How to extract all keys from a JSON object compares the jq approach with JavaScript, Python, and Ruby, including two widely shared jq commands that quietly give the wrong answer.
Further reading
- Run a cURL command online - supported options, what the API receives, and every limit
- How to extract all keys from a JSON object - six methods, tested on awkward JSON
- Why your browser blocks API calls - the reason a proxy is needed at all
- Common JSON structures in REST APIs - envelopes, pagination, and errors