# grange — a document database your agent can pay for grange is an agent-first document DB: one static binary (MFL/machin), crash-safe WAL, secondary indexes with O(1) count/sum/avg registers, sorted range indexes. Faster than SQLite on every indexed workload at 100k docs (see the README bench). OSS: https://github.com/javimosch/grange · This hosted instance: https://grange.intrane.fr READ REPLICA: https://read.grange.intrane.fr — same tokens, GET routes only (find/count/agg/get/watch/export); point read-heavy agents here, writes go to the primary ## Get a namespace (self-serve; a peage wallet is the only credential) 1. mint a wallet: curl -s -X POST https://peage.intrane.fr/v1/wallets -d '{}' (save the pw_ token; docs: https://peage.intrane.fr/llms.txt) 2. fund it: curl -s -X POST https://peage.intrane.fr/v1/topup -H 'Authorization: Bearer pw_...' -d '{"amount_cents":500}' -> a Stripe URL your human pays once 3. sign up here: curl -s -X POST https://grange.intrane.fr/tenants -H 'X-Peage-Wallet: pw_...' -d '{"name":"my agent"}' -> {"tenant":"t...","token":"gt_..."} (save the gt_ token — it is your database) ## Pricing (pay-as-you-go, no subscription) Storage: 15 cents/GB/month, first 50 MB free. Accrued continuously (price/30 per GB-day), charged to your wallet via peage when >= 5 cents. Queries and writes are free under FAIR USE: 300 requests/min and 5 concurrent watchers per tenant; beyond that you get 429 {type:rate-limited, retry_after_seconds} — back off and retry. Throttling is recorded; your GET /usage shows throttled_requests_total. ## The API (Authorization: Bearer gt_... on every call; ?coll= picks a collection, default 'default') PUT curl -s -X POST $G/put -H "$A" -d '{"coll":"leads","id":"l1","doc":{"co":"acme","score":9}}' # id optional (auto) GET curl -s "$G/get?coll=leads&id=l1" -H "$A" DEL curl -s -X POST $G/del -H "$A" -d '{"coll":"leads","id":"l1"}' FIND curl -s "$G/find?coll=leads&where=score%3E%3D5&limit=50" -H "$A" # where: f=v,f2>v2 (AND; = > < >= <=) COUNT curl -s "$G/count?coll=leads&where=co%3Dacme" -H "$A" # O(1) if the field is indexed INDEX curl -s -X POST $G/index -H "$A" -d '{"coll":"leads","field":"co","sums":"score"}' # kind:"range" for > < >= <= AGG curl -s "$G/agg?coll=leads&group-by=co&sum=score" -H "$A" # count/sum/avg O(1) via registers; minmax= scans BULK curl -s -X POST "$G/bulk?coll=leads" -H "$A" --data-binary @ops.ndjson # one line per op: '{...}' put auto-id · 'id{...}' put · '-id' del; ONE commit, all-or-nothing; 263k docs/s measured COLLS curl -s $G/collections -H "$A" DBS curl -s $G/dbs -H "$A" # tenants have MULTIPLE databases: pass db= on any call (default 'default') STATS curl -s "$G/stats?coll=leads" -H "$A" COMPACT curl -s -X POST $G/compact -H "$A" -d '{"coll":"leads"}' # fold WAL chunks; run when stats.chunks_replayed grows COLD curl -s -X POST "$G/cold?coll=archive" -H "$A" # DISK-RESIDENT mode: RAM stays ~flat regardless of size (20x less at 200k docs); point gets read one page file; scans stream; no secondary indexes/TTL on cold; irreversible per collection WATCH curl -s "$G/watch?coll=jobs&since=0&timeout=25" -H "$A" # LONG-POLL: blocks until the collection changes past since -> {seq,changes:[{seq,op,id}]}; poll again with the returned seq EXPORT curl -s "$G/export?coll=leads&format=lines" -H "$A" # NDJSON 'iddoc' lines — pipe straight into /bulk of another grange USAGE curl -s $G/usage -H "$A" HEALTH curl -s $G/health # open, no auth Docs are single-line minified JSON. Errors: {"ok":false,"error":{type,message}} with proper HTTP codes. ## Client SDKs (same surface everywhere: db(...).coll(...) scoping + put/get/find/count/agg/index/put_many) Python pip install grange-db from grange_db import Grange leads = Grange(url, token).db('crm').coll('leads') leads.put({'co':'acme'}); leads.count('co=acme'); leads.put_many([{...},...]) Node npm install grange-db (>=18, zero deps) const { Grange } = require('grange-db') const leads = new Grange({url, token}).db('crm').coll('leads') await leads.put({co:'acme'}); await leads.count('co=acme'); await leads.putMany([...]) Go go get github.com/javimosch/grange/sdk/go leads := grange.New(url, token).DB("crm").Coll("leads") id, _ := leads.Put("", map[string]any{"co":"acme"}); n, _ := leads.Count("co=acme") machin compose sdk/machin/grange_client.src (https://github.com/javimosch/grange) g := grange_client(url, token) g = grange_use(g, "crm", "leads") id, ok := grange_put(g, "", doc) n := grange_count(g, "co=acme") Durability: every write request is one immutable checksummed WAL chunk (process-crash-exact). Your data is exportable at any time and REPLICABLE: `grange follow --from https://grange.intrane.fr --rtoken gt_... --db ./local` pulls a live local replica over the /watch feed (resync via export, then deltas). The engine is OSS — self-host the same binary if you outgrow the hosted rail.