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": []
}
FieldTypeRequiredDescription
sqlstringyesSQL text to execute. Up to 8 KB. Multiple statements separated by ; are supported.
argsarraynoReserved for future bound parameters. Currently serialized on the wire but ignored by the server. Using them is forward-compatible.

Request headers

HeaderRequiredDescription
Content-TypeyesMust be application/json.
X-Walkin-SessionnoSession 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
}
FieldTypeDescription
columnsarray of stringsColumn names for query results. Absent / empty for non-SELECT statements.
rowsarray of arraysResult rows. Each row is an array of values in column order. Values are JSON primitives: integers, floats, strings, null.
rows_affectedintegerNumber of rows changed by INSERT/UPDATE/DELETE. 0 for SELECT.
truncatedbooleantrue if the result was capped at 10 000 rows or ~1 MB. Absent when not truncated.

Response headers (on the first call only)

HeaderDescription
X-Walkin-SessionBase64url-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-TtlUnix timestamp (seconds since epoch) at which the instance will be deleted. Typically ~600 seconds after creation.
The session header is set on the creation response — not on every response. Subsequent successful calls do not re-echo it. Save it after the first call.

Error responses

All error responses use the shape:

{"error": "human-readable message"}

Status codes:

CodeWhen
400Invalid JSON body, missing sql field, forbidden keyword (see error reference), or a SQL syntax/semantic error from SQLite.
404Session token unknown, tampered, or expired. Walkindb returns 404, not 401, on all session failures to prevent enumeration oracles.
408Query exceeded the 2-second wall-clock timeout.
413Request body exceeded the 8 KB cap.
429Per-IP rate limit exceeded. Two buckets: 60 requests / minute and 10 new-instance creations / minute. The error message indicates which one.
507Instance storage quota exceeded (10 MB max_page_count).
500Unexpected 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 /sql call (whether or not it creates a new instance)
  • 10 new-instance creations / minute — applied only when X-Walkin-Session is 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, recursive WITH)
  • Window functions (ROW_NUMBER() OVER (...))
  • Full-text search via fts5 virtual tables
  • json1 functions and JSONB
  • Triggers (up to the TRIGGER_DEPTH limit)
  • 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 by sqlite3_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 (regular CREATE 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 DB
  • journal_mode=WAL — WAL journaling for concurrency
  • max_page_count=2560 — 10 MB cap
  • foreign_keys=on — FK constraints enforced

Also see