Skip to content

API and Commands

Franciszek Ryszka edited this page Aug 10, 2026 · 8 revisions

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.

The 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".

The SnippetRevision model

(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.

Validation rules (both backends)

  • title, code, language are required.
  • title ≤ 255 characters.
  • language must be one of the recognized language values (below).
  • tags are lowercased, trimmed, emptied-filtered, and capped at 20.

Web: REST API

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)
  • PATCH is a partial update used to pin/unpin a snippet (added in v1.5.0). Since v2.2.0 it also bumps updated_at, so a pin change wins during sync.
  • POST /:id/copy records a copy — it bumps copy_count and stamps last_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. See SnippetRevision below. History is local to each database — not synced.
  • POST /restore restores 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 by uuid — 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 a deleted tombstone and bumps updated_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 } renames from→to (merging if to already exists), and { from, to: null } deletes it. Only changed rows are rewritten, each with updated_at bumped so the change syncs. Returns { changed } (rows affected); a missing from is a 400. from/to are trimmed + lowercased.
  • DELETE is a soft delete since v2.2.0: it sets deleted = 1 and bumps updated_at rather than removing the row, so the deletion propagates on the next sync. The row stays in the database, hidden from every read.

GET query parameters

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.

Sync & health endpoints (added in v2.2.0)

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/health

Desktop: Tauri commands

Invoked 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.

Database management commands (desktop only)

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.

Sync commands (desktop only) (added in v2.2.0)

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.


Import & export

(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. (kind was added in v2.3.0; files exported by older versions import as "prompt".)
  • Whole-library export (v2.4.0) — exportLibrary() maps the library to the SyncRecord shape and downloads it as one snipvault-library-<date>.json.
  • Whole-library import (v2.4.0) — importLibrary() routes the file's records through the sync merge (apply_sync_records on desktop, POST /api/sync on web), so entries merge by uuid — newest updated_at wins — instead of duplicating. The dashboard auto-detects a library file (records carrying a uuid + timestamps) versus single/array content and picks the right path.

Supported languages

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.

Clone this wiki locally