Open source · No credit card
A database that bills per document, not per instance.
Store JSON, read it by key, pay for exactly the operations you make. A real app runs about $2.43 a month. An idle one costs nothing at all.
1 GB, 100k reads and 10k writes free every month — not a trial, and it stops rather than bills you.
curl -X PUT \
https://demo.jaydb.com/v1/n/public/docs/notes/hello \
-H "X-JayDB-API-Key: jaydb_demo_public" \
-d '{"title":"Ship it","done":false}'
{ "status": "ok", "key": "notes/hello", "etag": "3f9a1c72" }
Runs against a public demo namespace. No signup, no key of your own,
nothing to install — the etag that comes back is what
makes concurrent writes safe.
Why you can rely on it
MIT licensed
Open-source engine
Self-host the same engine, export plain JSON. The exit is real, and it is on GitHub.
4,040 /sec
Reads per second, measured
One 0.5 vCPU Fargate task, co-located client, 20 concurrent workers, 1 KB documents. p50 1.77 ms, 100% success.
$0
Cost while idle
No instance to keep warm. Storage meters per megabyte-hour, so deleting data lowers the bill the same hour.
Getting started
Three steps to your first document
There is no schema step, no migration, no connection pool and no instance to size. That is the whole setup.
-
Create a namespace
One click. You get a URL and an API key, and no card is asked for at any point.
-
Write a document
One
PUTto a key of your choosing. The namespace exists the moment you write to it. -
Read it from anywhere
By key or by prefix. Every response carries an
ETag, so two writers cannot silently overwrite each other.
The model
One concept, and a bill you can predict
A key holds a JSON document. Read it, write it, or compare-and-swap it. That is the entire API surface, and the price follows the same shape.
-
No floor to clear
Every account keeps a free allowance every month, then meters what it uses. An idle project costs nothing. Supabase Pro is $25/mo and Convex Pro is $25 per developer before either serves a single request.
-
Per document, not per kilobyte
One read is one read whatever the document weighs. DynamoDB bills reads in 4 KB tranches and writes in 1 KB tranches, so an 8 KB document costs 8 write units there and one here.
-
Concurrency without a lock server
Every response carries an
ETag. Send it back withIf-Matchand your write is rejected with a412if someone moved first — no transaction coordinator, no advisory locks.
# create — fails with 412 if the key already exists
curl -X PUT https://acme.jaydb.com/v1/n/app/docs/users/101 \
-H "X-JayDB-API-Key: $JAYDB_KEY" \
-H "If-None-Match: *" \
-d '{"name":"Alice","plan":"free"}'
# read it back — the ETag comes with it
curl https://acme.jaydb.com/v1/n/app/docs/users/101 \
-H "X-JayDB-API-Key: $JAYDB_KEY"
# update only if nobody moved it since
curl -X PUT https://acme.jaydb.com/v1/n/app/docs/users/101 \
-H "X-JayDB-API-Key: $JAYDB_KEY" \
-H 'If-Match: "3f9a1c72"' \
-d '{"name":"Alice","plan":"pro"}'
const BASE = 'https://acme.jaydb.com/v1/n/app/docs';
const headers = { 'X-JayDB-API-Key': process.env.JAYDB_KEY };
// read, and keep the version it came with
const res = await fetch(`${BASE}/users/101`, { headers });
const user = await res.json();
const etag = res.headers.get('ETag');
// write only if nothing changed underneath
const put = await fetch(`${BASE}/users/101`, {
method: 'PUT',
headers: { ...headers, 'If-Match': etag },
body: JSON.stringify({ ...user, plan: 'pro' }),
});
if (put.status === 412) {
// another writer won — re-read and try again
}
import os, requests
BASE = "https://acme.jaydb.com/v1/n/app/docs"
H = {"X-JayDB-API-Key": os.environ["JAYDB_KEY"]}
# read, and keep the version it came with
r = requests.get(f"{BASE}/users/101", headers=H)
user, etag = r.json(), r.headers["ETag"]
# write only if nothing changed underneath
user["plan"] = "pro"
w = requests.put(
f"{BASE}/users/101",
headers={**H, "If-Match": etag},
json=user,
)
if w.status_code == 412:
# another writer won — re-read and try again
...
base := "https://acme.jaydb.com/v1/n/app/docs"
key := os.Getenv("JAYDB_KEY")
// read, and keep the version it came with
get, _ := http.NewRequest("GET", base+"/users/101", nil)
get.Header.Set("X-JayDB-API-Key", key)
res, err := http.DefaultClient.Do(get)
if err != nil {
return err
}
defer res.Body.Close()
etag := res.Header.Get("ETag")
// write only if nothing changed underneath
put, _ := http.NewRequest("PUT", base+"/users/101", body)
put.Header.Set("X-JayDB-API-Key", key)
put.Header.Set("If-Match", etag)
// 412 means another writer won — re-read and try again
Shared application state, without a state server
Compare-and-swap is enough to make two people editing the same board safe. This is the whole synchronisation primitive — there is no subscription to manage and no server of your own to run.
// Apply `mutate` to a document, retrying when someone else wins the race.
async function update(key, mutate, attempts = 5) {
for (let i = 0; i < attempts; i++) {
const res = await fetch(`${BASE}/${key}`, { headers });
const doc = res.ok ? await res.json() : {};
// If-Match on an existing doc; create-only when there is none yet.
const guard = res.ok
? { 'If-Match': res.headers.get('ETag') }
: { 'If-None-Match': '*' };
const put = await fetch(`${BASE}/${key}`, {
method: 'PUT',
headers: { ...headers, ...guard },
body: JSON.stringify(mutate(doc)),
});
if (put.ok) return put.json();
if (put.status !== 412) throw new Error(await put.text());
}
throw new Error('too much contention on ' + key);
}
// Two people adding a card to the same board cannot clobber each other.
await update('boards/42', (b) => ({ ...b, cards: [...(b.cards ?? []), card] }));
Cost
The same app, priced six ways
One workload, every platform's own published rates, arithmetic you can redo yourself: 20 GB stored, 5M reads, 1M writes a month, 8 KB average document.
Assumptions, stated so you can check them: US East list prices and every platform's free tier applied, including the ones more generous than ours. DynamoDB reads are strongly consistent, so an 8 KB document is 2 read units and 8 write units. Convex meters function calls, database I/O and storage, and all three are counted here. Supabase Pro is a floor rather than a usage figure — this workload fits inside it, and a fourth developer would make it $100 on Convex Pro. Egress is excluded for every platform including ours: at 8 KB a read this workload moves roughly 40 GB, which would add about $3.60 on Firestore and nothing here, where egress is included.
| Platform | Monthly floor | Storage /GB-mo | Reads | Writes | Egress |
|---|---|---|---|---|---|
| jaydb | $0 | $0.05 | $0.20 /M | $0.50 /M | included |
| Firestore | $0 | $0.15 | $0.30 /M | $0.90 /M | $0.12/GiB over 10 GiB |
| DynamoDB on-demand | $0 | $0.25 | $0.125 /M RRU | $0.625 /M WRU | not published |
| Supabase | $25 | $0.125 over 8 GB | no per-read charge | no per-write charge | $0.09/GB over 250 GB |
| Convex | $0, Pro $25/developer | $0.20–0.22 | I/O $0.22/GB + $2.20/M calls | same metering | $0.132/GB over 1 GB |
| Upstash Redis | $0 | $0.25 | $2.00 /M commands | $2.00 /M commands | $0.03/GB over 200 GB |
Where the others win, and why we say so: Firestore's free tier is far more generous than ours at 50k reads a day, and DynamoDB's per-unit read price is lower than ours. The difference shows up in the totals because their units are smaller than a document — and because index-entry reads, a one-read minimum per query, per-region write billing and hourly compute all land on the invoice without appearing on the rate card. Sources: Firestore, DynamoDB, Supabase, Convex, Upstash.
For coding agents
The storage an agent can adopt in one turn
When an agent scaffolds an app that needs shared state, most databases cost it a provisioning step, a schema decision and a client library. This one costs an HTTP call.
No provisioning turn
A namespace exists the moment something is written to it. There is no create-then-wait step to burn a turn on, and nothing to tear down afterwards.
curl is the whole SDK
Three verbs and one header. No package to install, no version to pin, no client to keep in sync — which means no dependency an agent can get wrong.
Parallel agents cannot clobber
Compare-and-swap is on every write. Two agents editing the same
document get a 412 rather than a silent last-write-wins,
so a lost update becomes a visible, retryable error.
Reach for it when
- Reads dominate and you fetch by key or by prefix.
- State belongs to a user, a tenant, a document or a session.
- People collaborate but rarely touch the same document in the same instant.
- An idle project must cost nothing.
- You want the option to self-host the same engine later.
Use something else when
- You need joins across entities, or foreign keys enforced.
- A write must be atomic across several documents at once.
- You query by field values rather than by key — there are no secondary indexes and no ad-hoc queries.
- You need full-text search or aggregation.
- One hot key takes sustained concurrent writes; optimistic concurrency degrades under real contention.
Console
See your data without instrumenting anything
Browse the key hierarchy, edit a document in place, and watch usage accrue against your bill. Both views come with the namespace — there is no agent to install and no dashboard to wire up.
Illustrations of the console, drawn in the page rather than screenshotted — the numbers are an example workload, not live data.
Pricing
Metered in the smallest unit there is
The rates are quoted per million because that is readable. The meter underneath counts single operations and megabyte-hours, so there is no threshold to cross and nothing to round up to.
Free every month, forever
1 GB stored, 100,000 reads and 10,000 writes. Not a trial and not a credit that expires — it applies to every account, every month. On the free plan usage stops at the allowance instead of billing you, so no card is needed to start.
$5
Prepays $6.50 of metered usage. A 1.3× discount, and nothing changes about how you are metered.
$10
Prepays $15 of metered usage. A 1.5× discount, for when the meter is regularly past the smaller pack.
There is no monthly floor, no per-seat charge and no tier that unlocks a feature. Packs are a discount on the same meter, so crossing one changes only the price you already paid — never what your application is allowed to do.
Architecture
Why it can be this cheap
The price is not a promotion. It follows from two decisions about where data lives and how writes agree with each other.
hot tier
A bounded cache, sharded by key
Recently touched documents are served from memory. Concurrent misses on the same key collapse into a single fetch, so a hundred simultaneous readers of one popular document cause one read of the cold tier rather than a hundred.
That coalescing is why cost stays roughly flat as traffic climbs: the expensive layer sees a fraction of the requests your application makes.
cold tier
Object storage, not a replicated cluster
Documents live in object storage, which is where both the durability and the low price come from. There is no always-on cluster sized for your peak, no standby replica, and nothing that bills you while your project sits idle.
A conventional database keeps machines running to hold indexes and transaction state in memory. That is a fixed cost you pay whether or not anyone is using your app — the floor everyone else charges.
The trade that makes it work: optimistic concurrency
Reading a document gives you its version. Writing it back means
submitting your change against that version. If
someone else got there first, the version no longer matches and your
write is rejected with a 412 — you re-read and retry.
That is compare-and-swap, and it is the whole coordination mechanism.
An ACID database gives a stronger guarantee, and it pays for it continuously: a lock manager, a transaction coordinator, and a quorum of nodes that must agree before a commit returns. Those resources are provisioned for the worst case — heavy contention on the same rows — and they run whether or not that contention ever happens.
Most application workloads are low-contention. Two users rarely write the same document in the same millisecond, and when they do, one retry settles it. jaydb charges for the case you actually have instead of the worst case you probably do not, and the difference between those two shows up on your invoice.
The honest limit of that trade is written on this page rather than buried: under sustained concurrent writes to a single hot key, optimistic concurrency degrades into retries and you want a database that takes locks. Everything above assumes collisions are the exception.