KV Store: Basic Operations
Section titled “KV Store: Basic Operations”The KV store is a per-database key-value table. It runs in either SQLite mode (one .db file) or directory mode (a folder of files), and supports hierarchical keys, optional TTL (SQLite mode), JSON-path access into nested values, compare-and-swap, and time-travel reads that reconstruct the value at a given moment using the key’s recorded history (and fall back to the current value when no recorded write establishes the value at that moment). This page covers the everyday read and write operations: get, set, delete, exists, the two atomic counter operations (incr and decr), reading a key’s full entry in one call, and TTL management.
Get value by key
Section titled “Get value by key”GET /api/v1/sqlite/kv/{key}
Retrieve a value from the KV store with optional JSON path extraction and time-travel support.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
key | path | string | Yes | Key name (supports / for hierarchical keys) |
db | query | string | Yes | Database file path or directory |
table | query | string | No | Custom table name (default: kv_store) |
path | query | string | No | JSON path for nested value extraction. The extracted value is returned as JSON with an X-JSON-Path header; a path that does not exist in the value answers 404. Ignored with at_timestamp. |
at_timestamp | query | integer | No | Unix timestamp. Returns the value the key held at that moment in a JSON envelope, instead of the current value. 0 or omitted reads the current value; a time in the future is rejected with 400. A directory (directory mode) is rejected with 400 in this mode. A write made with history=false that leaves the value at that moment unknown answers 409 TIME_TRAVEL_CHAIN_GAP. |
rebuild | query | boolean | No | Directory mode only: clear the directory’s cached file information (this directory only, not its subdirectories), then read the key. Ignored with at_timestamp. |
If-None-Match | header | string | No | Answers 304 while the key’s current ETag matches. |
timeout | query | integer | No | Server-side deadline in seconds, clamped to [1, 300]. Defaults to 30. |
Response
Section titled “Response”The body is the stored value itself, returned with its stored Content-Type. The Content-Type is application/json when the value was stored as JSON, or application/octet-stream when it was stored as raw bytes. With a non-zero at_timestamp, the body is always a JSON envelope carrying the value as it was at that time.
{ "user": "alice", "score": 42, "active": true}| Header | Description |
|---|---|
ETag | The key’s ETag; it changes on every write of the key, TTL changes included. Not sent with at_timestamp, in directory mode, or when the server could not verify the key’s version. |
X-Created-At | Unix timestamp when created; in directory mode, the file modification time. Not sent with at_timestamp. |
X-Updated-At | Unix timestamp when last updated; in directory mode, the file modification time. Not sent with at_timestamp. |
X-Expire-At | Unix timestamp when the key expires; sent only when a TTL is set. Not sent with at_timestamp. |
X-JSON-Path | The path that was extracted, sent when path is set. |
X-Directory-Mode | Set to true when db is a directory (directory mode). |
X-File-Hash | SHA-256 of the value file, hex-encoded. Directory mode only, and not sent when path is set. |
X-KV-Nesting-Depth | Number of /-separated segments in the key. Directory mode only. |
X-KV-Reference | Set to true if value is a KV store reference. |
X-Reference-Path | The reference target, sent with X-KV-Reference. |
Not modified: the key’s ETag matches If-None-Match. The response has no body.
{ "code": "INVALID_PARAMETERS", "error": "Invalid request parameters"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_PARAMETERS | Invalid request parameters | A parameter or the request body is missing, malformed or out of range. This is the default code for a 400 that has no more specific one. | Check parameter types and values against the API specification; the error text names the problem |
INVALID_DB_PATH | Invalid database path | The db value is empty, relative, outside /hoody/databases (unless the server allows any absolute path), contains ? or #, escapes via a symlink, or names something that is not a regular file. | Provide an absolute path under /hoody/databases, or a bare name / ./name shorthand (resolved to /hoody/databases/*.db). |
DATABASE_NOT_FOUND | Database file does not exist | The database file does not exist and this request does not create it. | Check the path, create the database first, or pass create_db_if_missing=true where the operation supports it. |
INVALID_SQLITE_HEADER | Not a valid SQLite database | The file exists but is empty or does not start with the SQLite header. | Point db at a SQLite database file. |
PATH_IS_DIRECTORY | Path is a directory | Expected a database file but the path is a directory. Only the KV endpoints accept a directory (directory mode). | Use a .db file path. |
{ "code": "FORBIDDEN", "error": "Forbidden"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
FORBIDDEN | Forbidden | The request is not allowed: a path that escapes the database directory, or a request refused by the Source IP Guard (Hoody Kit programs are reached through their URLs only). | Use a path inside the permitted directory, and reach the program through its URL. |
{ "code": "KEY_NOT_FOUND", "error": "Key not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NOT_FOUND | Not found | The requested item does not exist. Default code for a 404 that has no more specific one. | Check the identifiers in the request. |
KEY_NOT_FOUND | Key not found | The key does not exist (or did not exist at the requested timestamp / operation). | Verify the key name and database/table parameters. |
KEY_EXPIRED | Key expired | The key existed but its TTL had expired at the requested timestamp. | Ask for an earlier timestamp, or store a new value. |
A write made with history=false (or a pruned history entry) sits in the queried range, so the value at that point cannot be reconstructed. In directory mode, the value file is a SQLite database (or a side file of one) that this server has open and will not read, replace, or delete as a value.
{ "code": "TIME_TRAVEL_CHAIN_GAP", "error": "History has a gap"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
CONFLICT | Conflict | The request conflicts with the current state. Default code for a 409 that has no more specific one. | Read the current state and retry with a request that matches it. |
TIME_TRAVEL_CHAIN_GAP | History has a gap | A write made with history=false (or a pruned history entry) sits in the range, so the value at that point cannot be reconstructed. | Pick a point after the gap; writes made with history=false cannot be reconstructed. |
{ "code": "INTERNAL_ERROR", "error": "Internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INTERNAL_ERROR | Internal server error | An unexpected server-side failure. Default code for a 500 that has no more specific one; details are in the server log. | Retry later; report it if it persists. |
DATABASE_ERROR | Database fault | SQLite failed for a reason that is not the request: I/O error, corruption, disk full, a file that cannot be opened. The message is sanitised; details are in the server log. | Check the server log, disk space and the database file; the database may need repair. |
{ "code": "DATABASE_BUSY", "error": "Database busy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
SERVICE_UNAVAILABLE | Service unavailable | The service could not complete the request right now. Default code for a 503 that has no more specific one. | Retry after a short delay. |
REQUEST_TIMEOUT | Request deadline exceeded | The request did not finish before its deadline (the timeout parameter, default 30 s); nothing was committed. | Retry, or raise timeout (max 300). |
DATABASE_BUSY | Database busy | The database was locked by another writer or in use for longer than the request could wait; nothing was changed. | Retry after a short delay. |
Code examples
Section titled “Code examples”curl -X GET "https://{projectId}-{containerId}-sqlite-1.{server}.containers.hoody.com/api/v1/sqlite/kv/users/alice?db=app.db" \ -H "Authorization: Bearer $HOODY_TOKEN"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-sqlite-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN,});
// Basic readconst value = await client.sqlite.kv.get('users/alice', { db: 'app.db' });
// JSON path extractionconst score = await client.sqlite.kv.get('users/alice', { db: 'app.db', path: '.score' });
// Time-travel read (returns a JSON envelope)const past = await client.sqlite.kv.get('users/alice', { db: 'app.db', at_timestamp: 1700000000 });Set value for key
Section titled “Set value for key”PUT /api/v1/sqlite/kv/{key}
Store or update a value in the KV store with optional TTL, JSON path, and compare-and-swap.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
key | path | string | Yes | Key name |
db | query | string | Yes | Database file path or directory |
table | query | string | No | Custom table name (default: kv_store) |
path | query | string | No | JSON path for nested value update |
ttl | query | integer | No | Time-to-live in seconds. SQLite mode only; a non-zero ttl against a directory-mode store is rejected with 400. |
if_match | query | string | No | Current value for compare-and-swap. SQLite mode only; rejected with 400 against a directory-mode store. |
history | query | boolean | No | Store this write’s value in history; false records only that the write happened, not what it wrote. (default: true) |
create_db_if_missing | query | boolean | No | Create database file if it is missing. The legacy alias auto_create is still accepted and means the same thing. (default: false) |
If-Match | header | string | No | Applies the write only if the key’s current ETag matches. |
If-None-Match | header | string | No | Set to * to write only if the key does not exist yet. |
Idempotency-Key | header | string | No | Replays the stored response for a repeat of the same key (24 h). |
timeout | query | integer | No | Server-side deadline in seconds, clamped to [1, 300]. Defaults to 30. |
Request body
Section titled “Request body”The body is the value to store. With Content-Type: application/json the body must be one valid JSON document: an object, array, number, boolean, null, or a quoted string. To store raw text or bytes, send Content-Type: text/plain or application/octet-stream; those are stored verbatim. The Content-Type is recorded with the value and returned by a subsequent GET.
{ "name": "alice", "role": "admin", "score": 42}Response
Section titled “Response”{ "key": "users/alice", "size": 47, "success": true, "etag": "\"a1b2c3d4e5f6\""}| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | The key written. |
size | integer | Yes | Size of the stored value in bytes. With path, the size of the whole updated value. |
success | boolean | Yes | Always true on a 200. |
etag | string | No | The key’s ETag after the write (also sent as the ETag header). SQLite mode only. |
directory_mode | boolean | No | Always true when present. Directory mode only. |
file_path | string | No | Absolute path of the file that holds the value. Directory mode only. |
path | string | No | The path that was set. Present only when path was sent. |
{ "code": "INVALID_JSON_VALUE", "error": "Body is not valid JSON"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_PARAMETERS | Invalid request parameters | A parameter or the request body is missing, malformed or out of range. This is the default code for a 400 that has no more specific one. | Check parameter types and values against the API specification; the error text names the problem |
INVALID_DB_PATH | Invalid database path | The db value is empty, relative, outside /hoody/databases (unless the server allows any absolute path), contains ? or #, escapes via a symlink, or names something that is not a regular file. | Provide an absolute path under /hoody/databases, or a bare name / ./name shorthand (resolved to /hoody/databases/*.db). |
DATABASE_NOT_FOUND | Database file does not exist | The database file does not exist and this request does not create it. | Check the path, create the database first, or pass create_db_if_missing=true where the operation supports it. |
INVALID_SQLITE_HEADER | Not a valid SQLite database | The file exists but is empty or does not start with the SQLite header. | Point db at a SQLite database file. |
PATH_IS_DIRECTORY | Path is a directory | Expected a database file but the path is a directory. Only the KV endpoints accept a directory (directory mode). | Use a .db file path. |
INVALID_JSON_VALUE | Body is not valid JSON | The value was sent as application/json but is not one valid JSON document (for example an unquoted string). | Serialise the value as JSON (strings quoted), or send raw text/bytes as text/plain or application/octet-stream. |
{ "code": "FORBIDDEN", "error": "Forbidden"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
FORBIDDEN | Forbidden | The request is not allowed: a path that escapes the database directory, or a request refused by the Source IP Guard (Hoody Kit programs are reached through their URLs only). | Use a path inside the permitted directory, and reach the program through its URL. |
{ "code": "CONFLICT", "error": "Conflict"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
CONFLICT | Conflict | The request conflicts with the current state. Default code for a 409 that has no more specific one. | Read the current state and retry with a request that matches it. |
Returned when an if_match value is supplied and does not match the currently stored value, or when an If-Match or If-None-Match header condition does not hold. Nothing was written.
{ "code": "PRECONDITION_FAILED", "error": "Precondition failed", "current_etag": "\"a1b2c3d4e5f6\""}| Error Code | Title | Description | Resolution |
|---|---|---|---|
PRECONDITION_FAILED | Precondition failed | The compare-and-swap value (if_match) did not match the stored value, or an If-Match / If-None-Match condition did not hold; current_etag carries the current ETag. Nothing was written. | Read the current value and ETag and retry with them. |
{ "code": "PAYLOAD_TOO_LARGE", "error": "Request too large"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Request too large | The request exceeds a size or item-count limit. | Split the request. |
IDEMPOTENT_RESPONSE_TOO_LARGE | Response too large to store | With Idempotency-Key, the response would be larger than 1 MiB and could not be stored for replay; nothing was written. | Send the request without Idempotency-Key, or narrow it. |
{ "code": "IDEMPOTENCY_KEY_MISMATCH", "error": "Idempotency-Key reused"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
IDEMPOTENCY_KEY_MISMATCH | Idempotency-Key reused | The Idempotency-Key was already used, within its lifetime, for a different request (method, key, query, conditions, content type or body differ); nothing was written. | Use a new Idempotency-Key for a different request. |
{ "code": "INTERNAL_ERROR", "error": "Internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INTERNAL_ERROR | Internal server error | An unexpected server-side failure. Default code for a 500 that has no more specific one; details are in the server log. | Retry later; report it if it persists. |
DATABASE_ERROR | Database fault | SQLite failed for a reason that is not the request: I/O error, corruption, disk full, a file that cannot be opened. The message is sanitised; details are in the server log. | Check the server log, disk space and the database file; the database may need repair. |
{ "code": "DATABASE_BUSY", "error": "Database busy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
SERVICE_UNAVAILABLE | Service unavailable | The service could not complete the request right now. Default code for a 503 that has no more specific one. | Retry after a short delay. |
REQUEST_TIMEOUT | Request deadline exceeded | The request did not finish before its deadline (the timeout parameter, default 30 s); nothing was committed. | Retry, or raise timeout (max 300). |
DATABASE_BUSY | Database busy | The database was locked by another writer or in use for longer than the request could wait; nothing was changed. | Retry after a short delay. |
Code examples
Section titled “Code examples”curl -X PUT "https://{projectId}-{containerId}-sqlite-1.{server}.containers.hoody.com/api/v1/sqlite/kv/users/alice?db=app.db" \ -H "Authorization: Bearer $HOODY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"alice","role":"admin","score":42}'import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-sqlite-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN,});
// Store a JSON valueawait client.sqlite.kv.set( 'users/alice', { name: 'alice', role: 'admin', score: 42 }, { db: 'app.db' },);
// Compare-and-swap: only update if the current value matchesawait client.sqlite.kv.set( 'users/alice', { name: 'alice', role: 'admin', score: 43 }, { db: 'app.db', if_match: '{"name":"alice","role":"admin","score":42}' },);Delete key
Section titled “Delete key”DELETE /api/v1/sqlite/kv/{key}
Remove a key-value pair from the store.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
key | path | string | Yes | Key name |
db | query | string | Yes | Database file path or directory |
table | query | string | No | Custom table name (default: kv_store) |
history | query | boolean | No | Store this write’s value in history; false records only that the write happened, not what it wrote. (default: true) |
create_db_if_missing | query | boolean | No | Create database file if it is missing. The legacy alias auto_create is still accepted and means the same thing. (default: false) |
timeout | query | integer | No | Server-side deadline in seconds, clamped to [1, 300]. Defaults to 30. |
If-Match | header | string | No | Applies the write only if the key’s current ETag matches. |
Idempotency-Key | header | string | No | Replays the stored response for a repeat of the same key (24 h). |
Response
Section titled “Response”{ "success": true, "deleted": true}| Field | Type | Required | Description |
|---|---|---|---|
success | boolean | Yes | Always true on a 200. |
deleted | boolean | Yes | Always true on a 200 (a missing key is a 404). |
directory_mode | boolean | No | Always true when present. Directory mode only. |
{ "code": "INVALID_PARAMETERS", "error": "Invalid request parameters"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_PARAMETERS | Invalid request parameters | A parameter or the request body is missing, malformed or out of range. This is the default code for a 400 that has no more specific one. | Check parameter types and values against the API specification; the error text names the problem |
INVALID_DB_PATH | Invalid database path | The db value is empty, relative, outside /hoody/databases (unless the server allows any absolute path), contains ? or #, escapes via a symlink, or names something that is not a regular file. | Provide an absolute path under /hoody/databases, or a bare name / ./name shorthand (resolved to /hoody/databases/*.db). |
DATABASE_NOT_FOUND | Database file does not exist | The database file does not exist and this request does not create it. | Check the path, create the database first, or pass create_db_if_missing=true where the operation supports it. |
INVALID_SQLITE_HEADER | Not a valid SQLite database | The file exists but is empty or does not start with the SQLite header. | Point db at a SQLite database file. |
PATH_IS_DIRECTORY | Path is a directory | Expected a database file but the path is a directory. Only the KV endpoints accept a directory (directory mode). | Use a .db file path. |
{ "code": "KEY_NOT_FOUND", "error": "Key not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NOT_FOUND | Not found | The requested item does not exist. Default code for a 404 that has no more specific one. | Check the identifiers in the request. |
KEY_NOT_FOUND | Key not found | The key does not exist (or did not exist at the requested timestamp / operation). | Verify the key name and database/table parameters. |
{ "code": "INTERNAL_ERROR", "error": "Internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INTERNAL_ERROR | Internal server error | An unexpected server-side failure. Default code for a 500 that has no more specific one; details are in the server log. | Retry later; report it if it persists. |
DATABASE_ERROR | Database fault | SQLite failed for a reason that is not the request: I/O error, corruption, disk full, a file that cannot be opened. The message is sanitised; details are in the server log. | Check the server log, disk space and the database file; the database may need repair. |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
SERVICE_UNAVAILABLE | Service unavailable | The service could not complete the request right now. Default code for a 503 that has no more specific one | Retry after a short delay |
REQUEST_TIMEOUT | Request deadline exceeded | The request did not finish before its deadline (the timeout parameter, default 30 s); nothing was committed | Retry, or raise timeout (max 300) |
DATABASE_BUSY | Database busy | The database was locked by another writer or in use for longer than the request could wait; nothing was changed | Retry after a short delay |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
IDEMPOTENCY_KEY_MISMATCH | Idempotency-Key reused | The Idempotency-Key was already used, within its lifetime, for a different request (method, key, query, conditions, content type or body differ); nothing was written | Use a new Idempotency-Key for a different request |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Request too large | The request exceeds a size or item-count limit | Split the request |
IDEMPOTENT_RESPONSE_TOO_LARGE | Response too large to store | With Idempotency-Key, the response would be larger than 1 MiB and could not be stored for replay; nothing was written | Send the request without Idempotency-Key, or narrow it |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
PRECONDITION_FAILED | Precondition failed | The compare-and-swap value (if_match) did not match the stored value, or an If-Match / If-None-Match condition (or a batch item’s if_match / if_none_match) did not hold; current_etag (failed for a batch) carries the current ETag. Nothing was written | Read the current value and ETag and retry with them |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
CONFLICT | Conflict | The request conflicts with the current state. Default code for a 409 that has no more specific one | Read the current state and retry with a request that matches it |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
FORBIDDEN | Forbidden | The request is not allowed: a path that escapes the database directory, or a request refused by the Source IP Guard (Hoody Kit programs are reached through their URLs only) | Use a path inside the permitted directory, and reach the program through its URL |
Code examples
Section titled “Code examples”curl -X DELETE "https://{projectId}-{containerId}-sqlite-1.{server}.containers.hoody.com/api/v1/sqlite/kv/users/alice?db=app.db" \ -H "Authorization: Bearer $HOODY_TOKEN"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 result = await client.sqlite.kv.delete('users/alice', { db: 'app.db' });console.log(result.data.deleted);Check if key exists
Section titled “Check if key exists”HEAD /api/v1/sqlite/kv/{key}
Check whether a key exists in the KV store without retrieving its value. The method returns only the status code and a small set of headers, so it is cheaper than a full GET for existence checks.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
key | path | string | Yes | Key name |
db | query | string | Yes | Database file path or directory |
table | query | string | No | Custom table name (default: kv_store) |
If-None-Match | header | string | No | Answers 304 while the key’s current ETag matches. |
timeout | query | integer | No | Server-side deadline in seconds, clamped to [1, 300]. Defaults to 30. |
Response
Section titled “Response”The key exists.
Not modified: the key exists and its ETag matches If-None-Match. The response has no body.
Invalid request parameters, a malformed If-None-Match, or If-Match, If-None-Match or Idempotency-Key in directory mode.
db is a directory outside /hoody/databases, or reaches outside it through a symlink.
Key not found or expired. The reason is conveyed in the X-Hoody-Error-Code response header. A 404 without that header did not come from this operation’s key lookup (an unknown route, or a proxy).
| Header | Description |
|---|---|
X-Hoody-Error-Code | KEY_NOT_FOUND (no such key) or KEY_EXPIRED (the key’s TTL has passed) |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
KEY_NOT_FOUND | Key not found | The key does not exist (or did not exist at the requested timestamp / operation). | Verify the key name and database/table parameters. |
KEY_EXPIRED | Key expired | The key exists but its TTL has passed. | Store a new value, or ask for an earlier time. |
In directory mode, the key’s value file is a SQLite database (or a side file of one) that this server has open; GET answers 409 CONFLICT for it.
Internal server error.
Database busy (including: If-None-Match was sent and the key’s version could not be verified), request deadline exceeded, or server shutting down.
Code examples
Section titled “Code examples”curl -I "https://{projectId}-{containerId}-sqlite-1.{server}.containers.hoody.com/api/v1/sqlite/kv/users/alice?db=app.db" \ -H "Authorization: Bearer $HOODY_TOKEN"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 exists = await client.sqlite.kv.exists('users/alice', { db: 'app.db' });Atomic increment
Section titled “Atomic increment”POST /api/v1/sqlite/kv/{key}/incr
Atomically increment a numeric value. Supports JSON paths for nested values.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
key | path | string | Yes | Key name |
db | query | string | Yes | Database file path |
table | query | string | No | Custom table name (default: kv_store) |
delta | query | integer | No | Amount to increment (default: 1) |
path | query | string | No | JSON path to nested numeric value |
history | query | boolean | No | Store this write’s value in history; false records only that the write happened, not what it wrote. (default: true) |
create_db_if_missing | query | boolean | No | Create database file if it is missing. The legacy alias auto_create is still accepted and means the same thing. (default: false) |
timeout | query | integer | No | Server-side deadline in seconds, clamped to [1, 300]. Defaults to 30. |
If-Match | header | string | No | Applies the write only if the key’s current ETag matches. |
Idempotency-Key | header | string | No | Replays the stored response for a repeat of the same key (24 h). |
Response
Section titled “Response”{ "key": "visits/today", "value": 6, "etag": "\"a1b2c3d4e5f6\"", "success": true}| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | The key changed. |
value | integer | Yes | The value after the change; with path, the nested value. |
etag | string | Yes | The key’s ETag after the write (also sent as the ETag header). |
success | boolean | Yes | Always true on a 200. |
path | string | No | The path that was changed. Present only when path was sent. |
{ "code": "INVALID_PARAMETERS", "error": "Invalid request parameters"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_PARAMETERS | Invalid request parameters | A parameter or the request body is missing, malformed or out of range. This is the default code for a 400 that has no more specific one. | Check parameter types and values against the API specification; the error text names the problem |
INVALID_DB_PATH | Invalid database path | The db value is empty, relative, outside /hoody/databases (unless the server allows any absolute path), contains ? or #, escapes via a symlink, or names something that is not a regular file. | Provide an absolute path under /hoody/databases, or a bare name / ./name shorthand (resolved to /hoody/databases/*.db). |
DATABASE_NOT_FOUND | Database file does not exist | The database file does not exist and this request does not create it. | Check the path, create the database first, or pass create_db_if_missing=true where the operation supports it. |
INVALID_SQLITE_HEADER | Not a valid SQLite database | The file exists but is empty or does not start with the SQLite header. | Point db at a SQLite database file. |
PATH_IS_DIRECTORY | Path is a directory | Expected a database file but the path is a directory. Only the KV endpoints accept a directory (directory mode). | Use a .db file path. |
{ "code": "INTERNAL_ERROR", "error": "Internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INTERNAL_ERROR | Internal server error | An unexpected server-side failure. Default code for a 500 that has no more specific one; details are in the server log. | Retry later; report it if it persists. |
DATABASE_ERROR | Database fault | SQLite failed for a reason that is not the request: I/O error, corruption, disk full, a file that cannot be opened. The message is sanitised; details are in the server log. | Check the server log, disk space and the database file; the database may need repair. |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
SERVICE_UNAVAILABLE | Service unavailable | The service could not complete the request right now. Default code for a 503 that has no more specific one | Retry after a short delay |
REQUEST_TIMEOUT | Request deadline exceeded | The request did not finish before its deadline (the timeout parameter, default 30 s); nothing was committed | Retry, or raise timeout (max 300) |
DATABASE_BUSY | Database busy | The database was locked by another writer or in use for longer than the request could wait; nothing was changed | Retry after a short delay |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
IDEMPOTENCY_KEY_MISMATCH | Idempotency-Key reused | The Idempotency-Key was already used, within its lifetime, for a different request (method, key, query, conditions, content type or body differ); nothing was written | Use a new Idempotency-Key for a different request |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Request too large | The request exceeds a size or item-count limit | Split the request |
IDEMPOTENT_RESPONSE_TOO_LARGE | Response too large to store | With Idempotency-Key, the response would be larger than 1 MiB and could not be stored for replay; nothing was written | Send the request without Idempotency-Key, or narrow it |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
PRECONDITION_FAILED | Precondition failed | The compare-and-swap value (if_match) did not match the stored value, or an If-Match / If-None-Match condition (or a batch item’s if_match / if_none_match) did not hold; current_etag (failed for a batch) carries the current ETag. Nothing was written | Read the current value and ETag and retry with them |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
CONFLICT | Conflict | The request conflicts with the current state. Default code for a 409 that has no more specific one | Read the current state and retry with a request that matches it |
Code examples
Section titled “Code examples”curl -X POST "https://{projectId}-{containerId}-sqlite-1.{server}.containers.hoody.com/api/v1/sqlite/kv/visits/today/incr?db=app.db&delta=1" \ -H "Authorization: Bearer $HOODY_TOKEN"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 result = await client.sqlite.kv.increment('visits/today', { db: 'app.db', delta: 1 });console.log(result.data.value);Atomic decrement
Section titled “Atomic decrement”POST /api/v1/sqlite/kv/{key}/decr
Atomically decrement a numeric value. Supports JSON paths for nested values.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
key | path | string | Yes | Key name |
db | query | string | Yes | Database file path |
table | query | string | No | Custom table name (default: kv_store) |
delta | query | integer | No | Amount to decrement (default: 1) |
path | query | string | No | JSON path to nested numeric value |
history | query | boolean | No | Store this write’s value in history; false records only that the write happened, not what it wrote. (default: true) |
create_db_if_missing | query | boolean | No | Create database file if it is missing. The legacy alias auto_create is still accepted and means the same thing. (default: false) |
timeout | query | integer | No | Server-side deadline in seconds, clamped to [1, 300]. Defaults to 30. |
If-Match | header | string | No | Applies the write only if the key’s current ETag matches. |
Idempotency-Key | header | string | No | Replays the stored response for a repeat of the same key (24 h). |
Response
Section titled “Response”{ "key": "seats/remaining", "value": 4, "etag": "\"a1b2c3d4e5f6\"", "success": true}| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | The key changed. |
value | integer | Yes | The value after the change; with path, the nested value. |
etag | string | Yes | The key’s ETag after the write (also sent as the ETag header). |
success | boolean | Yes | Always true on a 200. |
path | string | No | The path that was changed. Present only when path was sent. |
{ "code": "INVALID_PARAMETERS", "error": "Invalid request parameters"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_PARAMETERS | Invalid request parameters | A parameter or the request body is missing, malformed or out of range. This is the default code for a 400 that has no more specific one. | Check parameter types and values against the API specification; the error text names the problem |
INVALID_DB_PATH | Invalid database path | The db value is empty, relative, outside /hoody/databases (unless the server allows any absolute path), contains ? or #, escapes via a symlink, or names something that is not a regular file. | Provide an absolute path under /hoody/databases, or a bare name / ./name shorthand (resolved to /hoody/databases/*.db). |
DATABASE_NOT_FOUND | Database file does not exist | The database file does not exist and this request does not create it. | Check the path, create the database first, or pass create_db_if_missing=true where the operation supports it. |
INVALID_SQLITE_HEADER | Not a valid SQLite database | The file exists but is empty or does not start with the SQLite header. | Point db at a SQLite database file. |
PATH_IS_DIRECTORY | Path is a directory | Expected a database file but the path is a directory. Only the KV endpoints accept a directory (directory mode). | Use a .db file path. |
{ "code": "INTERNAL_ERROR", "error": "Internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INTERNAL_ERROR | Internal server error | An unexpected server-side failure. Default code for a 500 that has no more specific one; details are in the server log. | Retry later; report it if it persists. |
DATABASE_ERROR | Database fault | SQLite failed for a reason that is not the request: I/O error, corruption, disk full, a file that cannot be opened. The message is sanitised; details are in the server log. | Check the server log, disk space and the database file; the database may need repair. |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
SERVICE_UNAVAILABLE | Service unavailable | The service could not complete the request right now. Default code for a 503 that has no more specific one | Retry after a short delay |
REQUEST_TIMEOUT | Request deadline exceeded | The request did not finish before its deadline (the timeout parameter, default 30 s); nothing was committed | Retry, or raise timeout (max 300) |
DATABASE_BUSY | Database busy | The database was locked by another writer or in use for longer than the request could wait; nothing was changed | Retry after a short delay |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
IDEMPOTENCY_KEY_MISMATCH | Idempotency-Key reused | The Idempotency-Key was already used, within its lifetime, for a different request (method, key, query, conditions, content type or body differ); nothing was written | Use a new Idempotency-Key for a different request |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Request too large | The request exceeds a size or item-count limit | Split the request |
IDEMPOTENT_RESPONSE_TOO_LARGE | Response too large to store | With Idempotency-Key, the response would be larger than 1 MiB and could not be stored for replay; nothing was written | Send the request without Idempotency-Key, or narrow it |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
PRECONDITION_FAILED | Precondition failed | The compare-and-swap value (if_match) did not match the stored value, or an If-Match / If-None-Match condition (or a batch item’s if_match / if_none_match) did not hold; current_etag (failed for a batch) carries the current ETag. Nothing was written | Read the current value and ETag and retry with them |
| Error Code | Title | Description | Resolution |
|---|---|---|---|
CONFLICT | Conflict | The request conflicts with the current state. Default code for a 409 that has no more specific one | Read the current state and retry with a request that matches it |
Code examples
Section titled “Code examples”curl -X POST "https://{projectId}-{containerId}-sqlite-1.{server}.containers.hoody.com/api/v1/sqlite/kv/seats/remaining/decr?db=app.db&delta=1" \ -H "Authorization: Bearer $HOODY_TOKEN"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 result = await client.sqlite.kv.decrement('seats/remaining', { db: 'app.db', delta: 1 });console.log(result.data.value);Get a key’s entry
Section titled “Get a key’s entry”GET /api/v1/sqlite/kv/{key}/entry
Read a key’s value together with its metadata and ETag in one JSON body: the value (parsed when its content type is JSON, otherwise base64), content type, ETag, and created/updated/expiry times. Send If-None-Match with a previously received ETag to get 304 Not Modified while the key is unchanged (weak comparison; any write, TTL change included, changes the ETag).
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
key | path | string | Yes | Key name (supports / for hierarchical keys) |
db | query | string | Yes | Database file path |
table | query | string | No | Custom table name (default: kv_store) |
If-None-Match | header | string | No | Answers 304 while the key’s current ETag matches. |
timeout | query | integer | No | Server-side deadline in seconds, clamped to [1, 300]. Defaults to 30. |
Response
Section titled “Response”{ "key": "users/alice", "content_type": "application/json", "etag": "\"a1b2c3d4e5f6\"", "created_at": 1700000000, "updated_at": 1700000060, "expire_at": null, "value": { "name": "alice", "role": "admin", "score": 42 }}| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | The key. |
content_type | string | Yes | The content type stored with the value. |
etag | string | Yes | The key’s ETag (also sent as the ETag header). null when the server could not verify the key’s version (database busy); retry for a tag. |
created_at | integer | Yes | When the key was created, Unix seconds. |
updated_at | integer | Yes | When the key was last written, Unix seconds. |
expire_at | integer | Yes | When the key expires, Unix seconds; null when it has no TTL. |
value | any JSON value | No | The value, parsed, when its content type is JSON and it parses; null for a stored JSON null. Absent otherwise. |
value_base64 | string | No | The value as base64, when its content type is not JSON or it does not parse as JSON; an empty string for an empty value. Absent otherwise. |
Not modified: the key’s ETag matches If-None-Match. The response has no body.
{ "code": "INVALID_PARAMETERS", "error": "Invalid request parameters"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_PARAMETERS | Invalid request parameters | A parameter or the request body is missing, malformed or out of range. This is the default code for a 400 that has no more specific one. | Check parameter types and values against the API specification; the error text names the problem |
INVALID_DB_PATH | Invalid database path | The db value is empty, relative, outside /hoody/databases (unless the server allows any absolute path), contains ? or #, escapes via a symlink, or names something that is not a regular file. | Provide an absolute path under /hoody/databases, or a bare name / ./name shorthand (resolved to /hoody/databases/*.db). |
DATABASE_NOT_FOUND | Database file does not exist | The database file does not exist and this request does not create it. | Check the path, create the database first, or pass create_db_if_missing=true where the operation supports it. |
INVALID_SQLITE_HEADER | Not a valid SQLite database | The file exists but is empty or does not start with the SQLite header. | Point db at a SQLite database file. |
PATH_IS_DIRECTORY | Path is a directory | Expected a database file but the path is a directory. Only the KV endpoints accept a directory (directory mode). | Use a .db file path. |
{ "code": "FORBIDDEN", "error": "Forbidden"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
FORBIDDEN | Forbidden | The request is not allowed: a path that escapes the database directory, or a request refused by the Source IP Guard (Hoody Kit programs are reached through their URLs only). | Use a path inside the permitted directory, and reach the program through its URL. |
{ "code": "KEY_NOT_FOUND", "error": "Key not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NOT_FOUND | Not found | The requested item does not exist. Default code for a 404 that has no more specific one. | Check the identifiers in the request. |
KEY_NOT_FOUND | Key not found | The key does not exist (or did not exist at the requested timestamp / operation). | Verify the key name and database/table parameters. |
{ "code": "INTERNAL_ERROR", "error": "Internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INTERNAL_ERROR | Internal server error | An unexpected server-side failure. Default code for a 500 that has no more specific one; details are in the server log. | Retry later; report it if it persists. |
DATABASE_ERROR | Database fault | SQLite failed for a reason that is not the request: I/O error, corruption, disk full, a file that cannot be opened. The message is sanitised; details are in the server log. | Check the server log, disk space and the database file; the database may need repair. |
{ "code": "DATABASE_BUSY", "error": "Database busy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
SERVICE_UNAVAILABLE | Service unavailable | The service could not complete the request right now. Default code for a 503 that has no more specific one. | Retry after a short delay. |
REQUEST_TIMEOUT | Request deadline exceeded | The request did not finish before its deadline (the timeout parameter, default 30 s); nothing was committed. | Retry, or raise timeout (max 300). |
DATABASE_BUSY | Database busy | The database was locked by another writer or in use for longer than the request could wait; nothing was changed. | Retry after a short delay. |
Code examples
Section titled “Code examples”curl -X GET "https://{projectId}-{containerId}-sqlite-1.{server}.containers.hoody.com/api/v1/sqlite/kv/users/alice/entry?db=app.db" \ -H "Authorization: Bearer $HOODY_TOKEN"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 entry = await client.sqlite.kv.getEntry('users/alice', { db: 'app.db' });console.log(entry.data.value, entry.data.etag, entry.data.expire_at);Set a key’s TTL
Section titled “Set a key’s TTL”POST /api/v1/sqlite/kv/{key}/expire
Make an existing key expire ttl seconds from now, replacing any TTL it had. The value, its content type, and updated_at are not changed; the ETag is. Setting the same expiry again is still a write (new ETag, a set event in the change feed). History records the change (rollback and time travel restore the TTL).
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
key | path | string | Yes | Key name (supports / for hierarchical keys) |
db | query | string | Yes | Database file path |
table | query | string | No | Custom table name (default: kv_store) |
ttl | query | integer | Yes | Seconds from now until the key expires; at least 1. |
history | query | boolean | No | Store this write’s value in history; false records only that the write happened, not what it wrote. (default: true) |
If-Match | header | string | No | Applies the write only if the key’s current ETag matches. |
Idempotency-Key | header | string | No | Replays the stored response for a repeat of the same key (24 h). |
create_db_if_missing | query | boolean | No | Create database file if it is missing. The legacy alias auto_create is still accepted and means the same thing. (default: false) |
timeout | query | integer | No | Server-side deadline in seconds, clamped to [1, 300]. Defaults to 30. |
Response
Section titled “Response”{ "key": "users/alice", "etag": "\"a1b2c3d4e5f6\"", "expire_at": 1700003600, "success": true}| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | The key. |
etag | string | Yes | The key’s ETag after the change (also sent as the ETag header). |
expire_at | integer | Yes | When the key now expires, Unix seconds; null when it has no TTL. |
success | boolean | Yes | Always true on a 200. |
| Header | Description |
|---|---|
ETag | The key’s new ETag. |
Idempotent-Replayed | true when this is the stored response of an earlier request with the same Idempotency-Key. |
{ "code": "INVALID_PARAMETERS", "error": "Invalid request parameters"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_PARAMETERS | Invalid request parameters | A parameter or the request body is missing, malformed or out of range. This is the default code for a 400 that has no more specific one. | Check parameter types and values against the API specification; the error text names the problem |
INVALID_DB_PATH | Invalid database path | The db value is empty, relative, outside /hoody/databases (unless the server allows any absolute path), contains ? or #, escapes via a symlink, or names something that is not a regular file. | Provide an absolute path under /hoody/databases, or a bare name / ./name shorthand (resolved to /hoody/databases/*.db). |
DATABASE_NOT_FOUND | Database file does not exist | The database file does not exist and this request does not create it. | Check the path, create the database first, or pass create_db_if_missing=true where the operation supports it. |
INVALID_SQLITE_HEADER | Not a valid SQLite database | The file exists but is empty or does not start with the SQLite header. | Point db at a SQLite database file. |
PATH_IS_DIRECTORY | Path is a directory | Expected a database file but the path is a directory. Only the KV endpoints accept a directory (directory mode). | Use a .db file path. |
{ "code": "FORBIDDEN", "error": "Forbidden"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
FORBIDDEN | Forbidden | The request is not allowed: a path that escapes the database directory, or a request refused by the Source IP Guard (Hoody Kit programs are reached through their URLs only). | Use a path inside the permitted directory, and reach the program through its URL. |
{ "code": "KEY_NOT_FOUND", "error": "Key not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NOT_FOUND | Not found | The requested item does not exist. Default code for a 404 that has no more specific one. | Check the identifiers in the request. |
KEY_NOT_FOUND | Key not found | The key does not exist (or did not exist at the requested timestamp / operation). | Verify the key name and database/table parameters. |
Change capture is not available for this table (a trigger that names the table, a self-referencing foreign key with CASCADE/SET NULL/SET DEFAULT actions, a UNIQUE index made only of expressions, or a rowid table whose columns hide rowid/_rowid_/oid). Nothing was written.
{ "code": "CONFLICT", "error": "Conflict"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
CONFLICT | Conflict | The request conflicts with the current state. Default code for a 409 that has no more specific one. | Read the current state and retry with a request that matches it. |
{ "code": "PRECONDITION_FAILED", "error": "Precondition failed"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
PRECONDITION_FAILED | Precondition failed | The If-Match value did not match the key’s current ETag; current_etag holds the key’s ETag, null when it does not exist. Nothing was written. | Read the current value and ETag and retry with them. |
{ "code": "PAYLOAD_TOO_LARGE", "error": "Request too large"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Request too large | The request exceeds a size or item-count limit. | Split the request. |
IDEMPOTENT_RESPONSE_TOO_LARGE | Response too large to store | With Idempotency-Key, the response would be larger than 1 MiB and could not be stored for replay; nothing was written. | Send the request without Idempotency-Key, or narrow it. |
{ "code": "IDEMPOTENCY_KEY_MISMATCH", "error": "Idempotency-Key reused"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
IDEMPOTENCY_KEY_MISMATCH | Idempotency-Key reused | The Idempotency-Key was already used, within its lifetime, for a different request (method, key, query, conditions, content type or body differ); nothing was written. | Use a new Idempotency-Key for a different request. |
{ "code": "INTERNAL_ERROR", "error": "Internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INTERNAL_ERROR | Internal server error | An unexpected server-side failure. Default code for a 500 that has no more specific one; details are in the server log. | Retry later; report it if it persists. |
DATABASE_ERROR | Database fault | SQLite failed for a reason that is not the request: I/O error, corruption, disk full, a file that cannot be opened. The message is sanitised; details are in the server log. | Check the server log, disk space and the database file; the database may need repair. |
{ "code": "DATABASE_BUSY", "error": "Database busy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
SERVICE_UNAVAILABLE | Service unavailable | The service could not complete the request right now. Default code for a 503 that has no more specific one. | Retry after a short delay. |
REQUEST_TIMEOUT | Request deadline exceeded | The request did not finish before its deadline (the timeout parameter, default 30 s); nothing was committed. | Retry, or raise timeout (max 300). |
DATABASE_BUSY | Database busy | The database was locked by another writer or in use for longer than the request could wait; nothing was changed. | Retry after a short delay. |
Code examples
Section titled “Code examples”curl -X POST "https://{projectId}-{containerId}-sqlite-1.{server}.containers.hoody.com/api/v1/sqlite/kv/users/alice/expire?db=app.db&ttl=3600" \ -H "Authorization: Bearer $HOODY_TOKEN"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 result = await client.sqlite.kv.setTtl('users/alice', { db: 'app.db', ttl: 3600 });console.log(result.data.expire_at);Remove a key’s TTL
Section titled “Remove a key’s TTL”POST /api/v1/sqlite/kv/{key}/persist
Make an existing key permanent: remove its expiry. The value, its content type, and updated_at are not changed; the ETag is. A key without a TTL is answered 200 with nothing changed (same ETag, no history entry, no change-feed event). History records the change (rollback and time travel restore the TTL).
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
key | path | string | Yes | Key name (supports / for hierarchical keys) |
db | query | string | Yes | Database file path |
table | query | string | No | Custom table name (default: kv_store) |
history | query | boolean | No | Store this write’s value in history; false records only that the write happened, not what it wrote. (default: true) |
If-Match | header | string | No | Applies the write only if the key’s current ETag matches. |
Idempotency-Key | header | string | No | Replays the stored response for a repeat of the same key (24 h). |
create_db_if_missing | query | boolean | No | Create database file if it is missing. The legacy alias auto_create is still accepted and means the same thing. (default: false) |
timeout | query | integer | No | Server-side deadline in seconds, clamped to [1, 300]. Defaults to 30. |
Response
Section titled “Response”{ "key": "users/alice", "etag": "\"a1b2c3d4e5f6\"", "expire_at": null, "success": true}| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | The key. |
etag | string | Yes | The key’s ETag after the change (also sent as the ETag header). |
expire_at | integer | Yes | When the key now expires, Unix seconds; null when it has no TTL. |
success | boolean | Yes | Always true on a 200. |
| Header | Description |
|---|---|
ETag | The key’s ETag. |
Idempotent-Replayed | true when this is the stored response of an earlier request with the same Idempotency-Key. |
{ "code": "INVALID_PARAMETERS", "error": "Invalid request parameters"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INVALID_PARAMETERS | Invalid request parameters | A parameter or the request body is missing, malformed or out of range. This is the default code for a 400 that has no more specific one. | Check parameter types and values against the API specification; the error text names the problem |
INVALID_DB_PATH | Invalid database path | The db value is empty, relative, outside /hoody/databases (unless the server allows any absolute path), contains ? or #, escapes via a symlink, or names something that is not a regular file. | Provide an absolute path under /hoody/databases, or a bare name / ./name shorthand (resolved to /hoody/databases/*.db). |
DATABASE_NOT_FOUND | Database file does not exist | The database file does not exist and this request does not create it. | Check the path, create the database first, or pass create_db_if_missing=true where the operation supports it. |
INVALID_SQLITE_HEADER | Not a valid SQLite database | The file exists but is empty or does not start with the SQLite header. | Point db at a SQLite database file. |
PATH_IS_DIRECTORY | Path is a directory | Expected a database file but the path is a directory. Only the KV endpoints accept a directory (directory mode). | Use a .db file path. |
{ "code": "FORBIDDEN", "error": "Forbidden"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
FORBIDDEN | Forbidden | The request is not allowed: a path that escapes the database directory, or a request refused by the Source IP Guard (Hoody Kit programs are reached through their URLs only). | Use a path inside the permitted directory, and reach the program through its URL. |
{ "code": "KEY_NOT_FOUND", "error": "Key not found"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
NOT_FOUND | Not found | The requested item does not exist. Default code for a 404 that has no more specific one. | Check the identifiers in the request. |
KEY_NOT_FOUND | Key not found | The key does not exist (or did not exist at the requested timestamp / operation). | Verify the key name and database/table parameters. |
Change capture is not available for this table (a trigger that names the table, a self-referencing foreign key with CASCADE/SET NULL/SET DEFAULT actions, a UNIQUE index made only of expressions, or a rowid table whose columns hide rowid/_rowid_/oid). Nothing was written.
{ "code": "CONFLICT", "error": "Conflict"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
CONFLICT | Conflict | The request conflicts with the current state. Default code for a 409 that has no more specific one. | Read the current state and retry with a request that matches it. |
{ "code": "PRECONDITION_FAILED", "error": "Precondition failed"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
PRECONDITION_FAILED | Precondition failed | The If-Match value did not match the key’s current ETag; current_etag holds the key’s ETag, null when it does not exist. Nothing was written. | Read the current value and ETag and retry with them. |
{ "code": "PAYLOAD_TOO_LARGE", "error": "Request too large"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
PAYLOAD_TOO_LARGE | Request too large | The request exceeds a size or item-count limit. | Split the request. |
IDEMPOTENT_RESPONSE_TOO_LARGE | Response too large to store | With Idempotency-Key, the response would be larger than 1 MiB and could not be stored for replay; nothing was written. | Send the request without Idempotency-Key, or narrow it. |
{ "code": "IDEMPOTENCY_KEY_MISMATCH", "error": "Idempotency-Key reused"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
IDEMPOTENCY_KEY_MISMATCH | Idempotency-Key reused | The Idempotency-Key was already used, within its lifetime, for a different request (method, key, query, conditions, content type or body differ); nothing was written. | Use a new Idempotency-Key for a different request. |
{ "code": "INTERNAL_ERROR", "error": "Internal server error"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
INTERNAL_ERROR | Internal server error | An unexpected server-side failure. Default code for a 500 that has no more specific one; details are in the server log. | Retry later; report it if it persists. |
DATABASE_ERROR | Database fault | SQLite failed for a reason that is not the request: I/O error, corruption, disk full, a file that cannot be opened. The message is sanitised; details are in the server log. | Check the server log, disk space and the database file; the database may need repair. |
{ "code": "DATABASE_BUSY", "error": "Database busy"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
SERVICE_UNAVAILABLE | Service unavailable | The service could not complete the request right now. Default code for a 503 that has no more specific one. | Retry after a short delay. |
REQUEST_TIMEOUT | Request deadline exceeded | The request did not finish before its deadline (the timeout parameter, default 30 s); nothing was committed. | Retry, or raise timeout (max 300). |
DATABASE_BUSY | Database busy | The database was locked by another writer or in use for longer than the request could wait; nothing was changed. | Retry after a short delay. |
Code examples
Section titled “Code examples”curl -X POST "https://{projectId}-{containerId}-sqlite-1.{server}.containers.hoody.com/api/v1/sqlite/kv/users/alice/persist?db=app.db" \ -H "Authorization: Bearer $HOODY_TOKEN"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 result = await client.sqlite.kv.clearTtl('users/alice', { db: 'app.db' });console.log(result.data.expire_at); // null