Google Sheets has no native JSON import. Ask how to pull an API into a sheet and the standard answers are: paste a 200-line ImportJSON Apps Script from a decade-old gist, install an add-on, or copy-paste and hope the data never changes. All of that machinery exists to work around one gap โ because the function Sheets does ship, IMPORTDATA, only speaks CSV and TSV.
So serve the API as CSV. Every Mockbird endpoint does that natively: add ?mock_format=csv to any list or single-record GET and you get RFC-4180 CSV with a header row โ columns picked server-side, sorting and filtering done in the URL, all the simulation params still available. One formula, zero scripts, zero add-ons, no signup. Useful for prototyping a dashboard before the real backend exists, teaching a spreadsheet class against realistic shared data, or giving formula practice targets that actually change. Every curl below was verified against production before publishing.
The public demo project is a seeded e-commerce API. In any sheet, paste:
=IMPORTDATA("https://mockbird.mockbird.workers.dev/m/demo/products?mock_format=csv&select=name,price,category,inStock&limit=100")
The first time a sheet uses an import function, Sheets shows a warning banner (the sheet is about to talk to an external URL) โ click Allow access and the table fills in: a header row plus one row per product, spilling down and across from the formula cell. What the sheet received is exactly what curl receives:
curl 'https://mockbird.mockbird.workers.dev/m/demo/products?mock_format=csv&select=name,price,category,inStock&limit=3'
# id,name,price,category,inStock
# 1,Bridge Area Night,358.76,clothing,true
# 2,World Thing System Point,153.79,toys,false
# 3,Field Thing Money,99.19,beauty,false
Response content-type is text/csv; charset=utf-8. Sending Accept: text/csv instead of the query param works too (for clients that can set headers โ Sheets can't, which is why everything in this guide rides in the URL).
?select= projects the columns server-side: id always comes first, then your fields in the order you listed them. That means column order in the sheet is stable and chosen by you โ no QUERY(A:H, "select B, D") reshuffling after import:
# just name + price, in that order after id
curl 'https://mockbird.mockbird.workers.dev/m/demo/products/1?mock_format=csv&select=name,price'
# id,name,price
# 1,Bridge Area Night,358.76
That's a single-record GET โ one row plus header, handy for a lookup cell. Nested values (objects/arrays in a record) are JSON-stringified into their cell rather than exploded.
The usual pattern is IMPORTDATA-everything-then-QUERY-it. You can skip the second step: the mock applies filters, sort, and free-text search before generating the CSV, so the import is already the view you want โ and smaller imports keep the sheet fast (Google's own guidance is to minimize what import functions pull).
# top 3 by price, descending
curl 'https://mockbird.mockbird.workers.dev/m/demo/products?mock_format=csv&select=name,price&sortBy=price&order=desc&limit=3'
# id,name,price
# 7,Mountain Part Law,926.11
# 17,House Game Year Place Thing,913.03
# 10,Problem Law Line Car Problem,792.85
# range filter: only products โฅ 900
curl 'https://mockbird.mockbird.workers.dev/m/demo/products?mock_format=csv&select=name,price&price_gte=900'
# id,name,price
# 7,Mountain Part Law,926.11
# 17,House Game Year Place Thing,913.03
# substring filter + free-text search work too
โฆ?mock_format=csv&category_like=cloth
โฆ?mock_format=csv&q=law
All three verified above against production. The full toolkit โ exact-match field filters, _gte/_lte/_gt/_lt/_ne/_like suffixes, q= search โ is in the docs parameter table, and every bit of it composes with mock_format=csv.
Because the URL is the query, you can also build it from cells the spreadsheet way โ no code, just string concatenation into IMPORTDATA:
=IMPORTDATA("https://mockbird.mockbird.workers.dev/m/demo/products?mock_format=csv&select=name,price&category=" & B1)
Change the category in B1, the import refetches with the new URL. (One caveat below: the cell you reference can't contain a volatile function like NOW().)
List endpoints page at up to 100 rows per request (limit โค 100, default page size smaller โ always pass an explicit limit). For more, add page=2, page=3โฆ in stacked formulas a few rows apart, or in separate tabs. Resources hold up to 1,000 records, so ten formulas covers the worst case. The JSON API exposes the total in an x-total-count response header, but Sheets can't read headers โ another reason this guide keeps everything in the URL and the body.
The demo is shared and reseeds daily. Your own project takes one curl (or one click), no signup:
# File โ Download โ CSV in Sheets, then:
curl -s -X POST 'https://mockbird.mockbird.workers.dev/api/projects/import?resource=people' \
--data-binary @people.csv
# โ {"id":"abc123xyz9", "adminKey":"โฆ", ...}
Rows become records with typed fields (CSV import guide), and the collection is immediately a REST API โ which your sheet can IMPORTDATA right back, now with server-side filters, a data editor, and the same data feeding Power BI, Grafana, or a teammate's app. Sheet โ API โ many sheets is a genuinely useful demo-day trick: edit a record in the dashboard, and every sheet importing it picks the change up on its next refresh.
Google documents the rules, and they surprise most people:
| Action | Refetches? |
|---|---|
| Sheet stays open | โ automatically, roughly every hour |
| Delete the formula cell and re-add it (or overwrite it with the same formula) | โ immediately |
| Close and reopen / reload the sheet | โ does not trigger a refresh |
And the classic cache-buster trick โ appending &x=" & NOW() to the URL โ is explicitly blocked: import functions may not reference volatile functions (NOW, RAND, RANDARRAY, RANDBETWEEN), and the cell shows #ERROR! if you try. Google's documented exception is TODAY(), which updates at most once a day. The reliable manual refresh is the unglamorous one: cut the formula cell, paste it back.
A sheet that feeds on an external URL will eventually meet that URL failing โ and IMPORTDATA surfaces that as an in-cell error that wipes out the imported range. Wrap it before that happens, and use mock_status to prove the wrapper works:
=IFERROR(
IMPORTDATA("https://mockbird.mockbird.workers.dev/m/demo/products?mock_format=csv&select=name,price&limit=100&mock_status=500"),
"source down โ showing nothing rather than something wrong")
Verified server-side: mock_status=500 makes that URL return HTTP 500 (body: {"error":"simulated 500 error (mock_status)"}), so the import fails and the fallback string renders. Delete &mock_status=500 and the data comes back on the next refetch. That's the whole error-handling test loop for a spreadsheet, run on demand instead of during a real outage.
IMPORTDATA sends a bare GET โ no way to attach an Authorization header. For a project in protected mode, Mockbird accepts the token as a query param exactly for header-less clients like this:
# mint a token (any email/password โ it's a mock; max lifetime 7 days)
curl -s -X POST https://mockbird.mockbird.workers.dev/m/YOUR_PROJECT/auth/login \
-H 'content-type: application/json' \
-d '{"email":"sheets@example.com","password":"x","expiresIn":604800}'
# โ {"token":"eyJโฆ", ...}
=IMPORTDATA("https://mockbird.mockbird.workers.dev/m/YOUR_PROJECT/products?mock_format=csv&mock_token=eyJโฆ")
Verified above: without the token the CSV URL returns 401; with ?mock_token= it serves the rows. Honest caveat: tokens expire (7 days max), so a long-lived sheet needs a re-mint โ put the token in one cell and reference it from the formula so it's a one-cell fix. If the sheet is shared, remember the URL (token included) is visible to every viewer; for classroom data, an unprotected project is simpler.
Fair's fair. If the data lives in another spreadsheet, use IMPORTRANGE; if it's in BigQuery and you're on Workspace Enterprise, Connected Sheets is the real thing. If you need to sync a third-party JSON API you don't control into Sheets on a schedule, that's Apps Script UrlFetchApp or a connector product โ IMPORTDATA can't send headers or parse JSON, and Mockbird only reshapes APIs it hosts. And the hourly refresh cadence means none of this is a realtime feed. Reach for the mock when the point is a live, controllable, realistic dataset in a sheet โ prototyping, teaching, testing formulas against data that changes โ without writing or trusting any script.
Related: CSV โ REST API (the other direction), the Excel version of this guide (From Web + Power Query), the Power BI / Power Query version of this guide, the Grafana version, the Streamlit version, and testing loading and error states.
Verification: all demo curls on this page (the CSV list with select=name,price,category,inStock, the single-record CSV, sortBy=price&order=desc top-3, the price_gte=900 range filter, category_like and q= variants, Accept: text/csv content negotiation, limit=100 paging, mock_status=500 returning HTTP 500, and a protected-project 401-without / 200-with-?mock_token= drill against a scratch project, deleted after) were run against production on 20 Sep 2026 before publishing. Demo data reseeds daily, so the literal values in your import will differ from the outputs shown. Google Sheets behavior โ IMPORTDATA's CSV/TSV-only scope and syntax, the first-use Allow-access banner, the hourly auto-refresh while a document is open, reload-not-refreshing, the volatile-function restriction with the TODAY exception โ is from Google's official Docs Editors documentation as of September 2026. We don't run your Google account; if a step here doesn't work in your sheet, that's a bug: tell us.