REST API reference
walkindb is one endpoint. This page documents it exhaustively.
Base URL
https://api.walkindb.com
TLS is required. Cleartext HTTP on port 80 redirects to HTTPS. Self-hosted deployments can run on any host + port.
Authentication
There is no authentication. The first request creates a walk-in instance and returns a session token in the response header; subsequent requests include the token to reach the same instance. You never sign up, never get an API key, and never enter a credit card.
Endpoints
POST /sql
Execute one SQL statement against the current walk-in instance. This is the only endpoint most clients will ever use.
Request body
{
"sql": "CREATE TABLE notes(id INTEGER PRIMARY KEY, body TEXT); INSERT INTO notes(body) VALUES('hello')",
"args": []
}
| Field | Type | Required | Description |
|---|---|---|---|
sql | string | yes | SQL text to execute. Up to 8 KB. Multiple statements separated by ; are supported. |
args | array | no | Reserved for future bound parameters. Currently serialized on the wire but ignored by the server. Using them is forward-compatible. |
Request headers
| Header | Required | Description |
|---|---|---|
Content-Type | yes | Must be application/json. |
X-Walkin-Session | no | Session token returned by the first call. Omit on the first call — walkindb will provision a fresh instance. Include on subsequent calls to reach the same instance. |
Response: 200 OK
The response body is always JSON:
{
"columns": ["id", "body"],
"rows": [[1, "hello"]],
"rows_affected": 0,
"truncated": false
}
| Field | Type | Description |
|---|---|---|
columns | array of strings | Column names for query results. Absent / empty for non-SELECT statements. |
rows | array of arrays | Result rows. Each row is an array of values in column order. Values are JSON primitives: integers, floats, strings, null. |
rows_affected | integer | Number of rows changed by INSERT/UPDATE/DELETE. 0 for SELECT. |
truncated | boolean | true if the result was capped at 10 000 rows or ~1 MB. Absent when not truncated. |
Response headers (on the first call only)
| Header | Description |
|---|---|
X-Walkin-Session | Base64url-encoded session token prefixed with wkn_. Format: wkn_ + base64url(UUIDv7 || 32-byte nonce || HMAC-SHA256). Save this and pass it as a request header on subsequent calls. |
X-Walkin-Ttl | Unix timestamp (seconds since epoch) at which the instance will be deleted. Typically ~600 seconds after creation. |
Error responses
All error responses use the shape:
{"error": "human-readable message"}
Status codes:
| Code | When |
|---|---|
400 | Invalid JSON body, missing sql field, forbidden keyword (see error reference), or a SQL syntax/semantic error from SQLite. |
404 | Session token unknown, tampered, or expired. Walkindb returns 404, not 401, on all session failures to prevent enumeration oracles. |
408 | Query exceeded the 2-second wall-clock timeout. |
413 | Request body exceeded the 8 KB cap. |
429 | Per-IP rate limit exceeded. Two buckets: 60 requests / minute and 10 new-instance creations / minute. The error message indicates which one. |
507 | Instance storage quota exceeded (10 MB max_page_count). |
500 | Unexpected server error. Should not happen in practice; report to [email protected] if you can reproduce one. |
GET /healthz
Liveness probe. Returns {"status":"ok"} with HTTP 200 when the server is accepting requests. Does not consume any rate-limit budget.
curl -i https://api.walkindb.com/healthz
HTTP/2 200 content-type: application/json {"status": "ok"}
GET /openapi.json
Returns the complete OpenAPI 3.1 spec for this API. Use it for code generation, LLM discovery, or to verify that the deployed server matches the documentation.
curl https://api.walkindb.com/openapi.json | jq .info
{
"title": "walkindb",
"version": "0.1.0",
"summary": "Disposable SQLite for LLM agents. No signup, 10-minute TTL."
}
Worked example: full two-call flow
# 1. First call — no session header, walkindb provisions a new instance curl -i -X POST https://api.walkindb.com/sql \ -H "content-type: application/json" \ -d '{"sql":"CREATE TABLE kv(k TEXT PRIMARY KEY, v TEXT)"}' HTTP/2 200 x-walkin-session: wkn_AZ159u9PdmS97ks7FnSmnRc6... x-walkin-ttl: 1775868670 {"rows_affected": 0} # 2. Save the token TOKEN="wkn_AZ159u9PdmS97ks7FnSmnRc6..." # 3. Reuse the token on subsequent calls curl -X POST https://api.walkindb.com/sql \ -H "X-Walkin-Session: $TOKEN" \ -H "content-type: application/json" \ -d '{"sql":"INSERT INTO kv VALUES(\"greeting\", \"hello\")"}' {"rows_affected": 1} # 4. Read it back curl -X POST https://api.walkindb.com/sql \ -H "X-Walkin-Session: $TOKEN" \ -H "content-type: application/json" \ -d '{"sql":"SELECT * FROM kv"}' {"columns": ["k", "v"], "rows": [["greeting", "hello"]], "rows_affected": 0} # 5. 10 minutes later, the instance is deleted automatically. # Any call with the same token now returns 404 "instance not found".
CORS
All walkindb API responses include permissive CORS headers so you can call the API directly from a browser:
access-control-allow-origin: * access-control-allow-methods: GET, POST, OPTIONS access-control-allow-headers: Content-Type, X-Walkin-Session access-control-expose-headers: X-Walkin-Session, X-Walkin-Ttl access-control-max-age: 86400
Preflight OPTIONS requests return 204. Because Access-Control-Expose-Headers lists both session headers, browser JavaScript can read them from the response.
Rate limiting
walkindb tracks rate limits per source IP. Two token buckets:
- 60 requests / minute — applied to every
POST /sqlcall (whether or not it creates a new instance) - 10 new-instance creations / minute — applied only when
X-Walkin-Sessionis absent, so a client that reuses its token can make 60 requests/min against the same walk-in without burning the instance-creation budget
Exceeding either budget returns 429 {"error":"rate limit exceeded"} or 429 {"error":"new-instance rate limit exceeded"}. walkindb does not yet emit Retry-After; back off for 10 seconds if you hit 429.
GET /healthz does not consume rate-limit budget.
SQL dialect
walkindb runs vanilla SQLite 3.51 (from modernc.org/sqlite). Almost everything that works in plain SQLite works in walkindb, including:
CREATE TABLE/CREATE INDEX/CREATE VIEW/CREATE VIRTUAL TABLE- Joins (inner / left / full outer)
- Common table expressions (
WITH, recursiveWITH) - Window functions (
ROW_NUMBER() OVER (...)) - Full-text search via
fts5virtual tables json1functions and JSONB- Triggers (up to the
TRIGGER_DEPTHlimit) - Transactions (
BEGIN/COMMIT/ROLLBACK) within a single call's statement list
Forbidden keywords
The following are rejected by walkindb's application-level blocklist before SQLite parses them. Requests containing them return 400 with {"error":"forbidden sql keyword: ..."}:
ATTACH/DETACH— walkindb is one-file-per-instance by design. Also enforced bysqlite3_limit(LIMIT_ATTACHED, 0)and by the Landlock filesystem jail.load_extension— no dynamic loading, ever.readfile,writefile,edit— filesystem scalar functions.fts5_*— internal fts5 admin functions (regularCREATE VIRTUAL TABLE ... USING fts5(...)still works).sqlite_dbpage,zipfile,unzip
The blocklist strips SQL comments before matching, so /* skip */ ATTACH ... and -- skip\nATTACH both trip it. Matching is case-insensitive.
Pragmas
walkindb applies these pragmas on every new connection via the DSN; you cannot change them per-request:
busy_timeout=1000— wait up to 1 s on a locked DBjournal_mode=WAL— WAL journaling for concurrencymax_page_count=2560— 10 MB capforeign_keys=on— FK constraints enforced
Also see
- Error codes — full reference
- OpenAPI 3.1 spec — machine-readable
- Python SDK — if you don't want to speak HTTP directly
- JavaScript / TypeScript SDK
- Security model