Repository navigation
API and Commands
The frontend calls one of two backends depending on runtime (see Architecture). Both expose the same operations over the same Snippet model.
type Snippet = {
id: number;
uuid: string; // stable cross-machine id for sync (added in v2.2.0)
title: string;
description: string;
code: string;
language: string;
tags: string[];
favorite: boolean; // pinned to the top of the list (added in v1.5.0)
model: string; // model/target metadata, "" if unset (added in v2.0.0)
kind: "prompt" | "code"; // entry kind, "prompt" default (added in v2.3.0)
copy_count: number; // times copied (added in v2.0.0)
last_used_at: string | null; // last copied, UTC (added in v2.0.0)
created_at: string; // "YYYY-MM-DD HH:MM:SS" (UTC)
updated_at: string;
};Create/update inputs:
type CreateSnippetInput = {
title: string;
description?: string;
code: string;
language: string;
tags?: string[];
model?: string; // added in v2.0.0
kind?: "prompt" | "code"; // added in v2.3.0; defaults to "prompt"
};
// UpdateSnippetInput has the same shape.copy_count and last_used_at are managed by the backend (via the copy endpoint/command below), not set through create/update. kind is normalized to "prompt" for any value other than "code".
(added in v2.7.0) A captured past version of a prompt, returned by the revisions endpoint/command. saved_at is the updated_at the version carried while it was live.
type SnippetRevision = {
id: number;
title: string;
description: string;
code: string;
language: string;
tags: string[];
model: string;
kind: "prompt" | "code";
saved_at: string; // "YYYY-MM-DD HH:MM:SS" (UTC)
};Revisions are stored in a separate snippet_revisions table (see Data Storage), keyed by the snippet's uuid. They are local to each database and never synced, so the newest-wins sync model is unchanged. The newest ~50 per prompt are kept.
-
title,code,languageare required. -
title≤ 255 characters. -
languagemust be one of the recognized language values (below). -
tagsare lowercased, trimmed, emptied-filtered, and capped at 20.
Base path: /api/snippets
| Method | Path | Body | Returns |
|---|---|---|---|
GET |
/api/snippets |
— | Snippet[] |
POST |
/api/snippets |
CreateSnippetInput |
201 + Snippet
|
PUT |
/api/snippets/:id |
UpdateSnippetInput |
Snippet (or 404) |
PATCH |
/api/snippets/:id |
{ favorite: boolean } |
Snippet (or 404) |
POST |
/api/snippets/:id/copy |
— |
Snippet (or 404) |
GET |
/api/snippets/:id/revisions |
— |
SnippetRevision[] (v2.7.0)
|
POST |
/api/snippets/restore |
a full Snippet
|
201 + Snippet
|
POST |
/api/snippets/purge |
— |
{ purged } (v2.11.0)
|
POST |
/api/tags |
{ from, to } |
{ changed } (v2.15.0)
|
DELETE |
/api/snippets/:id |
— |
{ success: true } (or 404) |
-
PATCHis a partial update used to pin/unpin a snippet (added in v1.5.0). Since v2.2.0 it also bumpsupdated_at, so a pin change wins during sync. -
POST /:id/copyrecords a copy — it bumpscopy_countand stampslast_used_at(added in v2.0.0). -
GET /:id/revisions(v2.7.0) returns the prompt's past versions (newest first) for the History panel. SeeSnippetRevisionbelow. History is local to each database — not synced. -
POST /restorerestores a snippet for undo-after-delete (added in v2.0.0). Since v2.2.0, when the row still exists (soft-deleted) it clears the tombstone byuuid— keeping the same id/uuid — and only re-inserts if the row is genuinely gone. -
POST /purge(v2.11.0) empties the Trash: it blanks each tombstone's content (title/description/code/tags/model) but keeps the row as adeletedtombstone and bumpsupdated_at, so an emptied entry can't be resurrected on the next sync — and the emptied state propagates. Returns how many were purged. -
POST /api/tags(v2.15.0) rewrites a tag library-wide:{ from, to }renamesfrom→to(merging iftoalready exists), and{ from, to: null }deletes it. Only changed rows are rewritten, each withupdated_atbumped so the change syncs. Returns{ changed }(rows affected); a missingfromis a400.from/toare trimmed + lowercased. -
DELETEis a soft delete since v2.2.0: it setsdeleted = 1and bumpsupdated_atrather than removing the row, so the deletion propagates on the next sync. The row stays in the database, hidden from every read.
| Param | Values | Meaning |
|---|---|---|
search |
any text | Substring match (case-insensitive) |
searchMode |
all (default), title, tags
|
Which fields search applies to |
language |
a language value | Filter to one language |
tag |
a tag | Filter to snippets containing that tag |
sort |
recent (default), most-used, recently-used, alpha
|
Ordering key (added in v2.5.0) |
deleted |
1 |
Trash: return only soft-deleted rows, newest-deleted first — ignores the other filters (added in v2.4.0) |
Results are ordered pinned first, then by the sort key (v2.5.0) — recent (created_at DESC, the default), most-used (copy_count DESC), recently-used (last_used_at DESC NULLS LAST), or alpha (title COLLATE NOCASE). favorite DESC always leads. The sort value maps to a fixed ORDER BY fragment from an allow-list on both backends (never interpolated), and an unknown value safely falls back to recent. The deleted=1 view is ordered newest-deleted first (updated_at DESC).
Examples
# All snippets
curl http://localhost:3000/api/snippets
# Search titles/descriptions for "auth"
curl "http://localhost:3000/api/snippets?search=auth&searchMode=title"
# Filter by language and tag
curl "http://localhost:3000/api/snippets?language=python&tag=cli"
# Create
curl -X POST http://localhost:3000/api/snippets \
-H "Content-Type: application/json" \
-d '{"title":"Hello","code":"print(1)","language":"python","tags":["demo"]}'Error responses use { "error": "…" } with 400 (validation), 404 (not found), or 500.
These back the self-hosted Syncing feature. When SNIPVAULT_TOKEN is set on the server, every /api request must carry Authorization: Bearer <token> — enforced by proxy.ts; requests without it get 401.
| Method | Path | Body | Returns |
|---|---|---|---|
GET |
/api/health |
— |
{ ok: true, count } — the connection test |
POST |
/api/sync |
{ records: SyncRecord[] } |
{ records: SyncRecord[], applied } |
A SyncRecord is a Snippet without id, plus a deleted: boolean flag (keyed by uuid, tombstones included). POST /api/sync merges each incoming record into the server by uuid — newest updated_at wins, tombstones respected — then returns the server's full merged set so the client can apply the same rule locally. One round trip reconciles both directions. See Syncing for the model.
# Connection test
curl -H "Authorization: Bearer $TOKEN" http://192.168.1.50:3000/api/healthInvoked from the frontend via @tauri-apps/api/core's invoke(...). Defined in src-tauri/src/lib.rs.
| Command | Arguments | Returns |
|---|---|---|
get_snippets |
search?, language?, tag?, searchMode?, sort?
|
Snippet[] |
get_deleted |
— |
Snippet[] (soft-deleted rows for Trash, v2.4.0) |
create_snippet |
input: CreateSnippetInput |
Snippet |
update_snippet |
id: number, input: UpdateSnippetInput
|
Snippet | null |
rewrite_tag |
from: string, to: string | null
|
number (rows changed) (v2.15.0)
|
get_revisions |
uuid: string |
SnippetRevision[] (v2.7.0)
|
set_favorite |
id: number, favorite: boolean
|
Snippet | null |
record_copy |
id: number |
Snippet | null |
restore_snippet |
snippet: Snippet |
Snippet |
purge_deleted |
— |
number (purged) (v2.11.0)
|
delete_snippet |
id: number |
boolean |
record_copy and restore_snippet (added in v2.0.0) are the desktop equivalents of the copy and restore REST endpoints above. get_deleted (added in v2.4.0) returns the soft-deleted rows for the Trash view; restoring one reuses restore_snippet, which clears the tombstone in place by uuid. get_snippets gained the sort argument in v2.5.0 (same allow-list keys as the REST sort param). get_revisions / purge_deleted / rewrite_tag mirror the revisions, purge, and tags REST endpoints above.
import { invoke } from "@tauri-apps/api/core";
const snippets = await invoke("get_snippets", {
search: "auth", language: null, tag: null, searchMode: "all", sort: "most-used",
});The command layer delegates to src-tauri/src/db.rs, which runs the equivalent SQL with the same filter semantics as the REST API.
These back the first-run setup and backup features (see User Guide and Data Storage). They have no web equivalent.
| Command | Arguments | Returns | Purpose |
|---|---|---|---|
get_init_status |
— | { initialized: boolean, db_path: string | null } |
Whether a database is configured (drives first-run setup) |
initialize_new_db |
path?: string |
string (path) |
Create a new database (default location if path omitted) |
use_existing_db |
path: string |
string (path) |
Adopt an existing snippets.db
|
get_database_path |
— | string | null |
Current database path |
backup_database |
destination: string |
string (dest) |
Write a consistent copy via SQLite's online backup API |
get_backups_dir |
— | string |
The stable Backups folder path (v2.6.0) |
backup_to_folder |
keep: number |
string (file) |
Snapshot into the Backups folder; prune to the newest keep (v2.6.0)
|
open_backups_dir |
— | — | Reveal the Backups folder in the file manager (v2.6.0) |
restore_from_backup |
path: string |
— | Validate a .db is a SnipVault database, then replace the live DB via the online restore API (v2.6.0)
|
get_backup_settings |
— | { auto_backup, backup_keep } |
Read the backup-on-launch settings (v2.6.0) |
set_backup_settings |
auto_backup: boolean, backup_keep: number
|
— | Save them (v2.6.0) |
The chosen database path is persisted to a config.json in the app data folder and reopened on the next launch. Data commands (get_snippets, etc.) return an error until a database has been initialized.
The v2.6.0 backup family backs the whole-database backup & restore UI: snapshots are written to a stable <app_dir>/backups/ folder with the online backup API (consistent even while running) and rotated to the newest N; restore_from_backup validates the file (opens read-only, checks the snippets table + expected columns) before adopting it, so an unrelated file can't clobber your data. When auto_backup is on, a snapshot is written on launch at most once a day.
These drive Syncing. The snippet CRUD above always runs against the local database; these commands read/write local sync state and store the server config. The frontend's syncNow() (lib/tauri-api.ts) reads all local records, POSTs them to the server's /api/sync over the Tauri HTTP plugin, and applies the response.
| Command | Arguments | Returns | Purpose |
|---|---|---|---|
get_all_for_sync |
— | SyncRecord[] |
Every local row (tombstones included) to push |
apply_sync_records |
records: SyncRecord[] |
number |
Merge the server's records locally (newest wins); returns how many rows changed |
get_remote_config |
— | { url, token } | null |
The saved sync server, if any |
set_remote_config |
url: string, token: string
|
— | Save the sync server (URL normalized) |
clear_remote_config |
— | — | Forget the sync server (local library untouched) |
The server config is stored in the same config.json under a remote key (the token itself lives in the OS credential store since v2.6.0 — see Syncing). Requests to the server go through @tauri-apps/plugin-http (fetch from Rust), which bypasses the webview CSP and server-side CORS.
The "Run in…" launcher (v2.15.0) opens external URLs via @tauri-apps/plugin-opener on the desktop (capability scoped to http/https), so links open in the system browser; on the web the frontend's openExternal() just opens a new tab.
(single-prompt in v1.5.0; whole-library in v2.4.0) Import and export live in the frontend (lib/tauri-api.ts + the dashboard) and reuse existing backends — no new endpoints or commands beyond those above:
-
Single-prompt export builds a JSON object (
title,description,code,language,tags,model,kind) and downloads it as a Blob — identical in the browser and the Tauri webview, so no filesystem plugin is needed. Import of a single object or array calls the normal create path (POST /api/snippets/create_snippet) once per valid entry, so imported prompts pass the same validation as any new prompt. (kindwas added in v2.3.0; files exported by older versions import as"prompt".) -
Whole-library export (v2.4.0) —
exportLibrary()maps the library to theSyncRecordshape and downloads it as onesnipvault-library-<date>.json. -
Whole-library import (v2.4.0) —
importLibrary()routes the file's records through the sync merge (apply_sync_recordson desktop,POST /api/syncon web), so entries merge byuuid— newestupdated_atwins — instead of duplicating. The dashboard auto-detects a library file (records carrying auuid+ timestamps) versus single/array content and picks the right path.
35 languages are defined in lib/languages.ts as { value, label } pairs (e.g. text → Plain Text, typescript → TypeScript, python → Python). The value is what's stored in the language column and validated by both backends; it also selects the highlight.js grammar. To add a language, add an entry there.
Using SnipVault
Development
Operations