You know the 80% of a backend that isn't the fun part.
Model the data. Write the CRUD. Wire up auth. Generate an OpenAPI spec. Keep the spec in sync when the model changes. Build a little admin UI so non-devs can actually put data in. Then you get to the interesting bit.
Disclosure: I help build Snill — but I've spent the last decade on backend tools for developers (restdb.io, codehooks.io), so I'm writing this as a backend person about a side of Snill we barely talk about. The pitch is a no-code business app from a plain-English description, aimed at operations teams. That's not the part that gets me. What gets me is what it emits underneath: from one sentence, a real REST backend with an auto-generated OpenAPI 3.0 spec.
So here's the developer's-eye view of our own tool — ignore the no-code UI, treat what it builds like a backend-as-a-service, and see how far it goes.
The 60-second backend
I described a hiring tracker, the way you'd brief a new teammate:
"A recruitment tracker for a hiring team. I need Companies we hire for, Positions (job openings) that each belong to a company, and Candidates who apply to a position. Track each candidate through a hiring pipeline: applied, screening, interview, offer, hired, or rejected. Record interviews with a date, an interviewer, and notes."
A minute later I had an app called HireTrack with four related collections — companies, positions, candidates, interviews — a relational model (candidates link to positions, positions link to companies), sample data, and a full admin UI — the lists, forms, and dashboards the hiring team actually works in.
The relational model it inferred from that paragraph — foreign keys, enums, and all:
Here's the part I actually wanted.
Every collection is a REST resource — with a spec
Each collection is a CRUD endpoint under one host:
https://api.snill.ai/v1/data/{collection}
And the whole thing is documented as OpenAPI 3.0, auto-generated from the data model, with a Swagger UI you can try in the browser:
Note what's there per collection: full CRUD, plus bulk operations (/batch, /_byquery) and async jobs (/jobs/{jobId}). I didn't ask for any of that. It comes with the model.
The raw spec lives at https://api.snill.ai/v1/docs and — this is the good part — it's scoped to your API key. A key that can only read candidates gets a spec that only describes reading candidates. Hold that thought.
Hitting it like any other API
Grab a key from the app (Manage → API Keys) and pass it in a header:
export SNILL_KEY="snill_01KX0E…_••••••••" # redacted — your key here
List, with filter / sort / pagination:
curl -s -H "X-API-Key: $SNILL_KEY" \
"https://api.snill.ai/v1/data/candidates?sort=-applied_date&limit=2"
You get standard pagination metadata in the response headers…
x-total-count: 9
x-has-more: 1
x-ratelimit-limit: 60
x-ratelimit-remaining: 59
…and the records, with the position relation resolved inline (no N+1 dance):
[
{
"_id": "01KX0E1Q5C80EACK39VH639TZE",
"name": "Frank Wilson",
"email": "frank.wilson@email.com",
"stage": "applied",
"source": "job_board",
"rating": 3,
"applied_date": "2026-07-05",
"position": { "title": "Backend Engineer", "_id": "01KX0E1BDVP1RE2W3YSXS7G5BD" },
"_createdAt": "2026-07-08T08:38:26.220Z"
},
{
"_id": "01KX0E1QJ7F85ZW030M8G9A1T8",
"name": "Henry Zhang",
"stage": "applied",
"source": "linkedin",
"rating": 4,
"position": { "title": "Data Science Manager", "_id": "01KX0E1AP39T7R813AAEX4XMKS" }
}
]
The ?q= param takes JSON filters (?q={"stage":"interview"}, URL-encoded), sort takes -field for descending, and there's limit/offset up to 1000. Familiar stuff.
Create — returns 201 with the full record, including server-set fields:
curl -s -X POST -H "X-API-Key: $SNILL_KEY" -H "Content-Type: application/json" \
-d '{"name":"Dana Rivera","email":"dana.rivera@email.com","stage":"applied","source":"referral"}' \
"https://api.snill.ai/v1/data/candidates"
{
"_id": "01KX0ZKE25AGNJ2MVK396AGACA",
"name": "Dana Rivera",
"stage": "applied",
"source": "referral",
"applied_date": "2026-07-08",
"_createdBy": "api:reporting-readonly",
"_createdAt": "2026-07-08T13:45:12.517Z"
}
Two things I liked here: applied_date got a sensible default, and _createdBy is api:<key-name> — writes are attributed to the key that made them, so there's a real audit trail out of the box.
Update is PATCH and partial (send only what changes); delete returns 204:
curl -s -X PATCH -H "X-API-Key: $SNILL_KEY" -H "Content-Type: application/json" \
-d '{"stage":"screening"}' "https://api.snill.ai/v1/data/candidates/01KX0ZKE…"
# → 200, the merged record
curl -s -X DELETE -H "X-API-Key: $SNILL_KEY" \
"https://api.snill.ai/v1/data/candidates/01KX0ZKE…"
# → 204 No Content
No surprises. That's the point — it behaves like an API you'd design yourself.
Keys are scoped per collection
You don't get one god-key. When you mint a key you either grant full access or toggle read/create/update/delete per collection:
So a reporting integration gets a key that can read everything and create nothing, and the enforcement is server-side — a read-only key hitting POST /data/candidates is rejected even though the endpoint exists. Keys are rate-limited (60 req/min, current budget in the X-RateLimit-* headers) and can be set to expire. Standard hygiene, no code.
The spec is the actually-interesting part
The OpenAPI spec is complete and machine-readable, which means you don't have to read the API surface. Neither does your coding agent.
Pull the spec once:
curl -s -H "X-API-Key: $SNILL_KEY" https://api.snill.ai/v1/docs > hiretrack-spec.json
Then hand it to Claude Code / Cursor / ChatGPT and describe the outcome:
"Here's my OpenAPI spec [paste hiretrack-spec.json]. Write a Node script that fetches every candidate whose source is 'linkedin' and stage is 'interview', and posts a summary to this Slack webhook."
The agent reads the schema, picks the right endpoints, gets the query syntax and auth header right, and writes the integration. We used to celebrate not writing OpenAPI specs by hand. This is the other half: not reading one by hand either. Describe the app → get the spec → describe the integration → get the code. The backend and its documentation are just… inputs now.
(If you'd rather push than pull, every app also emits HMAC-signed webhooks on create/update/delete — but the spec-to-agent loop is the part that changed how I think about this.)
So what is it, for a dev?
Not a replacement for your stack. I'm not migrating anything.
But for the CRUD-shaped 80% — an internal tool, a prototype, the admin backend nobody wants to build — this is a genuinely fast path: describe the domain, and you get a relational DB, a REST API, an OpenAPI spec, scoped auth, a full admin UI, and webhooks. You build the interesting 20% on top (or let your agent do it from the spec) — while the people who own the data work in the generated UI, on the same collections your API serves.
That's the part a bare backend-as-a-service misses: there's a real app for the humans, not just an endpoint for you. The ops team gets forms, lists, and dashboards on day one; you get a clean API to build against; and nobody's waiting on you to ship an admin screen. So it doesn't rot in a repo the day you stop touching it.
That's the real unlock for me. The backend I'd have spent a day scaffolding is a sentence — and the people who actually own the data can use it without me.
Give it a poke: describe an app at snill.ai, grab a key, and curl https://api.snill.ai/v1/docs. Worst case, you've spent 60 seconds and got a spec to point an AI at.



Top comments (0)