Skip to content

SQLite HTTP API

Run SQL against a named SQLite database over Hrana JSON v3. See the quickstart for a complete request. The experimental Turso engine uses separate endpoints and databases.

POST /v1/disks/{disk}/sqlite/{database}/hrana/v3/pipeline
POST /v1/disks/{disk}/sqlite/{database}/hrana/v3/cursor

Send Content-Type: application/json and the server Bearer key. The SDK base URL is /v1/disks/{disk}/sqlite/{database}/hrana/; the driver appends v3/pipeline or v3/cursor. In the examples, demo is a disk and mydb is an arbitrary database name within it. One disk can contain several independent databases. Both names use 1–128 ASCII letters, digits, _ or -. First access opens or creates the database.

The virtual shell’s wabesql command accesses the same named databases. It supports explicit creation and listing; its query command requires an existing database. SQL state is shared across filesystem branches.

The former custom /v1/disks/{disk}/sql/{database} endpoint and /v1/disks/{disk}/libsql/{database}/v3/* routes are disabled. Authenticated requests return 404. No alias or redirect is registered.

See the Turso SDK guide for installation, connection and query examples.

Field Used by Behavior
baton Both endpoints Omit or use null for a new session; pass the latest token to continue a session.
requests Pipeline Array of execute, batch, sequence, describe, get_autocommit, store_sql, close_sql or close operations.
batch.steps Cursor Statement steps with optional conditions on earlier steps or autocommit state.
stmt.sql / stmt.sql_id Statement Exactly one inline SQL text or stored SQL ID.
stmt.args / stmt.named_args Statement Positional or named typed values.
stmt.want_rows Statement Defaults to true.

This is the HTTP JSON v3 profile. Protobuf, WebSocket, replication, remote encryption and Hrana v2 endpoints are outside the profile. SQL uses ordinary SQLite; Turso MVCC and libSQL’s read-only transaction extension are unsupported. Host file access, ATTACH, extensions and unsafe PRAGMAs are forbidden. The former custom readOnly request flag is not part of this protocol.

A pipeline executes operations in order without an implicit transaction. Use the SDK transaction API or explicit BEGIN/COMMIT/ROLLBACK when several statements must commit together. Each execute operation contains one SQL statement.

Create and insert
curl --fail-with-body "https://api.wabe.sh/v1/disks/demo/sqlite/mydb/hrana/v3/pipeline" \
-H "Authorization: Bearer YOUR_PROJECT_API_KEY" \
--json '{"requests":[{"type":"execute","stmt":{"sql":"CREATE TABLE IF NOT EXISTS notes(id INTEGER PRIMARY KEY, body TEXT)"}},{"type":"execute","stmt":{"sql":"INSERT INTO notes(body) VALUES(?)","args":[{"type":"text","value":"Hello"}]}},{"type":"close"}]}'
Read rows
curl --fail-with-body "https://api.wabe.sh/v1/disks/demo/sqlite/mydb/hrana/v3/pipeline" \
-H "Authorization: Bearer YOUR_PROJECT_API_KEY" \
--json '{"requests":[{"type":"execute","stmt":{"sql":"SELECT id, body FROM notes ORDER BY id"}},{"type":"close"}]}'

Values use typed objects: {"type":"null"}, {"type":"text","value":"Hello"}, {"type":"integer","value":"9223372036854775807"}, {"type":"float","value":4.5} or {"type":"blob","base64":"AP8="}. Integer values use decimal strings; non-finite reals cannot be represented by this profile. UPSERT conflict-update last-rowid metadata may retain SQLite’s previous insert rowid; that metadata edge is not certified. Rows-read, rows-written and duration statistics are omitted.

Pipeline returns buffered JSON. In a new database, the read request above returns:

Read response
{"baton":null,"base_url":null,"results":[{"type":"ok","response":{"type":"execute","result":{"cols":[{"name":"id","decltype":"INTEGER"},{"name":"body","decltype":"TEXT"}],"rows":[[{"type":"integer","value":"1"},{"type":"text","value":"Hello"}]],"affected_row_count":0,"last_insert_rowid":null}}},{"type":"ok","response":{"type":"close"}}]}

Cursor returns application/x-ndjson: a session header, then step metadata, rows, step results or errors. Its header baton becomes usable after the response body completes. Consume or close the body; an abandoned cursor closes the session and rolls back an open transaction. Batons rotate and remain scoped to the disk and database. Continuations still require Bearer authentication.

An open transaction retains its disk lease between requests while releasing its SQL worker and concurrency permit. Eviction waits for COMMIT or ROLLBACK. The absolute transaction TTL is 60 seconds from leaving autocommit through BEGIN or an outermost SAVEPOINT; activity does not extend it. Expiry rolls back and closes before releasing the lease. Idle sessions outside transactions expire after 30 seconds and may be evicted.

A successful SQL COMMIT confirms buffered state. It does not force an object-store flush or wait for persistence. The default background WAL interval is 10 seconds; size triggers can flush earlier, and storage latency or failures can delay completion. Use explicit disk flush to confirm persistence of preceding writes. Eviction and graceful shutdown also flush pending data. Finishing a cursor alone does not commit a transaction.

Statement errors appear in protocol results, including after HTTP 200. Errors include a message, a base SQLite code and an optional symbolic extended code. Ordinary SQL errors permit later protocol operations, including ROLLBACK. Fatal policy, resource, storage, timeout or cancellation errors close the session; after cursor headers, a failure can terminate the stream. The SDK exposes these failures through its error handling.

Authentication returns 401 before disk access. Invalid names or protocol shapes return 400, invalid or expired batons return 404, policy violations return 403 and resource limits return 413. A SQL deadline can return 408; unavailable execution can return 503. An interrupted mutation response does not establish whether a transaction committed. Inspect application state before retrying a mutation whose repeated execution changes its meaning.

See the Drizzle example.

Defaults are 16 active SQL requests per router, a 30-second SQL deadline, a 4-MiB JSON request limit, a 64-KiB SQL text limit per statement, an 8-MiB total encoded result limit and 128 statements per request, including driver-generated statements. Shell and JavaScript wabesql calls share the active-request budget with HTTP SQL. Cursor backpressure counts toward the deadline. The SQL deadline starts after disk/session context acquisition; disk opening and HTTP JSON extraction precede it.

Sessions are bounded to 64 per router and 32 per disk/database, including cleanup. Each session stores at most 64 SQL texts totaling 256 KiB. Other limits cover rows, columns, parameters and values. These limits do not provide a hard per-query SQLite heap cap.

Set WABE_DISK_SQL_MAX_CONCURRENT and WABE_DISK_SQL_TIMEOUT_MS before startup to change concurrency and deadline. See server configuration and persistence.