sondahub

sondahub / Fake REST API

A free fake REST API that behaves like a real one

78,076 records in 37 collections across seven APIs — e-commerce, IoT, banking, social, helpdesk, flights and a company directory — with ids that never change and the rules a real backend enforces. Point a frontend, a test suite, a tutorial or an API client at it.

POST, PUT, PATCH and DELETE are validated and answered as a real server would, and they stick: the answer hands you a session token, and every request that carries it sees your writes. No database anywhere — the state travels with you.

Quick start

Base URL https://api.sondahub.com/v1. JSON in, JSON out, HTTPS, CORS open to every origin. No key, no header to add.

Products under $20, cheapest first
curl "https://api.sondahub.com/v1/store/products?price_lt=20&sort=price&limit=3"
An order with its customer and line items embedded
curl "https://api.sondahub.com/v1/store/orders/1?expand=customer,items"
Create an order — priced by the server
curl -X POST https://api.sondahub.com/v1/store/orders \
  -H "Content-Type: application/json" \
  -d '{"customer_id": 1, "items": [{"product_id": 1, "quantity": 2}]}'
A body that is wrong — 422 names every problem
curl -X POST https://api.sondahub.com/v1/store/products -H "Content-Type: application/json" -d '{"price": "free"}'

Every collection

37 collections, 78,076 records, related to each other through ids that never change. Each has a page with its fields, sample records and ready-made queries.

CollectionAPIRecordsPath
categoriesStore8/v1/store/categories
productsStore2,000/v1/store/products
customersStore800/v1/store/customers
ordersStore3,000/v1/store/orders
order itemsStore6,219/v1/store/order_items
reviewsStore2,500/v1/store/reviews
warehousesStore3/v1/store/warehouses
inventoryStore6,000/v1/store/inventory
cartsStore200/v1/store/carts
sitesFleet40/v1/fleet/sites
devicesFleet500/v1/fleet/devices
readingsFleet4,908/v1/fleet/readings
alertsFleet500/v1/fleet/alerts
commandsFleet300/v1/fleet/commands
customersBank400/v1/bank/customers
accountsBank640/v1/bank/accounts
cardsBank505/v1/bank/cards
transactionsBank7,674/v1/bank/transactions
transfersBank800/v1/bank/transfers
fx ratesBank28/v1/bank/fx_rates
usersSocial800/v1/social/users
postsSocial4,000/v1/social/posts
commentsSocial8,000/v1/social/comments
likesSocial8,000/v1/social/likes
followsSocial5,000/v1/social/follows
teamsHelpdesk4/v1/helpdesk/teams
agentsHelpdesk25/v1/helpdesk/agents
customersHelpdesk300/v1/helpdesk/customers
ticketsHelpdesk2,000/v1/helpdesk/tickets
messagesHelpdesk5,069/v1/helpdesk/messages
airportsFlights50/v1/flights/airports
airlinesFlights6/v1/flights/airlines
flightsFlights2,500/v1/flights/flights
bookingsFlights3,500/v1/flights/bookings
usersIdentity300/v1/identity/users
groupsIdentity40/v1/identity/groups
membershipsIdentity1,457/v1/identity/memberships

What a list can do

OptionExample
Paging?page=3&limit=50 · ?offset=100
Sorting, several keys?sort=-total,id
Equals?status=shipped · ?in_stock=true
Comparisons?price_gte=10&price_lt=50 · ?placed_at_gt=2026-08-01
Not equal, contains, any of, missing?status_ne=cancelled · ?name_like=lamp · ?id_in=1,2,3 · ?notes_null=true
Inside a JSON field?address.country=AR
Full-text search?q=alpine
Sparse fields?fields=id,name,price
Embed relations?expand=customer,items
Nested routes/v1/store/customers/1/orders

Single records carry an ETag; send it back in If-None-Match and get a 304.

Rules beyond CRUD

A fake API that accepts anything teaches a client nothing about the errors it will meet. These collections behave:

CollectionWhat a write does
bank/transfersPOST checks both accounts exist, are active, hold the same currency and that the amount is available, then debits, credits and writes a transaction on each side; the answer carries debit_transaction_id and credit_transaction_id. Anything wrong answers 422 with the reason.
store/ordersPOST may carry items: [{ product_id, quantity }]: the hub prices them from the products, computes subtotal, shipping, tax and total, creates the order items and returns them inline. PATCH to shipped stamps shipped_at and a tracking number; delivered stamps delivered_at.
helpdesk/ticketsPATCH checks the status move (open → pending | resolved; pending → open | resolved; resolved → closed | open; closed → open) and answers 422 invalid_transition otherwise; resolving and closing stamp their timestamps, assigning stamps first_response_at. Counters on the customer and agent follow.
flights/bookingsPOST needs passenger.first_name and last_name, refuses cancelled or departed flights and sold-out ones (409), picks a free seat in the cabin when you give none, prices the fare from the flight, and mints a six-character locator. Cancelling gives the seat back.
social/likesPOST refuses a second like of the same post by the same user (409 already_liked) and bumps the post’s likes_count; DELETE lowers it.
identity/usersPOST and PATCH keep user_name and email unique (409 already_exists, naming the user who has it); display_name defaults to the two names and active to true. The same people sign in through SAML and OIDC and are provisioned over SCIM.

OpenAPI 3 for every API

Import one into Sonda, or into any client or code generator that reads OpenAPI, and every operation arrives with its schema and an example body.

Storehttps://api.sondahub.com/v1/store/openapi.json
Fleethttps://api.sondahub.com/v1/fleet/openapi.json
Bankhttps://api.sondahub.com/v1/bank/openapi.json
Socialhttps://api.sondahub.com/v1/social/openapi.json
Helpdeskhttps://api.sondahub.com/v1/helpdesk/openapi.json
Flightshttps://api.sondahub.com/v1/flights/openapi.json
Identityhttps://api.sondahub.com/v1/identity/openapi.json

Questions

Does the data change after a POST?

For you, yes. A write is validated and answered exactly as a real server would answer it, and the answer carries an X-Sondahub-Session token with the change. Send that token back and the next GET finds your record, lists include it and totals count it; leave it off and you get the seed data, marked X-Sondahub-Write: simulated. The server stores nothing, so nobody else sees your writes. How sessions work.

What status codes and errors can I expect?

200 and 201 (with a Location header) for success, 304 for a matching ETag, 400 for a query naming a field that does not exist, 404 for a missing id, 405 with an Allow header for a verb a route does not take, 409 for conflicts such as a duplicate like or a sold-out flight, and 422 for a body that fails validation — with one line per problem. Every error is {"error": {"code", "message", "details"}}. Any other status is one call away at /v1/utils/status/{code}.

How does pagination work?

Lists answer {"data": [...], "meta": {"page", "limit", "total", "pages"}}, with X-Total-Count and an RFC 8288 Link header carrying next, prev, first and last. page and limit (up to 200) or offset.

Can I use it in automated tests and CI?

Yes — the seed is fixed, so record 1 is the same record on every run and assertions stay valid. It is a free public service on a shared daily budget, so for heavy suites, run your own copy: the source is MIT, and npm run build && npm run dev serves the same hub on localhost.