๐ก We check api.adviceslip.com (and 75+ other public dev APIs) with a plain GET every 30 minutes โ see the live status page. As of publishing it's up and answering fast.
Advice Slip is the little API behind Frontend Mentor's popular Advice generator app challenge and a decade of quote-button tutorials โ free, keyless, CORS open, serving (by its own count) over 10 million pieces of advice a year, run on Ko-fi money and goodwill. It's charming, and it was up and fast when we checked.
But if your advice button is misbehaving, it's probably one of these four (all checked live on September 16, 2026):
Cache-Control headers (max-age=2 and max-age=600, private, must-revalidate), so browsers happily reuse the cached response and your fetch() never hits the network again. The fix on their API: fetch(url, {cache:'no-store'})./advice/99999 answers 200 OK with {"message":{"type":"error","text":"Advice slip not found."}}. A no-result search answers 200 with a message object too โ so data.slips.map(โฆ) throws and response.ok checks catch nothing."slip_id": "2" (a string); the live API returns "id": 149 (a number). The docs say search's total_results is an integer; the live API returns "total_results": "1" โ a string. Code written from the docs breaks on the API, and vice versa.POST /advice answers 405. Your "save my own advice" feature has nowhere to go.For building and testing, the fix is the same shape on an API you control โ uncached, consistent, writable. One curl, no signup:
curl -s https://mockbird.mockbird.workers.dev/data/adviceslips.db.json \
| curl -s -X POST 'https://mockbird.mockbird.workers.dev/api/projects/import?name=advice' \
-H 'content-type: application/json' --data-binary @-
# โ {"id":"YOUR_ID","adminKey":"โฆ"} โ save both
The dataset is 30 original slips we wrote for this guide (free to use however you like) in the live API's field shape โ id (a number, consistently), advice, date. No cache headers, so every request is fresh โ your button works with plain fetch().
| Advice Slip | Your Mockbird project |
|---|---|
/advice (random) | /slips?sortBy=random&limit=1 โ fresh every time, no cache-buster needed |
/advice/{id} | /slips/17 |
bad id โ 200 + message object | bad id โ a real 404 |
/advice/search/{query} | /slips?advice_like=tests โ case-insensitive substring |
no results โ 200 + message object | no results โ 200 + an empty array (.map() never throws) |
?callback= JSONP | โ not needed; CORS is open (theirs is too) |
| cached for 2 s (or 600) | never cached |
curl "https://mockbird.mockbird.workers.dev/m/YOUR_ID/slips?sortBy=random&limit=1&select=advice"
curl "https://mockbird.mockbird.workers.dev/m/YOUR_ID/slips?advice_like=tests"
Challenge code reads data.slip.advice. One query param reproduces that envelope exactly, so your fetch code doesn't change:
curl "https://mockbird.mockbird.workers.dev/m/YOUR_ID/slips/17?mock_envelope=%7B%22slip%22%3A%22%24data%22%7D"
# โ {"slip":{"id":17,"advice":"โฆ","date":"2026-09-16"}} โ their shape, your data
# search object, with total_results as an actual number:
curl "https://mockbird.mockbird.workers.dev/m/YOUR_ID/slips?advice_like=tests&mock_envelope=%7B%22total_results%22%3A%22%24count%22%2C%22query%22%3A%22tests%22%2C%22slips%22%3A%22%24data%22%7D"
# โ {"total_results":1,"query":"tests","slips":[โฆ]}
# no match โ {"total_results":0,"query":"โฆ","slips":[]} โ an array, always
Set it once as the project default (PUT /api/projects/:id/settings {"envelope":โฆ}) and every GET answers in that shape. Details in docs โ envelope.
curl -X POST https://mockbird.mockbird.workers.dev/m/YOUR_ID/slips \
-H 'content-type: application/json' \
-d '{"advice":"Water the plant you can see from your desk.","date":"2026-09-16"}'
# โ 201 {"id":31,โฆ} โ and GET /slips/31 returns it. It persists.
That turns the challenge into a product: a team advice wall, a daily-standup fortune cookie, a "submit your own" form that actually submits.
If you ship against the real Advice Slip, rehearse its downtime and slow days deterministically instead of hoping:
# two failures, then success โ exercise your retry / error state
curl "https://mockbird.mockbird.workers.dev/m/YOUR_ID/slips?mock_seq=503,503,200"
# slow response for the challenge's loading state
curl "https://mockbird.mockbird.workers.dev/m/YOUR_ID/slips/1?mock_delay=2000"
More recipes in testing loading & error states.
Credit where due: it's free, keyless, open-CORS, has run for years on one person's goodwill, and the advice itself is genuinely charming โ that content is the product, and our 30 slips don't replace it. The cache exists so a free API serving 10M requests a year survives, which is fair. If your app ships against it, say thanks via the Ko-fi on their site. The honest split: production advice content at human pace โ Advice Slip (with {cache:'no-store'}). Development, tests, CI, demos, and apps that need writes, search that returns arrays, real 404s, or uncached randomness โ your own project.
The one-curl import in section 1, or zero setup at all:
curl https://mockbird.mockbird.workers.dev/m/demo/products?limit=3
Facts checked live on September 16, 2026: GET /advice answered 200 with both cache-control: max-age=2 and cache-control: max-age=600, private, must-revalidate headers; /advice/99999 answered HTTP 200 {"message":{"type":"error","text":"Advice slip not found."}}; /advice/search/xyzzynotaword answered HTTP 200 with a message object (no slips array); live slip objects use id (number) while the docs show slip_id (string); live search total_results is a string ("1") while the docs say integer; POST /advice answered 405. Every Mockbird command on this page was run against a live project before publishing.
Full API reference in the docs. More guides: Quotable alternative ยท Bored API alternative ยท Numbers API alternative ยท Open Trivia DB alternative ยท Host a JSON file as an API. Create your API โ