Reporting
Turn a nested API response into the exact spreadsheet somebody asked you for
Loading data is half the job. The other half is the sentence you hear on every project: “can you just send me that in a spreadsheet?”
Today’s version: the Golden Retriever Distribution Center wants everything that’s running low on stock, in Excel.
Everything you need is already in The Training Ground — you put it there in Lesson 2.
Step 1 — Pagination
An API will not hand you everything at once. Ask for 67,000 records and you get the first few hundred and a note saying there are more. That is every API you will ever meet.
Dobermann has a trick up its sleeve. It will walk the pages for you — and as each page comes back, its rows are added to the Completed tab. One click, one table, all of it.
Let’s see it in action. Copy this, open API Catalogue, and click New Endpoint from Clipboard:
// Name: Puppy School — Inventory Report
// Method: GET
// Path: /inventory
// QueryParam: page: {{A8:PAGE:0:totalCount}} [enabled]
// QueryParam: size: {{A8:SIZE:500:pageSize}} [enabled]
How Dobermann knows where to stop
Those two
QueryParamlines are pagination variables, and they are the whole trick.{{A8:PAGE:0:totalCount}}starts at page0and readstotalCountout of each response to work out how many pages there are.{{A8:SIZE:500:pageSize}}asks for500a page and readspageSizeback to check the API agreed.You will rarely type them. On an endpoint that has none, the Pagination button’s Settings tab reads a real response and writes them for you.
Save, and hit Run API. The Console opens with page one — 500 rows of 67,000-odd. Dobermann never walks a whole API without being asked.
So ask. In the Console footer click Pagination, choose Fetch all pages, and click Run.
Pages stream in several at a time and the row count climbs as they land. Dobermann loves to fetch.
Step 2 — Find the Data in the Response
Open the Raw tab and look at any one response. It’s an envelope again, with your records inside it:
{
"totalCount": 67991,
"pageSize": 500,
"page": 0,
"totalPages": 136,
"data": [
{
"sku": "SKU-WOOF-001-00001",
"gtin": "00012345600012",
"description": "Premium Belly Rub Machine",
"quantityOnHand": 12,
"uom": "EA",
"status": "low_stock",
"location": { "gln": "0614141000012", "name": "Golden Retriever Distribution Center", "city": "Atlanta" },
"product": {
"sku": "SKU-WOOF-001", "gtin": "00012345600012", "category": "Grooming & Wellness", "unitPrice": 149.99,
"reorderQty": 200, "supplierSku": "CTM-0001",
"supplier": { "gln": "4012345000016", "name": "Chew Toy Manufacturing Inc" }
}
}
]
}
Your counts will differ from the example — they depend on how much you loaded in Lessons 2 and 3.
Two things worth noticing. The records are in data, as before, and back on the
Completed tab Dobermann has already made each one a row — across every page, as one
table. And each record carries nested objects — location, product, and a supplier
inside the product — because the API resolves the reference data for you rather than
leaving you with bare codes.
Step 3 — Build the View
The table shows everything, which is not the same as showing what was asked for. Nobody wants forty columns of internal identifiers.
Click the view button in the toolbar, then + Create View…
The View Editor opens with three regions:
- Available Columns on the left — the full response tree, searchable
- Selected Columns on the right — what ends up in your table
- The row basis dropdown in the Selected panel header — which array produces one row each
Check the row basis is data, then tick these columns:
| Column | Where it comes from |
|---|---|
sku |
the record — its own stock code, not product.sku, the catalogue’s code for the product |
gtin |
the record — the product’s unique identifier |
description |
the record |
quantityOnHand |
the record |
uom |
the record |
status |
the record — you’re about to filter on it |
location.name |
the nested location object |
location.city |
the nested location object |
product.category |
the nested product object |
product.reorderQty |
the nested product object |
product.supplierSku |
the nested product object — blank for some suppliers, and that’s fine |
product.supplier.name |
two levels down: the supplier, inside the product |
product.supplier.gln |
two levels down |
location.gln |
the nested location object |
gtin and the last five are for Lesson 5, which turns this report into purchase orders.
Notice what you just did. location.name lives one level down in the JSON and
product.supplier.name two, and you’ve pulled them up alongside the record’s own fields as
if they were flat columns. That is the whole point of a view — the API’s shape and the reader’s shape are
almost never the same, and this is where you reconcile them without touching either.
Views work on errors too
Any tab takes a view, the Error tab included — the same three regions. That is how you turn a pile of failures into a table you can hand to whoever produced the file.
Name the view Low Stock by Location and save it.
Views save on the endpoint, so this one travels with it — export or share the endpoint and whoever receives it gets your columns and your row basis, not just the request.
Step 4 — Filter It in the Console
You have every inventory record on screen. The warehouse asked about one site’s low stock. You don’t go back to the API for that — you ask the table.
Click into the search box above the table and type:
low_stock
The row count drops to around eleven thousand: every record, at every site, whose status is
low. Search looks across every column in the view, which is why status is one of them.
Now narrow it to the site that asked. A space and a + means and:
low_stock +Golden
About nine hundred rows — low stock, at the Golden Retriever Distribution Center. That is the report.
One more, because someone always asks. They don’t reorder by the case, so leave the CS
rows out. A space and a - means not:
low_stock +Golden -"CS"
The quotes matter. Search normally matches part of a cell, and the letters “cs” turn up
inside plenty of words — Electronics, Logistics. Quotes mean the whole cell, exactly,
so only a unit of measure of CS is excluded. Try it without them and watch the table
empty.
Four moves cover most of what you’ll ever need, and they light up in colour as you type so you can see Dobermann has understood them:
| You type | It means |
|---|---|
low_stock +Golden |
both |
low_stock -Golden |
the first, without the second |
Golden,Labrador |
either |
"CS" |
the whole cell is exactly this |
There’s more — wildcards, how the pieces combine — in Console → Search.
Take the -"CS" back off, then click the quantityOnHand header to sort ascending. The most
urgent records rise to the top.
Whatever you’ve got on screen is what gets exported: the same columns, the same order, the same sort, the same search. There’s no reshaping afterwards.
Step 5 — Send It to Excel
Open the Export menu and choose Excel.
That’s the spreadsheet the warehouse asked for: the right rows, the right columns, sensible headers, straight out of a live API — and no manual step between the API and the file. Only the rows your search left on screen are in it.
That’s the file you’d send. For the course, there’s a quicker way to show us.
Step 6 — Copy It, and Show Us
Not everything needs a file. Next to Export is Copy, and it puts the same table — same columns, same sort, same search — straight on your clipboard:
| Copy → | Pastes best into |
|---|---|
| Standard | Outlook, Teams, Word, Confluence — arrives as a formatted table |
| Excel | Excel, Google Sheets — keeps leading zeros on codes like GLNs |
| CSV | scripts and anything that wants plain text |
| Markdown | GitHub, Jira, Notion |
Make sure your search still reads low_stock +Golden, open the Copy menu, choose
Excel, and paste it here:
We check it against what the API actually holds for you, and tell you straight away if a row doesn’t belong or one is missing. Pass, and Lesson 5 unlocks.
(Copy → Standard and Copy → Excel stop at 1,000 rows, because pasting more than that tends
to take the other application down with it. If yours is greyed out, the inventory file
probably went in twice — reset with DELETE /my-data, reload it once, and fetch again.)
Bonus Credit — Ask the API a Narrower Question
You just pulled 67,000 records to keep 900. It worked, and it will work on any API — which is why it came first. But when an API can filter, let it. Less data over the wire, fewer pages, less load on a system you’re a guest on.
The Training Ground can. Open Puppy School — Inventory Report, click Duplicate, and on the copy use Add → Query Parameter twice:
// Name: Puppy School — Low Stock Report
// Method: GET
// Path: /inventory
// QueryParam: status: low_stock [enabled]
// QueryParam: locationGln: 0614141000012 [enabled]
// QueryParam: page: {{A8:PAGE:0:totalCount}} [enabled]
// QueryParam: size: {{A8:SIZE:500:pageSize}} [enabled]
Save, Run API, Pagination → Fetch all pages. Two pages instead of a hundred and thirty-odd, and the same rows you filtered your way to in Step 4. The duplicate kept your view, so the table already looks right.
status and locationGln are this API’s parameter names. The next API will call them
something else, accept different values, or not offer them at all — its documentation is
where you find out. Ours is here. The habit to keep is the
order you ask in: filter at the API where you can, and in the Console where you can’t.
🐾 Dobermann Philosophy
An API response and a useful report are different shapes, and every tool makes you choose which one to live with. Named Views let you keep both — the raw response stays untouched, and you build as many lenses over it as you have audiences. Save them once and they belong to the endpoint, not to you, not to your laptop.
🦴 Dig Deeper
Paging exists because no sensible API hands you ten thousand records in one response — it protects the server’s memory and yours. The awkward part is always knowing when to stop, which is why responses carry a total count. Dobermann’s
{{A8:PAGE}}variable reads that count and does the walking for you; see Pagination for the full set.
Check My Homework
To pass: Your filtered report, copied from the Console, pasted into the lesson and verified