@openparachute/vault 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.parachute/module.json +15 -0
- package/README.md +9 -5
- package/core/src/core.test.ts +2252 -7
- package/core/src/links.ts +1 -1
- package/core/src/mcp.ts +801 -67
- package/core/src/note-schemas.ts +232 -0
- package/core/src/notes.ts +313 -35
- package/core/src/obsidian.ts +3 -3
- package/core/src/paths.ts +1 -1
- package/core/src/query-operators.ts +23 -7
- package/core/src/schema-defaults.ts +287 -0
- package/core/src/schema.ts +393 -9
- package/core/src/store.ts +248 -6
- package/core/src/tag-hierarchy.ts +137 -0
- package/core/src/tag-schemas.ts +242 -42
- package/core/src/types.ts +100 -6
- package/core/src/wikilinks.ts +3 -3
- package/package.json +13 -3
- package/src/admin-spa.test.ts +161 -0
- package/src/admin-spa.ts +161 -0
- package/src/auth-hub-jwt.test.ts +231 -0
- package/src/auth-status.ts +84 -0
- package/src/auth.test.ts +135 -23
- package/src/auth.ts +144 -15
- package/src/backup.ts +4 -7
- package/src/cli.ts +384 -78
- package/src/config.test.ts +44 -0
- package/src/config.ts +68 -40
- package/src/hub-jwt.test.ts +296 -0
- package/src/hub-jwt.ts +79 -0
- package/src/init-summary.test.ts +133 -0
- package/src/init-summary.ts +90 -0
- package/src/init.test.ts +216 -0
- package/src/mcp-http.ts +30 -28
- package/src/mcp-install.ts +1 -1
- package/src/mcp-tools.ts +294 -6
- package/src/module-config.ts +1 -1
- package/src/oauth.test.ts +345 -0
- package/src/oauth.ts +85 -14
- package/src/owner-auth.ts +57 -1
- package/src/prompt.ts +31 -14
- package/src/routes.ts +686 -58
- package/src/routing.test.ts +466 -1
- package/src/routing.ts +108 -24
- package/src/scopes.test.ts +66 -8
- package/src/scopes.ts +163 -37
- package/src/server.ts +24 -2
- package/src/services-manifest.test.ts +20 -0
- package/src/services-manifest.ts +9 -2
- package/src/stop-signal.test.ts +85 -0
- package/src/storage.test.ts +92 -0
- package/src/tag-scope.ts +118 -0
- package/src/token-store.test.ts +47 -0
- package/src/token-store.ts +128 -13
- package/src/tokens-routes.test.ts +720 -0
- package/src/tokens-routes.ts +392 -0
- package/src/transcription-worker.test.ts +5 -0
- package/src/triggers.ts +1 -1
- package/src/two-factor.ts +2 -2
- package/src/vault-create.test.ts +193 -0
- package/src/vault-name.test.ts +123 -0
- package/src/vault-name.ts +80 -0
- package/src/vault.test.ts +868 -3
- package/tsconfig.json +8 -1
- package/.claude/settings.local.json +0 -8
- package/.dockerignore +0 -8
- package/.env.example +0 -9
- package/CHANGELOG.md +0 -175
- package/CLAUDE.md +0 -125
- package/Caddyfile +0 -3
- package/Dockerfile +0 -22
- package/bun.lock +0 -219
- package/bunfig.toml +0 -2
- package/deploy/parachute-vault.service +0 -20
- package/docker-compose.yml +0 -50
- package/docs/HTTP_API.md +0 -434
- package/docs/auth-model.md +0 -340
- package/fly.toml +0 -24
- package/package/package.json +0 -32
- package/railway.json +0 -14
- package/scripts/migrate-audio-to-opus.test.ts +0 -237
- package/scripts/migrate-audio-to-opus.ts +0 -499
package/docs/HTTP_API.md
DELETED
|
@@ -1,434 +0,0 @@
|
|
|
1
|
-
# Parachute Vault HTTP API
|
|
2
|
-
|
|
3
|
-
A flat reference for the Parachute Vault HTTP surface. Intended for humans *and*
|
|
4
|
-
agents building tools that read or write a vault over HTTP.
|
|
5
|
-
|
|
6
|
-
All endpoints serve JSON. The same vault is reachable at two roots:
|
|
7
|
-
|
|
8
|
-
- `/api/...` — the server's default vault
|
|
9
|
-
- `/vaults/{name}/api/...` — any named vault on this server
|
|
10
|
-
|
|
11
|
-
Use whichever is convenient. Examples below use the default `/api` root.
|
|
12
|
-
|
|
13
|
-
## Quick start — render a graph in 5 lines
|
|
14
|
-
|
|
15
|
-
```js
|
|
16
|
-
const res = await fetch("http://localhost:1940/api/graph", {
|
|
17
|
-
headers: { Authorization: `Bearer ${apiKey}` },
|
|
18
|
-
});
|
|
19
|
-
const { notes, links, tags, meta } = await res.json();
|
|
20
|
-
// notes: lightweight NoteIndex[] — id, path, tags, createdAt, byteSize, preview
|
|
21
|
-
// links: Link[] — sourceId, targetId, relationship, metadata
|
|
22
|
-
// Hand this to d3-force, cytoscape, sigma.js, etc.
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
That's the whole happy path. Everything else in this doc is detail.
|
|
26
|
-
|
|
27
|
-
## Conventions
|
|
28
|
-
|
|
29
|
-
- **Response payloads are camelCase**: `createdAt`, `sourceId`, `mimeType`,
|
|
30
|
-
`totalNotes`.
|
|
31
|
-
- **Request payloads are camelCase**: you `POST {sourceId, targetId, ...}` and
|
|
32
|
-
get the same shape back.
|
|
33
|
-
- **Query params are snake_case**: `?include_content=true`, `?tag_match=any`,
|
|
34
|
-
`?date_from=2025-01-01`. This matches the MCP tool-arg convention, so one
|
|
35
|
-
concept ports cleanly between HTTP and MCP.
|
|
36
|
-
- **Timestamps are ISO-8601** UTC strings (e.g. `2026-04-07T15:30:00.000Z`).
|
|
37
|
-
- **No envelope**. Responses are the data itself (`{...}` or `[...]`), not
|
|
38
|
-
wrapped in `{data: ...}`. Error responses are `{error: "...", message?: "..."}`.
|
|
39
|
-
|
|
40
|
-
## Authentication
|
|
41
|
-
|
|
42
|
-
Pass your API key as either:
|
|
43
|
-
|
|
44
|
-
```
|
|
45
|
-
Authorization: Bearer <key>
|
|
46
|
-
X-API-Key: <key>
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
Every request is authenticated — localhost and remote traffic go through the
|
|
50
|
-
same path, there is no bypass. Local dev feels friction-free because you can
|
|
51
|
-
hand the CLI-generated API key to your script without exposing it to the
|
|
52
|
-
network, not because the auth check is skipped.
|
|
53
|
-
|
|
54
|
-
Keys have a **scope**:
|
|
55
|
-
|
|
56
|
-
- `write` — full access
|
|
57
|
-
- `read` — `GET`/`HEAD`/`OPTIONS` only; writes return `403 Forbidden`
|
|
58
|
-
|
|
59
|
-
A read-only key is the right thing to hand to a visualizer or static-site
|
|
60
|
-
generator.
|
|
61
|
-
|
|
62
|
-
## The shapes
|
|
63
|
-
|
|
64
|
-
### `Note`
|
|
65
|
-
|
|
66
|
-
```ts
|
|
67
|
-
{
|
|
68
|
-
id: string;
|
|
69
|
-
content: string;
|
|
70
|
-
path?: string;
|
|
71
|
-
metadata?: Record<string, unknown>;
|
|
72
|
-
createdAt: string;
|
|
73
|
-
updatedAt?: string;
|
|
74
|
-
tags?: string[];
|
|
75
|
-
}
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
### `NoteIndex` (lean shape)
|
|
79
|
-
|
|
80
|
-
Returned by list endpoints by default. Same as `Note` minus `content`, plus
|
|
81
|
-
`byteSize` and a one-line `preview` (120 code points, whitespace collapsed).
|
|
82
|
-
|
|
83
|
-
```ts
|
|
84
|
-
{
|
|
85
|
-
id: string;
|
|
86
|
-
path?: string;
|
|
87
|
-
createdAt: string;
|
|
88
|
-
updatedAt?: string;
|
|
89
|
-
tags?: string[];
|
|
90
|
-
metadata?: Record<string, unknown>;
|
|
91
|
-
byteSize: number; // UTF-8 bytes of the full content
|
|
92
|
-
preview: string; // first ~120 chars, single line
|
|
93
|
-
}
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
### `Link`
|
|
97
|
-
|
|
98
|
-
```ts
|
|
99
|
-
{
|
|
100
|
-
sourceId: string;
|
|
101
|
-
targetId: string;
|
|
102
|
-
relationship: string;
|
|
103
|
-
metadata?: Record<string, unknown>;
|
|
104
|
-
createdAt: string;
|
|
105
|
-
}
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### `VaultStats`
|
|
109
|
-
|
|
110
|
-
```ts
|
|
111
|
-
{
|
|
112
|
-
totalNotes: number;
|
|
113
|
-
earliestNote: { id: string; createdAt: string } | null;
|
|
114
|
-
latestNote: { id: string; createdAt: string } | null;
|
|
115
|
-
notesByMonth: { month: string; count: number }[]; // e.g. "2026-04"
|
|
116
|
-
topTags: { tag: string; count: number }[];
|
|
117
|
-
tagCount: number;
|
|
118
|
-
}
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
## Defaults: lean lists, fat point reads
|
|
122
|
-
|
|
123
|
-
- **List endpoints** (`GET /notes`, `GET /graph`) default to `NoteIndex`. The
|
|
124
|
-
common case is viz/listing, which doesn't need the full body of every note.
|
|
125
|
-
- **Point reads** (`GET /notes/:id`) default to the full `Note`. If you asked
|
|
126
|
-
for one specific thing by ID, you probably want its content.
|
|
127
|
-
|
|
128
|
-
Both shapes can be forced either way with `?include_content=true|false`.
|
|
129
|
-
|
|
130
|
-
## Endpoints
|
|
131
|
-
|
|
132
|
-
### Server-level
|
|
133
|
-
|
|
134
|
-
#### `GET /health`
|
|
135
|
-
Returns `{status: "ok", vaults: string[]}`. No auth required.
|
|
136
|
-
|
|
137
|
-
#### `GET /vaults`
|
|
138
|
-
List all vaults on the server.
|
|
139
|
-
```json
|
|
140
|
-
{
|
|
141
|
-
"vaults": [
|
|
142
|
-
{ "name": "default", "description": "...", "created_at": "..." }
|
|
143
|
-
]
|
|
144
|
-
}
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
#### `GET /vaults/{name}`
|
|
148
|
-
Single-vault landing payload — name, description, createdAt, and stats in one
|
|
149
|
-
round trip. Useful for a viz site's home page.
|
|
150
|
-
```json
|
|
151
|
-
{
|
|
152
|
-
"name": "default",
|
|
153
|
-
"description": "My knowledge graph",
|
|
154
|
-
"createdAt": "2026-01-01T00:00:00.000Z",
|
|
155
|
-
"stats": { "totalNotes": 617, "topTags": [...], "notesByMonth": [...], ... }
|
|
156
|
-
}
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
### Notes
|
|
160
|
-
|
|
161
|
-
#### `GET /notes`
|
|
162
|
-
Query notes. Returns `NoteIndex[]` by default.
|
|
163
|
-
|
|
164
|
-
Query params:
|
|
165
|
-
- `include_content=true` — return full `Note[]` instead.
|
|
166
|
-
- `ids=a,b,c` — fetch specific notes by ID. Practical limit ~50 IDs due to
|
|
167
|
-
URL length; for larger batches call multiple times.
|
|
168
|
-
- `tag=foo&tag=bar` — filter by tags (repeat param to pass multiple).
|
|
169
|
-
- `tag_match=all|any` — default `all`.
|
|
170
|
-
- `exclude_tag=foo` — exclude notes with this tag.
|
|
171
|
-
- `date_from=ISO` — inclusive lower bound on `createdAt`.
|
|
172
|
-
- `date_to=ISO` — exclusive upper bound.
|
|
173
|
-
- `sort=asc|desc` — by `createdAt`. Default `asc`.
|
|
174
|
-
- `limit=N` — default 100.
|
|
175
|
-
- `offset=N` — default 0.
|
|
176
|
-
|
|
177
|
-
#### `POST /notes`
|
|
178
|
-
Create a note. Body:
|
|
179
|
-
```json
|
|
180
|
-
{
|
|
181
|
-
"content": "...", // required
|
|
182
|
-
"id": "optional-client-id",
|
|
183
|
-
"path": "Projects/Foo",
|
|
184
|
-
"tags": ["a", "b"],
|
|
185
|
-
"metadata": { "status": "draft" },
|
|
186
|
-
"createdAt": "2026-04-07T..."
|
|
187
|
-
}
|
|
188
|
-
```
|
|
189
|
-
Returns the created `Note`, `201 Created`.
|
|
190
|
-
|
|
191
|
-
#### `GET /notes/{id}`
|
|
192
|
-
Returns the full `Note`. `?include_content=false` returns a `NoteIndex`.
|
|
193
|
-
|
|
194
|
-
#### `PATCH /notes/{id}`
|
|
195
|
-
Update content, path, or metadata. Body:
|
|
196
|
-
```json
|
|
197
|
-
{ "content": "new body", "path": "new/path", "metadata": {...} }
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
#### `DELETE /notes/{id}`
|
|
201
|
-
Returns `{deleted: true}`.
|
|
202
|
-
|
|
203
|
-
#### `POST /notes/{id}/tags`, `DELETE /notes/{id}/tags`
|
|
204
|
-
Body: `{"tags": ["a", "b"]}`.
|
|
205
|
-
|
|
206
|
-
#### `POST /notes/{id}/attachments`
|
|
207
|
-
Body: `{"path": "files/a.png", "mimeType": "image/png", "transcribe"?: boolean}`.
|
|
208
|
-
|
|
209
|
-
When `transcribe: true` and the file is audio, the server queues a
|
|
210
|
-
transcription job: `attachment.metadata.transcribe_status = "pending"` is
|
|
211
|
-
set, and `note.metadata.transcribe_stub = true` is written as the opt-in to
|
|
212
|
-
overwrite content when the transcript lands. A background worker (enabled
|
|
213
|
-
by setting `SCRIBE_URL` on the server) drains the queue FIFO, one at a
|
|
214
|
-
time, calling `${SCRIBE_URL}/v1/audio/transcriptions` with the audio as
|
|
215
|
-
multipart `file` and expecting `{ text: string }` back.
|
|
216
|
-
|
|
217
|
-
On success:
|
|
218
|
-
- If `note.metadata.transcribe_stub === true`, the worker replaces the
|
|
219
|
-
literal `_Transcript pending._` placeholder in the note body with the
|
|
220
|
-
transcript, or the whole body if the placeholder is absent. The stub
|
|
221
|
-
marker is cleared. A user edit clearing `transcribe_stub` before the
|
|
222
|
-
transcript arrives opts out of the overwrite.
|
|
223
|
-
- `attachment.metadata.transcribe_status` becomes `"done"` and
|
|
224
|
-
`transcript` + `transcribe_done_at` are recorded on the attachment even
|
|
225
|
-
when the note opted out, so the transcript is always addressable.
|
|
226
|
-
|
|
227
|
-
On failure, the worker retries with exponential backoff up to three
|
|
228
|
-
attempts before setting `transcribe_status = "failed"` and capturing
|
|
229
|
-
`transcribe_error`.
|
|
230
|
-
|
|
231
|
-
The queue lives in the DB (`attachments` table), so a server restart
|
|
232
|
-
resumes pending work without replay.
|
|
233
|
-
|
|
234
|
-
#### `GET /notes/{id}/attachments`
|
|
235
|
-
Returns `Attachment[]`.
|
|
236
|
-
|
|
237
|
-
#### `DELETE /notes/{id}/attachments/{attId}`
|
|
238
|
-
Returns `204 No Content`. The attachment record is removed and the underlying
|
|
239
|
-
storage file is unlinked when no other attachment still references the same
|
|
240
|
-
path (orphan-check). Returns `404` if the attachment doesn't exist or belongs
|
|
241
|
-
to a different note. Idempotent: a second delete of the same id returns `404`.
|
|
242
|
-
|
|
243
|
-
### Links
|
|
244
|
-
|
|
245
|
-
#### `GET /links`
|
|
246
|
-
List edges. Polymorphic — filters compose freely.
|
|
247
|
-
|
|
248
|
-
Query params:
|
|
249
|
-
- `note_id=abc` — only edges touching this note.
|
|
250
|
-
- `direction=outbound|inbound|both` — only meaningful with `note_id`.
|
|
251
|
-
Default `both`.
|
|
252
|
-
- `relationship=cites` — only edges of this type.
|
|
253
|
-
|
|
254
|
-
Returns bare `Link[]` — no hydration. If you need the connected notes'
|
|
255
|
-
details, pair the result with `GET /notes?ids=...`.
|
|
256
|
-
|
|
257
|
-
Examples:
|
|
258
|
-
```
|
|
259
|
-
GET /links # everything
|
|
260
|
-
GET /links?note_id=abc # all edges touching note abc
|
|
261
|
-
GET /links?note_id=abc&direction=outbound
|
|
262
|
-
GET /links?relationship=cites # vault-wide, by type
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
#### `POST /links`
|
|
266
|
-
Body:
|
|
267
|
-
```json
|
|
268
|
-
{ "sourceId": "a", "targetId": "b", "relationship": "cites", "metadata": {...} }
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
#### `DELETE /links`
|
|
272
|
-
Body:
|
|
273
|
-
```json
|
|
274
|
-
{ "sourceId": "a", "targetId": "b", "relationship": "cites" }
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
### Graph
|
|
278
|
-
|
|
279
|
-
#### `GET /graph`
|
|
280
|
-
One-shot knowledge graph payload for visualization.
|
|
281
|
-
|
|
282
|
-
```json
|
|
283
|
-
{
|
|
284
|
-
"notes": [ /* NoteIndex[] by default, Note[] if include_content=true */ ],
|
|
285
|
-
"links": [ /* Link[] */ ],
|
|
286
|
-
"tags": [ { "name": "...", "count": 12 } ],
|
|
287
|
-
"meta": {
|
|
288
|
-
"totalNotes": 617,
|
|
289
|
-
"totalLinks": 1234,
|
|
290
|
-
"filteredNotes": 617,
|
|
291
|
-
"filteredLinks": 1234,
|
|
292
|
-
"includeContent": false
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
Query params:
|
|
298
|
-
- `include_content=true` — fatten each note to include full content.
|
|
299
|
-
- `tag=foo&tag=bar` — filter to a subgraph (only notes with these tags, and
|
|
300
|
-
only links where **both** endpoints are in the subset).
|
|
301
|
-
- `tag_match=all|any` — default `all`.
|
|
302
|
-
- `exclude_tag=foo`.
|
|
303
|
-
|
|
304
|
-
`meta.totalNotes` / `meta.totalLinks` always reflect the full vault;
|
|
305
|
-
`filteredNotes` / `filteredLinks` reflect the response.
|
|
306
|
-
|
|
307
|
-
### Search
|
|
308
|
-
|
|
309
|
-
#### `GET /search?q=query`
|
|
310
|
-
Full-text search. Returns `Note[]` (full shape).
|
|
311
|
-
|
|
312
|
-
Query params:
|
|
313
|
-
- `q=...` — required.
|
|
314
|
-
- `tag=foo` — optional tag filter (repeatable).
|
|
315
|
-
- `limit=N` — default 50.
|
|
316
|
-
|
|
317
|
-
### Tags
|
|
318
|
-
|
|
319
|
-
#### `GET /tags`
|
|
320
|
-
Returns `[{name, count}]`.
|
|
321
|
-
|
|
322
|
-
#### `POST /tags/{name}/rename`
|
|
323
|
-
Body: `{ "new_name": string }`. Atomically renames the tag across `tags`,
|
|
324
|
-
`note_tags`, and `tag_schemas` in a single transaction.
|
|
325
|
-
|
|
326
|
-
Returns `{ "renamed": number }` on success — the number of note-tag rows
|
|
327
|
-
rewritten.
|
|
328
|
-
|
|
329
|
-
Errors:
|
|
330
|
-
- `404 { "error": "not_found" }` — source tag does not exist.
|
|
331
|
-
- `409 { "error": "target_exists", "target": string, "message": "..." }` —
|
|
332
|
-
`new_name` is already a tag. The client should call `POST /tags/merge`
|
|
333
|
-
instead if combining the two tags is the intent.
|
|
334
|
-
|
|
335
|
-
#### `POST /tags/merge`
|
|
336
|
-
Body: `{ "sources": string[], "target": string }`. Retags every note carrying
|
|
337
|
-
any of the `sources` tags with `target`, then drops the source tags (and
|
|
338
|
-
their schemas) in a single transaction. `target`'s own schema is preserved.
|
|
339
|
-
|
|
340
|
-
`target` is created if it doesn't exist yet. Sources that don't exist are
|
|
341
|
-
recorded with count `0`. Duplicate sources are deduped; `target` appearing
|
|
342
|
-
in `sources` is a no-op for that entry.
|
|
343
|
-
|
|
344
|
-
Returns `{ "merged": { [source]: count }, "target": string }`.
|
|
345
|
-
|
|
346
|
-
### Vault stats
|
|
347
|
-
|
|
348
|
-
You usually want `GET /vaults/{name}` which bundles stats with vault metadata.
|
|
349
|
-
If you only need the stats, call `GET /vaults/{name}` and read `.stats`.
|
|
350
|
-
|
|
351
|
-
### Vault config
|
|
352
|
-
|
|
353
|
-
#### `GET /api/vault`
|
|
354
|
-
Returns the vault's identity plus a nested `config` block for mutable
|
|
355
|
-
settings.
|
|
356
|
-
|
|
357
|
-
```json
|
|
358
|
-
{
|
|
359
|
-
"name": "default",
|
|
360
|
-
"description": "My knowledge graph",
|
|
361
|
-
"config": {
|
|
362
|
-
"audio_retention": "keep"
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
`?include_stats=true` folds the same `VaultStats` shape into the response
|
|
368
|
-
under `stats`.
|
|
369
|
-
|
|
370
|
-
#### `PATCH /api/vault`
|
|
371
|
-
Update the description and/or nested `config` fields. Only the fields you
|
|
372
|
-
pass are changed; omitted fields are left alone.
|
|
373
|
-
|
|
374
|
-
```json
|
|
375
|
-
{
|
|
376
|
-
"description": "new description",
|
|
377
|
-
"config": { "audio_retention": "until_transcribed" }
|
|
378
|
-
}
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
Response echoes the full vault payload (same shape as `GET /api/vault`).
|
|
382
|
-
|
|
383
|
-
##### `config.audio_retention`
|
|
384
|
-
|
|
385
|
-
Controls what the transcription worker does with the audio file on disk
|
|
386
|
-
once it reaches a terminal state. The attachment row (including any
|
|
387
|
-
recorded transcript) is always preserved — only the file on disk is
|
|
388
|
-
affected.
|
|
389
|
-
|
|
390
|
-
| Value | Behavior |
|
|
391
|
-
|---|---|
|
|
392
|
-
| `"keep"` (default) | Never unlink. The original audio stays on disk indefinitely. |
|
|
393
|
-
| `"until_transcribed"` | Unlink on successful transcription. On failure the file is kept so you can retry or re-upload. |
|
|
394
|
-
| `"never"` | Unlink on any terminal state — **including failure**. Users who opt in accept that losing a bad transcription also loses the source audio. |
|
|
395
|
-
|
|
396
|
-
Validation: `audio_retention` must be exactly one of those three strings.
|
|
397
|
-
Any other value returns `400 { "error": "invalid_audio_retention" }`.
|
|
398
|
-
Vaults created before this setting existed read back as `"keep"`.
|
|
399
|
-
|
|
400
|
-
### Storage
|
|
401
|
-
|
|
402
|
-
#### `POST /storage/upload`
|
|
403
|
-
Multipart form:
|
|
404
|
-
- `file` — required, audio/image, ≤100MB
|
|
405
|
-
|
|
406
|
-
Returns `{path, size, mimeType}`.
|
|
407
|
-
|
|
408
|
-
#### `GET /storage/{date}/{filename}`
|
|
409
|
-
Serves the uploaded file.
|
|
410
|
-
|
|
411
|
-
## CORS
|
|
412
|
-
|
|
413
|
-
The server sends permissive CORS headers (`Access-Control-Allow-Origin: *`)
|
|
414
|
-
so a static site on any origin can fetch the API. Writes still require a
|
|
415
|
-
valid API key.
|
|
416
|
-
|
|
417
|
-
## Pairing with MCP
|
|
418
|
-
|
|
419
|
-
Every read endpoint here has a matching MCP tool over `/mcp`
|
|
420
|
-
(unified) or `/vaults/{name}/mcp` (scoped):
|
|
421
|
-
|
|
422
|
-
| HTTP | MCP tool |
|
|
423
|
-
|---------------------------|---------------------|
|
|
424
|
-
| `GET /notes` | `read-notes` |
|
|
425
|
-
| `GET /notes?ids=...` | `get-note` (ids) |
|
|
426
|
-
| `GET /notes/{id}` | `get-note` |
|
|
427
|
-
| `GET /links` | `get-links` |
|
|
428
|
-
| `GET /graph` | `get-graph` |
|
|
429
|
-
| `GET /vaults/{name}` | `get-vault-stats` + `get-vault-description` |
|
|
430
|
-
| `GET /tags` | `list-tags` |
|
|
431
|
-
| `GET /search?q=` | `search-notes` |
|
|
432
|
-
|
|
433
|
-
The MCP tools use the same lean-vs-fat convention (`include_content: true|false`)
|
|
434
|
-
and the same snake_case arg names as the HTTP query params.
|