Skip to content
Hoody.com

Use these endpoints to list the keys in a SQLite key-value store. The store runs in one of three modes: regular (a single .db file), directory mode (a directory of files), or time-travel via at_timestamp. The response envelope differs by mode; the per-field descriptions note which mode each field applies to.

List all keys in the KV store with optional filtering and pagination.

NameInTypeRequiredDescription
dbquerystringYesDatabase file path or directory
tablequerystringNoCustom table name. Default: "kv_store"
prefixquerystringNoFilter keys by prefix
limitqueryintegerNoMaximum number of results. Values above 1000 are treated as 1000; a negative value is rejected with 400. 0 returns an empty page, except with at_timestamp, where it means no limit. Default: 100
offsetqueryintegerNoSkip N results for pagination. Honoured for both the regular listing and the at_timestamp listing; results are ordered by key in both cases. A negative value is rejected with 400. Default: 0
afterquerystringNoCursor: list only keys that sort after this one (byte order, exclusive). Pass the previous page’s next_after; a key written or deleted elsewhere between two pages never makes another key be skipped or repeated. The value is used as given, so any string works, including a key that no longer exists. Combined with offset greater than 0 or with at_timestamp it is rejected with 400
at_timestampqueryintegerNoUnix timestamp for time-travel LIST: returns the keys as they stood at that moment, in a different response envelope. 0 or omitted lists the current keys; a time in the future is rejected with 400. A directory (directory mode) is rejected with 400 in this mode
timeoutqueryintegerNoDeadline for this request, in whole seconds, clamped to [1, 300]. Once it passes, a long operation stops at its next checkpoint rather than being cut off mid-step; a step already running, such as a filesystem scan or a wait for another writer, finishes first. A request that has not finished by then answers 503 REQUEST_TIMEOUT, except that a write which has already committed still returns its success. A value that is not a whole number is ignored and the default applies. This is a server-side deadline, not a client transport timeout. Default: 30

Keys listed. The envelope depends on the mode: regular, directory mode (db names a directory) or at_timestamp; each field says which modes carry it.

{
"success": true,
"count": 2,
"items": [
{
"key": "user:1:profile",
"content_type": "application/json",
"size": 256,
"created_at": 1700000000,
"updated_at": 1700000100,
"etag": "a1b2c3d4e5f6"
},
{
"key": "user:2:profile",
"content_type": "application/json",
"size": 312,
"created_at": 1700000200,
"updated_at": 1700000300,
"etag": "f6e5d4c3b2a1"
}
],
"limit": 100,
"offset": 0,
"has_more": false,
"next_after": null,
"change_cursor": "0/42"
}
FieldTypeModeDescription
successbooleanallAlways true on a 200
countintegerallNumber of entries in items
itemsarrayallThe page of keys. null in regular mode for an empty page
items[].keystringallThe key
items[].content_typestringallThe content type. In directory mode, derived from the file extension; in at_timestamp mode, the value the entry had at that timestamp
items[].sizeintegerregular, directorySize of the stored value; in directory mode, the file size in bytes
items[].created_atintegerregular, directoryCreation time, Unix seconds; in directory mode, the file modification time
items[].updated_atintegerregular, directoryLast update time, Unix seconds; in directory mode, the file modification time
items[].etagstringregularThe entry’s current ETag, the same value GET /kv/{key} sends; usable in If-Match
items[].expire_atintegerregularExpiry time, Unix seconds. Present only when the key has a TTL
items[].existed_at_timestampbooleanat_timestampAlways true: only keys that existed at the timestamp are listed
items[].op_numberintegerat_timestampHistory operation number of the write that set the value seen at the timestamp; 0 when the key has no recorded history
items[].size_estimateintegerat_timestampApproximate size: the size of the recorded history entry for that write, or of the current value when the current value is reported. Not the exact size of the value
offsetintegerallThe offset applied, as requested
limitintegerregular, directoryThe page size applied, after the 1000 cap. Absent in at_timestamp mode
has_morebooleanregular, directoryTrue when at least one more key follows this page
next_afterstringregular, directoryCursor for the next page: pass it as after. The last key this page covered, or null when no key follows. With limit=0 it echoes after, null when after was not given
change_cursorstringregularChange-feed cursor read in the same snapshot as items: pass it as since to GET /changes or /changes/stream to receive every write made after this page was read. null when the page was read without versions (the database was busy)
scan_errorsintegerregularRows that could not be read and were left out of items. Present only when at least one row was left out
directory_modebooleandirectoryAlways true when present
totalintegerdirectoryNumber of keys matching prefix before pagination
timestampintegerat_timestampThe requested at_timestamp
has_gapsbooleanat_timestampTrue when gap_keys is non-empty
gap_keysarrayat_timestampKeys left out of items because a write made with history=false leaves their state at the timestamp unknown. null when has_gaps is false
candidate_truncatedbooleanat_timestampTrue when the candidate-key scan stopped at its internal cap, so keys may be missing from items without appearing in gap_keys; narrow the request with prefix to get a complete listing
import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-sqlite-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN });
const response = await client.sqlite.kv.list({ db: './app.db', prefix: 'user:', limit: 50 });