{"openapi":"3.1.0","info":{"title":"vault-http","version":"1","summary":"The Mac's public boundary — files and tables in one vault, behind accounts and staged grants.","description":"The Mac is set up once, at the machine, and then left alone: it takes no code\nfrom outside. This document is the contract between it and everything else.\n\n- **v1 only grows.** Endpoints, actions, optional params and response fields\n  may be added; nothing existing changes meaning or is removed. Clients ignore\n  fields they do not know.\n- **Two doors, one API.** The public door (Tailscale Funnel, 443) and the\n  tailnet door (`tailscale serve`, 8443) serve the same `/v1`. Both are TLS end\n  to end, terminated on the Mac. Admin actions (`login`, `accounts`, `grant`,\n  `deny`, `disable`, `enable`) answer only on the tailnet door.\n- **Everything but `GET /v1`, `register` and `login` needs `Authorization: Bearer <key>`.**\n- **Access is granted in stages**: `browse` (folders and their meta) → `list`\n  (file names and meta, `find`) → `read` (contents); `write` is separate and\n  implies no reading.\n- Discovery: `https://gateway.jeuk.page/` explains this API and names the base URL;\n  it never proxies a call.\n","contact":{"name":"Jake","url":"https://gateway.jeuk.page"}},"servers":[{"url":"https://jk-mac.tail79dafa.ts.net","description":"public door (Tailscale Funnel)"},{"url":"https://jk-mac.tail79dafa.ts.net:8443","description":"tailnet door (tailscale serve); the only one that takes admin actions"}],"security":[{"bearer":[]}],"tags":[{"name":"index"},{"name":"auth"},{"name":"meta"},{"name":"file"},{"name":"table"}],"paths":{"/v1":{"get":{"tags":["index"],"operationId":"index","x-needs":"no key","summary":"What this server is; the only call without a key.","security":[],"responses":{"200":{"description":"index","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Ok"},{"properties":{"result":{"type":"object","required":["api","commit","door","endpoints"],"properties":{"api":{"const":1},"commit":{"type":["string","null"],"description":"the installed commit"},"door":{"enum":["tailnet","public"],"description":"which door this request came through"},"endpoints":{"type":"object","additionalProperties":{"type":"string"}}}}}}]}}}}}}},"/v1/openapi.yaml":{"get":{"tags":["index"],"operationId":"spec","x-needs":"no key","summary":"This document, as installed.","security":[],"responses":{"200":{"description":"the spec","content":{"application/yaml":{}}}}}},"/v1/auth":{"get":{"tags":["auth"],"operationId":"whoami","x-needs":"any key","summary":"The account behind this key, and its grants.","responses":{"200":{"description":"me","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Ok"},{"properties":{"result":{"$ref":"#/components/schemas/Me"}}}]}}}},"default":{"$ref":"#/components/responses/Error"}}},"post":{"tags":["auth"],"operationId":"auth","x-needs":"see the table","summary":"Log in, register, ask for a grant; and, for the admin, decide.","description":"| action | who | door |\n|---|---|---|\n| `login` | the owner, no key; the `Tailscale-User-Login` that `tailscale serve` vouches for must be listed in `admins` | tailnet |\n| `register` | anyone, no key | both |\n| `request` | any key | both |\n| `accounts` · `grant` · `deny` · `disable` · `enable` | admin key | tailnet |\n","security":[{"bearer":[]},{}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/AuthLogin"},{"$ref":"#/components/schemas/AuthRegister"},{"$ref":"#/components/schemas/AuthRequest"},{"$ref":"#/components/schemas/AuthAccounts"},{"$ref":"#/components/schemas/AuthGrant"},{"$ref":"#/components/schemas/AuthDeny"},{"$ref":"#/components/schemas/AuthStatus"}],"discriminator":{"propertyName":"action","mapping":{"login":"#/components/schemas/AuthLogin","register":"#/components/schemas/AuthRegister","request":"#/components/schemas/AuthRequest","accounts":"#/components/schemas/AuthAccounts","grant":"#/components/schemas/AuthGrant","deny":"#/components/schemas/AuthDeny","disable":"#/components/schemas/AuthStatus","enable":"#/components/schemas/AuthStatus"}}}}}},"responses":{"200":{"description":"`login` → `{key, expiresAt}` · `register` → `{id, key, status}` (the key is shown once) ·\n`request` / `grant` → `{grant}` · `accounts` → `{accounts}` · `deny` → `{account, index, removed}` ·\n`disable` / `enable` → `{id, status}`\n","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Ok"},{"properties":{"result":{"oneOf":[{"type":"object","required":["key","expiresAt"],"properties":{"key":{"type":"string","pattern":"^ok_"},"expiresAt":{"type":"integer"}}},{"type":"object","required":["id","key","status"],"properties":{"id":{"type":"string"},"key":{"type":"string","pattern":"^ak_"},"status":{"const":"enabled"}}},{"type":"object","required":["grant"],"properties":{"grant":{"$ref":"#/components/schemas/Grant"}}},{"type":"object","required":["accounts"],"properties":{"accounts":{"type":"array","items":{"$ref":"#/components/schemas/Account"}}}},{"type":"object","required":["account","index","removed"],"properties":{"account":{"type":"string"},"index":{"type":"integer"},"removed":{"const":true}}},{"type":"object","required":["id","status"],"properties":{"id":{"type":"string"},"status":{"enum":["enabled","disabled"]}}}]}}}]}}}},"default":{"$ref":"#/components/responses/Error"}}}},"/v1/meta":{"get":{"tags":["meta"],"operationId":"meta","x-needs":"a meta grant (any level)","summary":"The device — power, disks, the file index, backups. Needs a `meta` grant (any level).","responses":{"200":{"description":"meta","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Ok"},{"properties":{"result":{"$ref":"#/components/schemas/Meta"}}}]}}}},"default":{"$ref":"#/components/responses/Error"}}}},"/v1/file/{path}":{"parameters":[{"name":"path","in":"path","required":true,"description":"vault-relative, `/` allowed; no segment may start with `.`","schema":{"type":"string"},"x-allow-slashes":true}],"get":{"tags":["file"],"operationId":"fileGet","x-needs":"read — or a signed link from `link`","summary":"The bytes of one file. Needs `read`.","description":"A key, or a signed link from the `link` action (`?exp=…&sig=…`) for `<img>` and\n`<video>`, which cannot send headers.\n","security":[{"bearer":[]},{}],"parameters":[{"name":"Range","in":"header","schema":{"type":"string","example":"bytes=0-1023"}},{"name":"exp","in":"query","schema":{"type":"integer"}},{"name":"sig","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"the file","headers":{"ETag":{"$ref":"#/components/headers/ETag"}},"content":{"*/*":{}}},"206":{"description":"a range of it","headers":{"ETag":{"$ref":"#/components/headers/ETag"},"Content-Range":{"schema":{"type":"string"}}},"content":{"*/*":{}}},"default":{"$ref":"#/components/responses/Error"}}},"put":{"tags":["file"],"operationId":"filePut","x-needs":"write","summary":"Write the bytes of one file. Needs `write`.","parameters":[{"name":"If-Match","in":"header","description":"the ETag you read; required to replace","schema":{"type":"string"}},{"name":"If-None-Match","in":"header","description":"`*` to create; required to create","schema":{"const":"*"}}],"requestBody":{"required":true,"content":{"*/*":{"schema":{"type":"string","format":"binary","maxLength":67108864}}}},"responses":{"200":{"description":"replaced","headers":{"ETag":{"$ref":"#/components/headers/ETag"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PutResult"}}}},"201":{"description":"created","headers":{"ETag":{"$ref":"#/components/headers/ETag"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PutResult"}}}},"default":{"$ref":"#/components/responses/Error"}}}},"/v1/file":{"post":{"tags":["file"],"operationId":"file","x-needs":"see the table","summary":"File actions. The level each needs is in the table.","description":"| action | level |\n|---|---|\n| `folders` | browse |\n| `list` · `find` · `stat` · `resolve` | list |\n| `search` · `link` | read |\n| `move` · `mkdir` · `delete` | write |\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/FileFolders"},{"$ref":"#/components/schemas/FileList"},{"$ref":"#/components/schemas/FileFind"},{"$ref":"#/components/schemas/FileStat"},{"$ref":"#/components/schemas/FileResolve"},{"$ref":"#/components/schemas/FileSearch"},{"$ref":"#/components/schemas/FileLink"},{"$ref":"#/components/schemas/FileMove"},{"$ref":"#/components/schemas/FileMkdir"},{"$ref":"#/components/schemas/FileDelete"}],"discriminator":{"propertyName":"action","mapping":{"folders":"#/components/schemas/FileFolders","list":"#/components/schemas/FileList","find":"#/components/schemas/FileFind","stat":"#/components/schemas/FileStat","resolve":"#/components/schemas/FileResolve","search":"#/components/schemas/FileSearch","link":"#/components/schemas/FileLink","move":"#/components/schemas/FileMove","mkdir":"#/components/schemas/FileMkdir","delete":"#/components/schemas/FileDelete"}}}}}},"responses":{"200":{"description":"`folders` → `{path, folders: [{path, count, bytes, mtime}]}` ·\n`list` → `{path, entries: [FileMeta]}` · `find` → `{results: [FileMeta], truncated}` ·\n`stat` → `FileMeta` · `resolve` → `{paths: {name: path|null}}` ·\n`search` → `{results: [{path, match, snippet?}]}` · `link` → `{url, expiresAt}` ·\n`move` → `{from, to}` · `mkdir` → `{path}` · `delete` → `{path, trashed}`\n","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Ok"},{"properties":{"result":{"type":"object"}}}]}}}},"default":{"$ref":"#/components/responses/Error"}}}},"/v1/table":{"post":{"tags":["table"],"operationId":"table","x-needs":"see the table","summary":"Table actions over `table/<name>/{schema.json, entries/<id>.json}`.","description":"| action | level |\n|---|---|\n| `tables` | browse |\n| `schema` · `ids` | list |\n| `query` · `get` | read |\n| `create` · `set_schema` · `insert` · `put` · `delete` | write |\n\nA row is a file in `entries/` that is valid JSON and passes `schema.json` (when\nthere is one); anything else is listed under `errors`, never served as a row.\nChanging the schema never rewrites rows.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/TableTables"},{"$ref":"#/components/schemas/TableSchema"},{"$ref":"#/components/schemas/TableIds"},{"$ref":"#/components/schemas/TableQuery"},{"$ref":"#/components/schemas/TableGet"},{"$ref":"#/components/schemas/TableCreate"},{"$ref":"#/components/schemas/TableSetSchema"},{"$ref":"#/components/schemas/TableInsert"},{"$ref":"#/components/schemas/TablePut"},{"$ref":"#/components/schemas/TableDelete"}],"discriminator":{"propertyName":"action","mapping":{"tables":"#/components/schemas/TableTables","schema":"#/components/schemas/TableSchema","ids":"#/components/schemas/TableIds","query":"#/components/schemas/TableQuery","get":"#/components/schemas/TableGet","create":"#/components/schemas/TableCreate","set_schema":"#/components/schemas/TableSetSchema","insert":"#/components/schemas/TableInsert","put":"#/components/schemas/TablePut","delete":"#/components/schemas/TableDelete"}}}}}},"responses":{"200":{"description":"`tables` → `{tables: [{scope, rows, errors, mtime}]}` · `schema` → `{schema, etag}` ·\n`ids` → `{ids: [{id, mtime}], total}` · `query` → `{rows: [Row], total, limit, offset, errors}` ·\n`get` → `Row` · `create` → `{scope}` · `set_schema` → `{etag}` · `insert` → `{id, etag}` ·\n`put` → `{id, etag, created}` · `delete` → `{id, trashed}`\n","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Ok"},{"properties":{"result":{"type":"object"}}}]}}}},"default":{"$ref":"#/components/responses/Error"}}}}},"components":{"securitySchemes":{"bearer":{"type":"http","scheme":"bearer","description":"`ak_…` from `register`, or `ok_…` (the owner, 12 h) from `login`."}},"headers":{"ETag":{"description":"the SHA-256 of the file, quoted","schema":{"type":"string","pattern":"^\"[0-9a-f]{64}\"$"}}},"responses":{"Error":{"description":"| status | HTTP |\n|---|---|\n| `invalid_request` | 400 |\n| `unauthorized` | 401 |\n| `grant_required` · `forbidden` | 403 |\n| `not_found` | 404 |\n| `conflict` | 409 |\n| `precondition_failed` | 412 |\n| `too_large` | 413 |\n| `misdirected` | 421 |\n| `precondition_required` | 428 |\n| `rate_limited` | 429 |\n| `internal_error` | 500 |\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Ok":{"type":"object","required":["status","result"],"properties":{"status":{"const":"ok"},"result":{}}},"Error":{"type":"object","required":["status","message"],"properties":{"status":{"enum":["invalid_request","unauthorized","grant_required","forbidden","not_found","conflict","precondition_failed","too_large","misdirected","precondition_required","rate_limited","internal_error"]},"message":{"type":"string","description":"for people; may change"},"resource":{"type":"string"},"scope":{"type":"string"},"level":{"type":"string"}}},"Path":{"type":"string","description":"vault-relative, no leading `/`, no segment starting with `.`","examples":["photos/2026/10/06/IMG_0001.jpg"]},"ETag":{"type":"string","pattern":"^\"[0-9a-f]{64}\"$"},"Level":{"enum":["browse","list","read"]},"Resource":{"enum":["file","table","meta"]},"Grant":{"type":"object","required":["index","resource","scope","level","write","reason","status","expiresAt"],"properties":{"index":{"type":"integer"},"resource":{"$ref":"#/components/schemas/Resource"},"scope":{"description":"a path prefix matched by whole segments, or a list of them (any one)","oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":32}]},"level":{"oneOf":[{"$ref":"#/components/schemas/Level"},{"type":"null"}]},"write":{"type":"boolean"},"reason":{"type":"string"},"status":{"enum":["pending","active"]},"requestedAt":{"type":"integer"},"expiresAt":{"type":["integer","null"]}}},"Account":{"type":"object","required":["id","purpose","status","createdAt","lastUsedAt","grants"],"properties":{"id":{"type":"string"},"purpose":{"type":"string"},"status":{"enum":["enabled","disabled"]},"createdAt":{"type":"integer"},"lastUsedAt":{"type":["integer","null"]},"grants":{"type":"array","items":{"$ref":"#/components/schemas/Grant"}}}},"Me":{"type":"object","required":["id","role","grants"],"properties":{"id":{"type":"string"},"role":{"enum":["admin","account"]},"purpose":{"type":"string"},"status":{"enum":["enabled","disabled"]},"grants":{"type":"array","items":{"$ref":"#/components/schemas/Grant"}},"expiresAt":{"type":["integer","null"]}}},"AuthLogin":{"x-needs":"no key · the tailnet door · a login listed in admins","examples":[{"action":"login"}],"type":"object","required":["action"],"properties":{"action":{"const":"login"}}},"AuthRegister":{"x-needs":"no key","examples":[{"action":"register","params":{"id":"claude-fashion-1","purpose":"Build a fashion table from my photos"}}],"type":"object","required":["action","params"],"properties":{"action":{"const":"register"},"params":{"type":"object","required":["id","purpose"],"properties":{"id":{"type":"string","pattern":"^[a-z0-9][a-z0-9._-]{2,39}$"},"purpose":{"type":"string","minLength":1,"maxLength":500}}}}},"AuthRequest":{"x-needs":"any key","examples":[{"action":"request","params":{"resource":"file","scope":"photos","level":"browse","reason":"Find the folder of today's photos"}}],"type":"object","required":["action","params"],"properties":{"action":{"const":"request"},"params":{"type":"object","required":["resource","scope","reason"],"properties":{"resource":{"$ref":"#/components/schemas/Resource"},"scope":{"$ref":"#/components/schemas/Grant/properties/scope"},"level":{"$ref":"#/components/schemas/Level"},"write":{"type":"boolean","default":false},"reason":{"type":"string","minLength":1,"maxLength":1000},"ttlDays":{"type":"integer","minimum":1,"maximum":365}}}}},"AuthAccounts":{"x-needs":"admin key · the tailnet door","examples":[{"action":"accounts"}],"type":"object","required":["action"],"properties":{"action":{"const":"accounts"}}},"AuthGrant":{"x-needs":"admin key · the tailnet door","examples":[{"action":"grant","params":{"account":"claude-fashion-1","index":0,"ttlDays":7}}],"type":"object","required":["action","params"],"properties":{"action":{"const":"grant"},"params":{"type":"object","required":["account"],"description":"`index` approves a pending request; otherwise `resource` + `scope` (+ `level` / `write`) grants a new one.","properties":{"account":{"type":"string"},"index":{"type":"integer"},"ttlDays":{"type":"integer","minimum":1,"maximum":365},"resource":{"$ref":"#/components/schemas/Resource"},"scope":{"$ref":"#/components/schemas/Grant/properties/scope"},"level":{"$ref":"#/components/schemas/Level"},"write":{"type":"boolean"}}}}},"AuthDeny":{"x-needs":"admin key · the tailnet door","examples":[{"action":"deny","params":{"account":"claude-fashion-1","index":0}}],"type":"object","required":["action","params"],"properties":{"action":{"const":"deny"},"params":{"type":"object","required":["account","index"],"properties":{"account":{"type":"string"},"index":{"type":"integer"}}}}},"AuthStatus":{"x-needs":"admin key · the tailnet door","examples":[{"action":"disable","params":{"account":"claude-fashion-1"}}],"type":"object","required":["action","params"],"properties":{"action":{"enum":["disable","enable"]},"params":{"type":"object","required":["account"],"properties":{"account":{"type":"string"}}}}},"Meta":{"type":"object","required":["api","commit","now","host","vault","backups"],"properties":{"api":{"const":1},"commit":{"type":["string","null"]},"now":{"type":"integer"},"host":{"type":"object","description":"host-health.sh's line","properties":{"at":{"type":"integer"},"boot":{"type":"integer"},"power":{"type":["object","null"],"properties":{"source":{"type":"string"},"percent":{"type":["number","null"]},"state":{"type":"string"}}},"sleep":{"type":["object","null"],"additionalProperties":{"type":["number","null"]}},"disk":{"$ref":"#/components/schemas/Disk"},"drive":{"oneOf":[{"$ref":"#/components/schemas/Disk"},{"type":"null"}]},"drive_path":{"type":"string"},"error":{"type":"string"}}},"vault":{"type":"object","properties":{"files":{"type":"integer"},"bytes":{"type":"integer"},"texts":{"type":"integer"},"fts":{"type":"boolean"},"kinds":{"type":"array","items":{"type":"object","properties":{"ext":{"type":"string"},"files":{"type":"integer"},"bytes":{"type":"integer"}}}},"indexing":{"type":"boolean"},"last_walk":{"type":["number","null"]},"last_seconds":{"type":["number","null"]},"error":{"type":["string","null"]}}},"backups":{"type":"array","items":{"type":"object","required":["name","status"],"properties":{"name":{"type":"string"},"status":{"enum":["ok","warn","fail","running","unknown"]},"last":{"type":"object","properties":{"started":{"type":"integer"},"done":{"type":["integer","null"]},"snapshot":{"type":["string","null"]},"warn":{"type":"array","items":{"type":"string"}},"abort":{"type":"array","items":{"type":"string"}}}},"last_ok":{"type":["integer","null"]},"error":{"type":"string"}}}}}},"Disk":{"type":"object","properties":{"total":{"type":"integer"},"free":{"type":"integer"}}},"FileMeta":{"type":"object","required":["path","dir","size","mtime"],"properties":{"path":{"$ref":"#/components/schemas/Path"},"name":{"type":"string"},"dir":{"type":"boolean"},"size":{"type":["integer","null"]},"mtime":{"type":"integer"},"taken":{"type":["integer","null"],"description":"when the picture or video was made (file metadata)","when known":null},"type":{"type":["string","null"],"description":"MIME type"},"etag":{"$ref":"#/components/schemas/ETag"}}},"PutResult":{"allOf":[{"$ref":"#/components/schemas/Ok"},{"properties":{"result":{"type":"object","required":["path","etag","created"],"properties":{"path":{"$ref":"#/components/schemas/Path"},"etag":{"$ref":"#/components/schemas/ETag"},"created":{"type":"boolean"}}}}}]},"FileFolders":{"x-needs":"browse","examples":[{"action":"folders","params":{"path":"photos","depth":2}}],"type":"object","required":["action","params"],"properties":{"action":{"const":"folders"},"params":{"type":"object","required":["path"],"properties":{"path":{"type":"string","description":"\"\" is the root"},"depth":{"type":"integer","minimum":1,"maximum":4,"default":1}}}}},"FileList":{"x-needs":"list","examples":[{"action":"list","params":{"path":"photos/2026/10/06"}}],"type":"object","required":["action","params"],"properties":{"action":{"const":"list"},"params":{"type":"object","required":["path"],"properties":{"path":{"type":"string"}}}}},"FileFind":{"x-needs":"list","examples":[{"action":"find","params":{"path":"photos","ext":[".jpg"],"by":"taken","since":1791241200}}],"type":"object","required":["action","params"],"properties":{"action":{"const":"find"},"params":{"type":"object","required":["path"],"properties":{"path":{"type":"string","description":"searched with every folder below it"},"ext":{"type":"array","items":{"type":"string","examples":[".jpg"]}},"name":{"type":"string","description":"a substring of the file name","case-insensitive":null},"since":{"type":"integer"},"until":{"type":"integer"},"by":{"enum":["mtime","taken"],"default":"mtime","description":"`taken` falls back to `mtime` where unknown"},"limit":{"type":"integer","minimum":1,"maximum":1000,"default":200}}}}},"FileStat":{"x-needs":"list","examples":[{"action":"stat","params":{"path":"photos/2026/10/06/IMG_0001.jpg"}}],"type":"object","required":["action","params"],"properties":{"action":{"const":"stat"},"params":{"type":"object","required":["path"],"properties":{"path":{"$ref":"#/components/schemas/Path"}}}}},"FileResolve":{"x-needs":"list","examples":[{"action":"resolve","params":{"names":["Siddhartha"]}}],"type":"object","required":["action","params"],"properties":{"action":{"const":"resolve"},"params":{"type":"object","required":["names"],"properties":{"names":{"type":"array","items":{"type":"string"},"maxItems":500}}}}},"FileSearch":{"x-needs":"read","examples":[{"action":"search","params":{"q":"Siddhartha","limit":20}}],"type":"object","required":["action","params"],"properties":{"action":{"const":"search"},"params":{"type":"object","required":["q"],"properties":{"q":{"type":"string","minLength":1},"path":{"type":"string"},"limit":{"type":"integer","minimum":1,"maximum":200,"default":50}}}}},"FileLink":{"x-needs":"read","examples":[{"action":"link","params":{"path":"photos/2026/10/06/IMG_0001.jpg","ttl":600}}],"type":"object","required":["action","params"],"properties":{"action":{"const":"link"},"params":{"type":"object","required":["path"],"properties":{"path":{"$ref":"#/components/schemas/Path"},"ttl":{"type":"integer","minimum":30,"maximum":3600,"default":600}}}}},"FileMove":{"x-needs":"write","examples":[{"action":"move","params":{"from":"inbox/a.md","to":"notes/a.md"}}],"type":"object","required":["action","params"],"properties":{"action":{"const":"move"},"params":{"type":"object","required":["from","to"],"properties":{"from":{"$ref":"#/components/schemas/Path"},"to":{"$ref":"#/components/schemas/Path"},"etag":{"$ref":"#/components/schemas/ETag"}}}}},"FileMkdir":{"x-needs":"write","examples":[{"action":"mkdir","params":{"path":"notes/2026"}}],"type":"object","required":["action","params"],"properties":{"action":{"const":"mkdir"},"params":{"type":"object","required":["path"],"properties":{"path":{"$ref":"#/components/schemas/Path"}}}}},"FileDelete":{"x-needs":"write","examples":[{"action":"delete","params":{"path":"inbox/a.md","etag":"\"0000000000000000000000000000000000000000000000000000000000000000\""}}],"type":"object","required":["action","params"],"properties":{"action":{"const":"delete"},"params":{"type":"object","required":["path","etag"],"properties":{"path":{"$ref":"#/components/schemas/Path"},"etag":{"$ref":"#/components/schemas/ETag"}}}}},"TableScope":{"type":"string","pattern":"^table/[a-z0-9][a-z0-9_-]{0,63}$","examples":["table/fashion"]},"Row":{"type":"object","required":["id","mtime","etag","data"],"properties":{"id":{"type":"string"},"mtime":{"type":"integer"},"etag":{"$ref":"#/components/schemas/ETag"},"data":{}}},"TableTables":{"x-needs":"browse","examples":[{"action":"tables"}],"type":"object","required":["action"],"properties":{"action":{"const":"tables"}}},"TableSchema":{"x-needs":"list","examples":[{"action":"schema","scope":"table/fashion"}],"type":"object","required":["action","scope"],"properties":{"action":{"const":"schema"},"scope":{"$ref":"#/components/schemas/TableScope"}}},"TableIds":{"x-needs":"list","examples":[{"action":"ids","scope":"table/fashion","params":{"limit":100}}],"type":"object","required":["action","scope"],"properties":{"action":{"const":"ids"},"scope":{"$ref":"#/components/schemas/TableScope"},"params":{"type":"object","properties":{"limit":{"type":"integer","minimum":1,"maximum":10000,"default":1000},"offset":{"type":"integer","minimum":0,"default":0}}}}},"TableQuery":{"x-needs":"read","examples":[{"action":"query","scope":"table/fashion","params":{"where":{"kind":"top"},"sort":"-taken","limit":20}}],"type":"object","required":["action","scope"],"properties":{"action":{"const":"query"},"scope":{"$ref":"#/components/schemas/TableScope"},"params":{"type":"object","properties":{"where":{"type":"object","description":"`{field: value}`; a dotted field reaches into objects; an array field matches if any element does","additionalProperties":{"type":["string","number","boolean"]}},"sort":{"type":"string","description":"`field` or `-field`"},"limit":{"type":"integer","minimum":0,"maximum":1000,"default":100},"offset":{"type":"integer","minimum":0,"default":0}}}}},"TableGet":{"x-needs":"read","examples":[{"action":"get","scope":"table/fashion","params":{"id":"row-1"}}],"type":"object","required":["action","scope","params"],"properties":{"action":{"const":"get"},"scope":{"$ref":"#/components/schemas/TableScope"},"params":{"type":"object","required":["id"],"properties":{"id":{"$ref":"#/components/schemas/RowId"}}}}},"TableCreate":{"x-needs":"write","examples":[{"action":"create","scope":"table/fashion","params":{"schema":{"type":"object","required":["photo","kind"],"properties":{"photo":{"type":"string"},"kind":{"enum":["top","bottom","outer","shoes","accessory"]},"colors":{"type":"array","items":{"type":"string"}},"taken":{"type":"integer"}}}}}],"type":"object","required":["action","scope"],"properties":{"action":{"const":"create"},"scope":{"$ref":"#/components/schemas/TableScope"},"params":{"type":"object","properties":{"schema":{"$ref":"#/components/schemas/RowSchema"}}}}},"TableSetSchema":{"x-needs":"write","examples":[{"action":"set_schema","scope":"table/fashion","params":{"schema":{"type":"object","required":["photo","kind"]},"etag":"\"0000000000000000000000000000000000000000000000000000000000000000\""}}],"type":"object","required":["action","scope","params"],"properties":{"action":{"const":"set_schema"},"scope":{"$ref":"#/components/schemas/TableScope"},"params":{"type":"object","required":["schema"],"properties":{"schema":{"$ref":"#/components/schemas/RowSchema"},"etag":{"$ref":"#/components/schemas/ETag"}}}}},"TableInsert":{"x-needs":"write","examples":[{"action":"insert","scope":"table/fashion","params":{"data":{"photo":"photos/2026/10/06/IMG_0001.jpg","kind":"top","colors":["navy"],"taken":1791243000}}}],"type":"object","required":["action","scope","params"],"properties":{"action":{"const":"insert"},"scope":{"$ref":"#/components/schemas/TableScope"},"params":{"type":"object","required":["data"],"properties":{"data":{}}}}},"TablePut":{"x-needs":"write","examples":[{"action":"put","scope":"table/fashion","params":{"id":"row-1","data":{"photo":"photos/2026/10/06/IMG_0001.jpg","kind":"shoes"},"create":true}}],"type":"object","required":["action","scope","params"],"properties":{"action":{"const":"put"},"scope":{"$ref":"#/components/schemas/TableScope"},"params":{"type":"object","required":["id","data"],"description":"`etag` to replace, or `create: true` to create","properties":{"id":{"$ref":"#/components/schemas/RowId"},"data":{},"etag":{"$ref":"#/components/schemas/ETag"},"create":{"type":"boolean"}}}}},"TableDelete":{"x-needs":"write","examples":[{"action":"delete","scope":"table/fashion","params":{"id":"row-1","etag":"\"0000000000000000000000000000000000000000000000000000000000000000\""}}],"type":"object","required":["action","scope","params"],"properties":{"action":{"const":"delete"},"scope":{"$ref":"#/components/schemas/TableScope"},"params":{"type":"object","required":["id","etag"],"properties":{"id":{"$ref":"#/components/schemas/RowId"},"etag":{"$ref":"#/components/schemas/ETag"}}}}},"RowId":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$"},"RowSchema":{"type":"object","description":"A JSON Schema (2020-12 spelling) for one row. v1 checks these keywords and\nrefuses a schema that uses any other (so a schema never silently means less\nthan it says): `type`, `properties`, `required`, `additionalProperties`\n(boolean or schema), `items`, `minItems`, `maxItems`, `enum`, `const`,\n`minimum`, `maximum`, `minLength`, `maxLength`, `pattern`, `anyOf`;\nand, as annotations only, `$schema`, `$id`, `title`, `description`,\n`default`, `examples`, `format`.\n"}}}}