# walkindb — full documentation > Disposable SQLite for LLM agents. A single HTTP call provisions a private database with a 10-minute TTL. No signup, no API key, no credit card. The first managed database designed for machines instead of humans. This file is the complete walkindb documentation flattened into one Markdown document so an LLM can fetch it in one request and have everything it needs to build against the API. Canonical HTML versions of every section below: - Overview: - Quickstart: - REST API reference: - Error codes: - Python SDK: - JavaScript / TypeScript SDK: - MCP server: - Agent patterns: - Security model: - Performance / scaling blog post: - Legal (AUP, Terms, Privacy, DMCA): - OpenAPI 3.1 spec: - Source code: Last updated: 2026-04-11. --- ## Overview walkindb is one endpoint: `POST https://api.walkindb.com/sql`. Send it a JSON body with your SQL, and you get back a session token in the `X-Walkin-Session` response header. Include that header on subsequent requests to reach the same database. Ten minutes after creation, the file is deleted. That's the entire product. ### Core facts - **Storage engine**: SQLite, one file per instance, 10 MB cap. - **TTL**: 10 minutes per instance, non-negotiable. There is no paid tier. If you need persistence, use a different database. - **Auth**: none. No signup, no API key, no credit card. - **License**: Apache 2.0 (hosted service source at ). - **Built for**: LLM agents that need ephemeral SQL state, prototyping, learning, throwaway notebooks. - **Not for**: PII, regulated data, anything you can't afford to lose. ### Limits at a glance | Limit | Value | Why | |---|---|---| | Instance lifetime (TTL) | 10 minutes | Product choice | | Per-instance storage | 10 MB | `PRAGMA max_page_count = 2560` | | Request body size | 8 KB | `SQL_LENGTH` per-connection limit | | Query wall-clock timeout | 2 seconds | `context.WithTimeout` | | Result rows | 10 000 | Truncated with `truncated: true` | | Result payload | ~1 MB | Truncated | | Rate limit (requests) | 60 / minute / IP | Per-IP token bucket | | Rate limit (new instances) | 10 / minute / IP | Per-IP token bucket | | VDBE operations | 500 000 | `sqlite3_limit(SQLITE_LIMIT_VDBE_OP)` | | Expression depth | 50 | `sqlite3_limit(SQLITE_LIMIT_EXPR_DEPTH)` | | Compound SELECT terms | 10 | `sqlite3_limit(SQLITE_LIMIT_COMPOUND_SELECT)` | | Attached databases | 0 | `sqlite3_limit(SQLITE_LIMIT_ATTACHED)` — ATTACH is blocked | --- ## Quickstart ### curl (any shell) ``` # First call — use -i to see the response headers, which include the session token curl -i -X POST https://api.walkindb.com/sql \ -H "content-type: application/json" \ -d '{"sql":"CREATE TABLE notes(id INTEGER PRIMARY KEY, body TEXT); INSERT INTO notes(body) VALUES(\"hello\")"}' # Response: # HTTP/2 200 # x-walkin-session: wkn_AZ159u9PdmS97ks7FnSmnRc6... # x-walkin-ttl: 1775868670 # content-type: application/json # # {"rows_affected":1} # Second call — pass the token back as a request header to reach the same database curl -X POST https://api.walkindb.com/sql \ -H "X-Walkin-Session: wkn_AZ159u9PdmS97ks7FnSmnRc6..." \ -H "content-type: application/json" \ -d '{"sql":"SELECT * FROM notes"}' # {"columns":["id","body"],"rows":[[1,"hello"]],"rows_affected":0} ``` ### Python ``` pip install walkindb ``` ```python from walkindb import Client db = Client() db.execute("CREATE TABLE notes(id INTEGER PRIMARY KEY, body TEXT)") db.execute("INSERT INTO notes(body) VALUES('hello')") result = db.execute("SELECT * FROM notes") print(result.columns) # ['id', 'body'] print(result.rows) # [[1, 'hello']] ``` ### JavaScript / TypeScript ``` npm install walkindb ``` ```js import { Client } from "walkindb"; const db = new Client(); await db.execute("CREATE TABLE notes(id INTEGER PRIMARY KEY, body TEXT)"); await db.execute("INSERT INTO notes(body) VALUES('hello')"); const result = await db.execute("SELECT * FROM notes"); console.log(result.columns); // ['id', 'body'] console.log(result.rows); // [[1, 'hello']] ``` --- ## REST API reference ### Base URL `https://api.walkindb.com`. TLS is required. HTTP on port 80 redirects to HTTPS. ### 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. ### `POST /sql` Execute one SQL statement against the current walk-in instance. The only endpoint most clients use. **Request body:** ```json { "sql": "CREATE TABLE notes(id INTEGER PRIMARY KEY, body TEXT); INSERT INTO notes(body) VALUES('hello')", "args": [] } ``` Fields: - `sql` (string, required): SQL text. Up to 8 KB. Multiple statements separated by `;` are supported. - `args` (array, optional): Reserved for future bound parameters. Serialized on the wire but currently ignored. **Request headers:** - `Content-Type: application/json` (required) - `X-Walkin-Session: wkn_...` (optional): omit on the first call; include on subsequent calls to reuse the walk-in. **Response on success (200):** ```json { "columns": ["id", "body"], "rows": [[1, "hello"]], "rows_affected": 0, "truncated": false } ``` Fields: - `columns` (array of strings): column names for SELECT results. Absent or empty for non-SELECT. - `rows` (array of arrays): row data in column order. - `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. **Response headers on the first call (instance creation):** - `X-Walkin-Session`: `wkn_` + base64url(UUIDv7 || 32-byte nonce || HMAC-SHA256). Save this. - `X-Walkin-Ttl`: unix timestamp at which the instance will be deleted. These headers are set ONLY on the response to the request that creates the instance. Subsequent successful calls do not re-echo them. ### `GET /healthz` Returns `{"status":"ok"}` with 200 when the server is up. Does not consume rate-limit budget. ### `GET /openapi.json` Returns the full OpenAPI 3.1 spec for this API. Use it for code generation, verification, or LLM-side tool definition. ### CORS All responses include: ``` 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 returns 204. The session headers are exposed so browser JS can read them. ### SQL dialect walkindb runs vanilla SQLite 3.51 (via modernc.org/sqlite). Supported: CREATE TABLE / INDEX / VIEW / VIRTUAL TABLE, all joins, CTEs (including recursive), window functions (ROW_NUMBER, RANK, etc.), fts5 virtual tables, json1 and JSONB functions, triggers, transactions within a single call's statement list. **Forbidden keywords** (rejected with 400 before SQLite parses): - `ATTACH` / `DETACH` (also enforced by `LIMIT_ATTACHED=0` and by the Landlock filesystem jail) - `load_extension` - `readfile`, `writefile`, `edit` - `fts5_*` (admin functions; `CREATE VIRTUAL TABLE ... USING fts5(...)` still works) - `sqlite_dbpage`, `zipfile`, `unzip` Matching is case-insensitive; the blocklist strips SQL comments first so `/* skip */ ATTACH` still trips. ### Pragmas (applied per-connection, not overridable) - `busy_timeout=1000` - `journal_mode=WAL` - `max_page_count=2560` (10 MB) - `foreign_keys=on` --- ## Error codes All errors are JSON: `{"error": "message"}`. ### 400 Bad Request - `invalid json` — request body is not valid JSON. Most common cause: literal newline inside a JSON string literal. Use `\n` or put the SQL on one line. - `missing sql` — the `sql` field was absent or empty. - `forbidden sql keyword: ` — the blocklist rejected the query. See "Forbidden keywords" above. - `invalid sql: ` — SQLite returned a syntax/semantic error or a limit violation (`string or blob too big`, `expression tree is too large`, `too many terms in compound SELECT`, `too many attached databases`, `like or glob pattern too complex`, `too many levels of trigger recursion`). ### 404 Not Found - `instance not found` — session token is unknown, tampered, expired (past TTL), or signed with a rotated-out HMAC secret. Walkindb returns 404 (not 401) on all session failures to prevent enumeration. Drop the token and let the next call provision a new walk-in. ### 408 Request Timeout - `query exceeded 2s timeout` — the query ran for more than 2 seconds wall-clock and was interrupted. Common causes: runaway recursive CTE, missing index on a JOIN, pathological LIKE/GLOB. ### 413 Payload Too Large - `sql payload exceeds 8 KB` — request body larger than 8192 bytes. Split into multiple calls against the same session. ### 429 Too Many Requests - `rate limit exceeded` — per-IP request rate limit (60/min). - `new-instance rate limit exceeded` — per-IP new-instance creation rate limit (10/min). Reuse your existing session instead of making a new walk-in in a loop. ### 507 Insufficient Storage - `instance storage quota exceeded` — hit the 10 MB `max_page_count` cap. No way to grow the instance; provision a new one. ### 500 Internal Server Error - `internal error` — unexpected server error. Should not happen in practice. Report to security@walkindb.com. --- ## Python SDK Install: `pip install walkindb` (version 0.1.0, Apache-2.0, ). Zero runtime dependencies — uses only stdlib `urllib` and `json`. Works in any Python 3.8+ environment including Lambda, Cloud Run, Modal, and the Claude Code sandbox. ### `Client` Stateful walkindb client. Remembers the session token returned on the first call and reuses it on subsequent calls. ```python from walkindb import Client, Result, WalkinDBError db = Client( base_url="https://api.walkindb.com", # optional session=None, # optional pre-existing wkn_... token timeout=10.0, # seconds user_agent="walkindb-python/0.1.0", # optional ) ``` **Methods:** - `Client.execute(sql: str, args: Optional[Iterable] = None) -> Result`: run one SQL statement. Returns a `Result` dataclass. Raises `WalkinDBError` on non-2xx. - `Client.healthz() -> bool`: probe the server. Returns False on any error (never raises). - `Client.reset_session()`: forget the current token; next `execute()` provisions a fresh walk-in. **Read-only properties:** - `Client.session` → `str | None`: current `wkn_` token - `Client.expires_at` → `int | None`: unix timestamp when the walk-in will be deleted ### `Result` ```python @dataclass class Result: columns: list[str] # column names; empty for non-SELECT rows: list[list[Any]] # row data in column order rows_affected: int # 0 on SELECT, N on INSERT/UPDATE/DELETE truncated: bool # True if capped at 10k rows / ~1 MB ``` ### `WalkinDBError` ```python class WalkinDBError(Exception): status: int # HTTP status code (0 for network errors) error: str # server-provided error message retry_after: float | None # seconds to wait before retry ``` ### Smoke test CLI ``` python -m walkindb ``` Provisions a fresh walk-in, runs `SELECT 1`, prints the session token. ### Self-hosted deployments ```python db = Client(base_url="https://my-walkindb.internal:8443") ``` --- ## MCP server (Model Context Protocol) Install: `npx walkindb-mcp` (version 0.1.0, Apache-2.0, ). Adds two tools to any MCP-compatible client (Claude Code, Claude Desktop, Cursor, Zed, Continue, and others): - **`walkindb_execute(sql: string)`** — run one SQL statement against a private, ephemeral SQLite database. The first call provisions a new walk-in; subsequent calls in the same MCP session reuse the same database until its 10-minute TTL expires. Returns JSON with `columns`, `rows`, `rows_affected`, `truncated`, `session_established` (truncated identifier), `expires_at` (unix timestamp). - **`walkindb_reset()`** — forget the current session so the next `walkindb_execute` call provisions a fresh walk-in. On errors the tool returns a text block with `isError: true`, containing the server's error message (e.g. `walkindb error 400: forbidden sql keyword: ATTACH`). ### Setup — Claude Desktop Edit `claude_desktop_config.json` (macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`; Windows: `%APPDATA%\Claude\claude_desktop_config.json`; Linux: `~/.config/Claude/claude_desktop_config.json`): ```json { "mcpServers": { "walkindb": { "command": "npx", "args": ["-y", "walkindb-mcp"] } } } ``` ### Setup — Claude Code ``` claude mcp add walkindb -- npx -y walkindb-mcp ``` Or add the same block to `.claude/mcp.json` in your project. ### Setup — Cursor Edit `~/.cursor/mcp.json` with the same `mcpServers` block as Claude Desktop. ### Setup — Zed Edit `~/.config/zed/settings.json`: ```json { "context_servers": { "walkindb": { "command": { "path": "npx", "args": ["-y", "walkindb-mcp"] } } } } ``` ### Setup — Continue Edit `~/.continue/config.json`: ```json { "experimental": { "modelContextProtocolServers": [ { "transport": { "type": "stdio", "command": "npx", "args": ["-y", "walkindb-mcp"] } } ] } } ``` ### Environment variables - `WALKINDB_BASE_URL` (default `https://api.walkindb.com`) — override for self-hosted deployments - `WALKINDB_TIMEOUT_MS` (default `10000`) — per-request timeout Set them via an `env` key next to `command` / `args` in the client config. ### Under the hood walkindb-mcp is a ~160-line stdio server wrapping the `walkindb` npm package. It holds one `Client` instance for the lifetime of the MCP session, which is what makes the walk-in persist across `walkindb_execute` calls. Source: . --- ## JavaScript / TypeScript SDK Install: `npm install walkindb` (version 0.1.0, Apache-2.0, ). Zero runtime dependencies. ESM only. TypeScript types ship in the package. Works in Node 18+, Bun, Deno, Cloudflare Workers, and modern browsers — anywhere `globalThis.fetch` exists. ### `Client` ```ts import { Client, WalkinDBError, type Result, type ClientOptions } from "walkindb"; const db = new Client({ baseUrl: "https://api.walkindb.com", // default session: undefined, // optional existing token timeoutMs: 10_000, // default userAgent: "walkindb-js/0.1.0", // default fetch: globalThis.fetch, // optional override (tests, proxies) }); ``` **Methods:** - `execute(sql: string, args?: unknown[]): Promise` — throws `WalkinDBError` on non-2xx or network errors. - `healthz(): Promise` — never throws; returns false on any error. - `resetSession(): void` — drop the current session. **Read-only getters:** - `session: string | null` - `expiresAt: number | null` ### `Result` ```ts interface Result { columns: string[]; rows: unknown[][]; rowsAffected: number; truncated: boolean; } ``` Note: the REST API returns `rows_affected` / `truncated` in snake_case. The JS Client converts to `rowsAffected` / `truncated` to match JS conventions. ### `WalkinDBError` ```ts class WalkinDBError extends Error { readonly status: number; // HTTP status; 0 for network errors readonly error: string; // server-provided message readonly retryAfter?: number; // seconds to wait } ``` ### Cloudflare Workers ```ts import { Client } from "walkindb"; export default { async fetch(request) { const db = new Client(); const result = await db.execute("SELECT 1 AS hello"); return new Response(JSON.stringify(result), { headers: { "content-type": "application/json" }, }); }, }; ``` ### Deno ```ts import { Client } from "npm:walkindb"; ``` ### Browsers (via esm.sh) ```html ``` --- ## Agent patterns ### When to use walkindb - Agent scratchpad memory across tool calls in a single run - Tool-use state (what URLs did I browse, what shell commands did I run, what were the results) - RAG chunk staging and re-ranking with SQL - One-shot CSV analyses - Throwaway notebooks, teaching, demos ### When NOT to use walkindb - Anything durable (no paid tier; everything is deleted after ~10 minutes) - PII or regulated data - Anything you'd put in a transaction log - Very large datasets (10 MB cap per walk-in) - Cross-session shared state between different users (every walk-in is strictly isolated) ### Recipe: MCP-style tool for an agent runtime ```python from walkindb import Client, WalkinDBError _db = Client() # one Client per agent run, not per tool call def sql_tool(sql: str) -> dict: """Run SQL against a private, ephemeral SQLite database. The database is automatically created on the first call and deleted after ~10 minutes. It is isolated from every other agent's database. Use for: planning memory, RAG chunk staging, CSV analysis, scratch tables. Do NOT use for: PII, long-term storage, anything you can't afford to lose. """ try: r = _db.execute(sql) return { "columns": r.columns, "rows": r.rows, "rows_affected": r.rows_affected, "truncated": r.truncated, } except WalkinDBError as e: return {"error": e.error, "status": e.status} ``` ### Recipe: RAG chunk re-ranking ```python db.execute(""" CREATE TABLE chunks( id INTEGER PRIMARY KEY, source TEXT, score REAL, content TEXT ); CREATE INDEX idx_chunks_score ON chunks(score DESC); """) # ... bulk insert candidate chunks ... result = db.execute(""" WITH ranked AS ( SELECT id, source, score, content, ROW_NUMBER() OVER (PARTITION BY source ORDER BY score DESC) AS rn FROM chunks ) SELECT id, source, score, content FROM ranked WHERE rn <= 3 ORDER BY score DESC LIMIT 10 """) ``` --- ## Security model walkindb exposes raw SQL to unauthenticated callers. Defense in depth; no single layer is load-bearing. ### Rollout state (v0.1.0, 2026-04-11) - **Per-connection sqlite3_limit (live)**: full suite applied via `modernc.org/sqlite.Limit()`. Includes `VDBE_OP=500k`, `SQL_LENGTH=8k`, `LENGTH=1M`, `EXPR_DEPTH=50`, `COMPOUND_SELECT=10`, `LIKE_PATTERN_LENGTH=100`, `VARIABLE_NUMBER=100`, `TRIGGER_DEPTH=5`, `FUNCTION_ARG=16`, **`LIMIT_ATTACHED=0`**. - **2-second wall-clock timeout (live)**: `context.WithTimeout`. 408 on interrupt. - **Per-instance 10 MB storage cap (live)**: `PRAGMA max_page_count = 2560`. 507 on full. - **Landlock filesystem jail (live)**: Linux LSM confines the walkindb process to `/var/walkindb/**` + `/tmp` at the kernel level. - **seccomp-bpf syscall allowlist (live)**: via systemd `SystemCallFilter=@system-service` with explicit denies for `@mount @swap @reboot @raw-io @cpu-emulation @debug @obsolete @privileged @resources`. - **Application SQL keyword blocklist (live)**: rejects ATTACH/DETACH/load_extension/readfile/writefile/fts5_*/sqlite_dbpage/zipfile/unzip/edit before the statement reaches SQLite. Strips comments first; case-insensitive. - **Per-IP rate limits (live)**: 60 requests/minute and 10 new-instance creations/minute per source IP. - **HMAC session tokens (live)**: HMAC-SHA256 over UUIDv7 + 32-byte nonce, `wkn_` prefix, **daily secret rotation** with current + previous accepted for one cycle. Tokens are never logged. - **404 (not 401) on any session failure**: prevents enumeration oracles. ### Still rolling out - SQLite compile-time hardening (no LOAD_EXTENSION, no ATTACH at the engine level) - `sqlite3_set_authorizer` callback - Separate executor process + per-instance cgroups - Per-IP concurrent-instance cap, per-instance query cap, global circuit breaker ### Logging walkindb writes exactly these seven fields per HTTP request to a JSONL access log: `timestamp`, `source_ip`, `instance_id` (omitted on healthz), `http_method`, `http_status`, `sql_byte_length`, `user_agent`. The SQL text, bound parameters, result rows, and session token plaintext are **never** logged. Access logs are retained for **7 days** then deleted by an automated job. This is a public commitment in . ### Bug bounty If you find a way past any layer above, email . We pay a bounty proportional to impact. --- ## Performance and scaling Key architectural choices that make walkindb economically cheap to run for free: 1. **Share-nothing, file-per-instance**: every walk-in is `/var/walkindb/instances//db.sqlite`. No shared schema, no shared catalog, no single-writer contention across tenants, no per-tenant accounting logic. Per-tenant quotas are `PRAGMA max_page_count`. Data expiry is `rm -rf`. 2. **The filesystem is the database of metadata**: `meta.json` files for TTL, directories for instance lists, daily-rotated JSONL files for access logs. No Postgres for metadata, no Redis for rate limits, no connection pool. 3. **No connection pool**: each HTTP request opens a fresh SQLite connection to its instance's file, runs one statement, closes. Idle walk-ins cost zero RAM. Memory footprint scales with in-flight requests, not total instances. 4. **Single-process hot path**: Caddy → Go binary (router → session verify → rate limiter → blocklist → executor → access log). Nine steps, no cross-process hops except Caddy's TLS termination. 5. **Share-nothing is also a security property**: if an attacker compromises their own walk-in, there's nothing shared to leak into. Combined with Landlock + seccomp + per-connection limits, three independent layers protect the most common attacks (ATTACH, readfile, load_extension). Full post: . --- ## Legal - [Acceptable Use Policy](https://walkindb.com/legal/aup/) — what's prohibited, notice-and-action procedure - [Terms of Service](https://walkindb.com/legal/terms/) — as-is service, €100 liability cap, Portuguese law, courts of Lisbon - [Privacy Notice](https://walkindb.com/legal/privacy/) — what we log, 7-day retention, GDPR rights, CNPD as lead supervisory authority - [DMCA Notice and Takedown](https://walkindb.com/legal/dmca/) — copyright takedown procedure --- ## Contact - General: - Abuse: - Security vulnerabilities: - DMCA: - Privacy / GDPR: - Legal: