← All guides

Mock APIs for Grafana β€” practice the Infinity data source against an API you control

The Infinity data source is Grafana's go-to plugin for pulling REST APIs into dashboards β€” and its own JSON docs demo against jsonplaceholder.typicode.com/users. That's fine for a first render, but JSONPlaceholder is a fixed dataset with no timestamps, no pagination worth configuring, no envelopes, and no failure modes β€” which is to say, none of the things that actually make an Infinity query hard. Root selectors, time-field parsing, pagination settings, and how a panel degrades when the API has a bad day: rehearsing those needs an API you can shape.

This guide points Infinity at a hosted, stateful mock instead: real endpoints with ISO 8601 timestamps, a response shape you can reshape from a URL param, pagination in two flavors, and failure modes on demand. No signup; works in Grafana OSS, Cloud, and Enterprise alike. If you don't have Infinity yet: docker run -p 3000:3000 -e "GF_INSTALL_PLUGINS=yesoreyeram-infinity-datasource" grafana/grafana:latest. Every curl below was verified against production before publishing.

1. First panel in 60 seconds

Add an Infinity data source (no config needed for public URLs), create a panel, set query Type: JSON, Source: URL, and paste:

https://mockbird.mockbird.workers.dev/m/demo/orders

The public demo project is a seeded e-commerce API. The response is an array at the root, so β€” exactly like the JSONPlaceholder example in Infinity's docs β€” no root selector or column config is required; the plugin auto-detects the fields. Switch the panel to Table and you're looking at 25 orders with id, orderNumber, customerId, total, status, placedAt. Verify from a terminal:

curl -s 'https://mockbird.mockbird.workers.dev/m/demo/orders?limit=2&sortBy=placedAt&order=asc'
# β†’ [ {"id":4, "total":243.27, "status":"cancelled", "placedAt":"2025-09-25T03:40:24.880Z"}, … ]

2. A time series that obeys the time picker

placedAt is ISO 8601 β€” which matters, because Infinity's backend parsers (JSONata/JQ, the ones you need for alerting and server-side processing) expect ISO 8601 timestamps unless you supply a custom Go time layout. Set the parser to JSONata or JQ, format Time series, add columns: placedAt as Time, total as Number β€” and orders plot over time.

Now make the query respect the dashboard time picker. Grafana interpolates its global time-range variables into Infinity URLs, and the mock supports range filters on any field (_gte/_lte suffixes, lexicographic β€” ISO dates compare correctly):

https://mockbird.mockbird.workers.dev/m/demo/orders?placedAt_gte=${__from:date:iso}&placedAt_lte=${__to:date:iso}&sortBy=placedAt&order=asc

Zoom the dashboard and the mock does server-side range filtering β€” the same contract a real time-windowed API gives you. Terminal check:

curl -s 'https://mockbird.mockbird.workers.dev/m/demo/orders?placedAt_gte=2026-01-01T00:00:00Z&placedAt_lte=2026-12-31T23:59:59Z&sortBy=placedAt&order=asc'
# β†’ only this year's orders, oldest first

3. Root selector reps β€” against any envelope shape

Real APIs rarely hand you an array at the root; the data hides in {"data": [...]} or worse, and the Root selector is where Infinity queries actually get written. The mock reshapes its response from a URL param, so you can practice against the exact envelope your real API uses:

curl -s 'https://mockbird.mockbird.workers.dev/m/demo/orders?mock_envelope=data&limit=2'
# β†’ {"data":[ {…}, {…} ]}
ParserRoot selector for the shape above
JSONata$.data
JQ.data
UQLparse-json | scope "data"

Need the messier envelope with metadata alongside? mock_envelope also takes a URL-encoded JSON template β€” {"data":"$data","page":"$page","total":"$total","hasMore":"$hasMore"} returns exactly that, with the placeholders filled server-side. Details in the docs.

4. Pagination rehearsal β€” page-number and cursor

Infinity supports offset, page-number, and cursor pagination β€” and caps pagination queries at 5 pages by default (raise it with the GF_PLUGIN_PAGINATION_MAX_PAGES environment variable, per their docs). That cap is exactly the kind of thing you want to discover against a mock rather than a production API. Page-number mode maps to the mock directly:

curl -s 'https://mockbird.mockbird.workers.dev/m/demo/orders?_page=2&_limit=5'
# β†’ records 6–10 of 25; every list response carries an x-total-count header

For cursor mode, the mock speaks Stripe/Slack-style cursors β€” the response body carries the field Infinity's cursor pagination reads:

curl -s 'https://mockbird.mockbird.workers.dev/m/demo/orders?mock_cursor=1&limit=10'
# β†’ {"data":[…10 records…], "next_cursor":"eyJ2IjoxLCJvIjoxMH0", "has_more":true}
# continue with ?cursor=<token>; a tampered token β†’ 400

Configure cursor pagination with next_cursor as the cursor field and watch Infinity walk all pages. The cursor pagination guide covers the token semantics.

5. Failure drills β€” what does the panel look like when the API is down?

Every dashboard eventually renders during an outage. See yours before your users do β€” the mock fails on request:

# the API 500s β†’ panel error state
https://mockbird.mockbird.workers.dev/m/demo/orders?mock_status=500

# the API is slow β†’ 2s of added latency, watch the panel loading state
https://mockbird.mockbird.workers.dev/m/demo/orders?mock_delay=2000

# flapping: same URL fails twice, then recovers (sequence advances per request)
https://mockbird.mockbird.workers.dev/m/demo/orders?mock_seq=503,503,200&mock_seq_key=grafdrill

If you alert on this query (JSONata/JQ parser required), the drills double as alert-rule rehearsal: what does your rule do when the datasource errors, and does the flapping sequence trip it? Cheaper to find out here. mock_ratelimit=3 adds a real 429-with-Retry-After story β€” dashboards with several Infinity panels fan out more requests than you think.

6. Template variables fed from the mock

Infinity registers as a variable data source, so dashboard dropdowns can come from the API too. Point a variable query at:

https://mockbird.mockbird.workers.dev/m/demo/orders?select=status
# β†’ [{"id":1,"status":"delivered"}, {"id":2,"status":"pending"}, …]  (select= projects just the fields you name)

…select the status column, and you have a $status dropdown. Wire it back into the panel URL β€” the mock does exact-match filtering on any field:

https://mockbird.mockbird.workers.dev/m/demo/orders?status=$status

7. Watch the dashboard move

The mock is writable β€” which turns "is auto-refresh actually working?" into something you can see. Set the dashboard refresh to 5s, then:

curl -s -X POST https://mockbird.mockbird.workers.dev/m/demo/orders \
  -H 'content-type: application/json' \
  -d '{"total": 123.45, "status": "pending", "placedAt": "2026-09-20T19:00:00.000Z"}'
# β†’ {"id":26, …} β€” within one refresh, the new order is on the panel
# tidy up: curl -X DELETE https://mockbird.mockbird.workers.dev/m/demo/orders/26

A static file or JSONPlaceholder can't do this β€” their writes are fake. Here the POST persists, the next refresh picks it up, and your time series grows a point while you watch.

8. Bonus: the GraphQL query type

Infinity also speaks GraphQL, and the demo exposes the same data through a typed GraphQL endpoint β€” one mock, both query types:

curl -s https://mockbird.mockbird.workers.dev/m/demo/graphql \
  -H 'content-type: application/json' \
  -d '{"query":"{ orders(limit: 2, sortBy: \"placedAt\", order: \"asc\") { id total placedAt } }"}'

Set query type to GraphQL, URL to /m/demo/graphql, paste the query, root selector $.data.orders (JSONata) β€” useful when the real API you're dashboarding is GraphQL and you want the Infinity config rehearsed before you get credentials.

9. See exactly what Grafana sends

When a query errors and the message is unhelpful, the question is what actually went over the wire after variable interpolation. Every hit on the demo β€” method, path, query string, headers β€” lands in the public demo request inspector. Run your panel, then look: you'll see the interpolated URL (was ${__from:date:iso} expanded the way you assumed?), the User-Agent Grafana used, and which parser fetched server-side versus from the browser. On your own project the inspector is private, per-project.

10. Your own API instead of the shared demo

One curl (or one click), no signup:

curl -s -X POST https://mockbird.mockbird.workers.dev/api/projects \
  -H "content-type: application/json" \
  -d '{"preset":"ecommerce"}'
# β†’ {"id":"abc123xyz9", "adminKey":"…", ...}

Presets: blog / ecommerce / saas β€” or define resources by hand (the date/pastDate field types generate ISO 8601 timestamps, so seeded data plots as time series immediately), or import an OpenAPI spec, db.json, CSV, or a HAR recording of the real API. Infinity's auth options (basic, bearer, API key) pair with mock JWT auth and protected mode when you want the credentials config rehearsed too. Projects have a 10,000 requests/day cap β€” mind it if you set aggressive auto-refresh on many panels.

Where Grafana's built-ins are still the right tool

Fair's fair: if all you need is plausible lines to design a layout, Grafana ships the TestData data source β€” random walks and scenario series with zero setup, no plugin, no network. Use it for pure panel-styling work. A hosted mock earns its keep when the work is the API integration itself: root selectors against a real envelope, pagination settings, time-field parsing, variables from an endpoint, auth config, and how panels and alert rules behave when the API is slow, flapping, or down. TestData can't rehearse any of that, because there's no HTTP in it.

Related: the Power BI version of this guide, the Retool version, the Streamlit version, the Google Sheets version (IMPORTDATA + CSV), cursor pagination, testing loading and error states, simulating rate limits, the GraphQL mock API guide, and mocking third-party APIs generally.

Verification: all demo curls on this page (orders list with ISO 8601 placedAt sorted ascending, 2026 range filter via placedAt_gte/_lte, envelope {"data":[…]} and the custom page/total/hasMore template, _page=2&_limit=5 returning records 6–10 with x-total-count: 25, cursor mode returning data/next_cursor/has_more, mock_status=500, a 2.7s measured delay, select=status projection, exact-match status= filter, POSTβ†’GET-backβ†’DELETE with id 26, and the GraphQL orders query) were run against production on 20 Sep 2026 before publishing. Infinity behavior β€” JSON query type auto-detecting array-at-root responses (their docs' own JSONPlaceholder example), root selector syntax per parser (JSONata/JQ/UQL), backend parsers expecting ISO 8601 timestamps or a custom Go time layout, offset/page-number/cursor pagination with a 5-page default cap raised via GF_PLUGIN_PAGINATION_MAX_PAGES, the GraphQL query type, variable queries, and the Docker install line β€” is from Grafana's official plugin docs and the plugin maintainers' GitHub discussions as of September 2026. ${__from:date:iso}/${__to:date:iso} interpolation is standard Grafana global-variable formatting (UTC). We don't run your Grafana; if a step here doesn't work, that's a bug: tell us.