{"node":{"id":"a78418d7-e9f3-426d-9148-a8fc7c3787c9","handle":"a784","type":"note","intent":"truth","title":"Using it — the doors, the CLI and the API","aiSummary":"Authenticating, capturing, reading, linking, walking, granting and publishing, through the CLI or against the API directly.","content":"## Two ways in\n\nThe CLI wraps the API and holds the credential, so most tools never see a token. The API is the same surface without the wrapper, and it is the front door for any AI that connects directly.\n\nThe route reference is the OpenAPI spec the API serves at `/api/json`, readable at `/api`. Both are relative to the API base, not the host — on prod the base is `/api/lattris`, so the spec is at `https://lattris.com/api/lattris/api/json`. It is the reference, not a promise of completeness: some routes are still missing their documentation. It is honest about credentials: it requires a bearer credential on every route site-wide and marks the open routes with an empty security list. Read it for every route's shape and its credential, and read this block for what the spec cannot carry: which door to use when, and why.\n\nWhat shipped in code is the repo's record, not this tree's. The merged pull requests of `foundations-io` and the runs of its deploy workflow, named Continuous Deployment, say what reached prod and when. The tree keeps no deploy list.\n\n## The CLI\n\n- `lattris login` signs in through the browser.\n- `lattris whoami` prints who the session acts as, and quietly refreshes an expired token.\n- `lattris use <actor>` binds a directory to an identity, so different workspaces act as different people on one machine.\n- `lattris capture memory` saves a note from stdin. `--ai-summary` gives it its abstract in the same call, and `--linked-from <id>` or `--links-to <id>` wires one edge as it is created — the existing block pointing at the new one, or the new one pointing out.\n- `lattris capture session` saves a whole conversation from its transcript. `lattris capture recording` keeps an audio recording, its source, or its transcript — a vendor transcript is converted here, because the door meets one shape.\n- `lattris link <from> <to> --verb --reason` writes an edge, `lattris unlink <from> <to> --verb` removes it, and `lattris update edge <from> --verb --to --reason` rewrites the reason. `--reason` is required and the CLI never invents one.\n- `lattris update recording transcript <id> <file>` replaces the words; `lattris update recording speakers <id> <key>=me|<contact-id>|clear` names who spoke.\n- `lattris set banned-words` shows this account's list, and replaces the whole list when given words.\n- `lattris close question <id> --close-as updated|declined|already-answered` closes an ask.\n- `lattris get memories` lists. `lattris get memory <id>` reads one back with its links, and `--depth <n>` walks on from there — one call can pull most of a tree that way, which the reading rules forbid, so raise it only when you mean to.\n- `lattris update memory <id>` corrects one. The new words arrive on stdin, the way capture takes them, and the banned-word gate fires on them with `--allow-banned-words` as the override. Three flags ride with it:\n  - `--title` retitles\n  - `--intent <name>` re-declares what it is\n  - `--clear-intent` takes it back to none\n\n  The CLI reads the block first and sends the `updatedAt` it read, so a block that moved in between is refused rather than overwritten, and `--overwrite` skips that check.\n- `lattris share memory <id> --with public` grants. `lattris unshare` withdraws.\n- `lattris setup` moves an AI workspace onto Lattris as its memory.\n- Your own settings live on your person record. `GET /person/me` reads them back with your preferences; `PATCH /person/me` writes them, replacing a preference whole per key. Two keys exist: `banned_words`, the list the write doors check, and `ask_requires_role_lines`, true or false, which makes your questions door refuse an ask that lacks a For line or a Filed by line.\n- `--as <email>` acts as another local actor for one command. `--env prod` targets production instead of local. Sessions are stored per actor, per environment.\n\n## Reading\n\n`GET /memories/{id}` returns a block. Add `?links=1` for its edges and their reasons, and `&content=0` when you only need the shape. Every link's node and the anchor's own node carry `href`, the absolute read address of that block with `links=1`, in the public form on the public door and the authenticated form on this one, so a reader follows a link by opening its href and never builds an address. The public read door answers the block as a page when the caller's Accept prefers `text/html` or `format=html` rides on the address: every link is a real anchor carrying its verb and reason, and a footer anchor points at the JSON form. `format=json` forces JSON whatever the Accept says, and a JSON caller sees no change. The door speaks both. The website's public pages, the homepage and any public block address, carry the block's title, summary, words and one anchor per link in their HTML before any script runs. They are filled from the anonymous read, so a private block reads not-found for everyone, with a short shared cache keyed on the block's `updatedAt`, and the plain shell when the fill cannot happen.\n\n`GET /memories` lists newest first and takes filters: `q=` searches titles and content, `topic=` narrows to one topic's members.\n\nA public tree is read at `/by/{handle}/memories` without signing in, and a block can be addressed by its full id, its short handle, or its slug. An id obtained outside the tree can return 404, and a 404 deliberately does not tell you whether the thing does not exist or is simply not yours to see.\n\n**There is no history.** A block has one version, the current one. `updatedAt` says when it last changed, and no route returns what it said before — there is no versions, revisions or audit door to find. One caveat, and it is real: a captured session is an ordinary block on this tree, and its transcript may describe what a block used to say. That is a side effect, not a history door — it exists only where someone saved a conversation that discussed the change, and it is a description of the edit, not the old text.\n\n## Writing\n\n- `POST /memories/notes` creates a note, and takes `aiSummary` in the same call. The create door refuses unknown fields rather than ignoring them.\n- `PATCH /memories/{id}` corrects a block, and re-declares intent: omit the field and it stays as it was, send null and it clears. It also takes `expectedUpdatedAt`, the `updatedAt` a content read returned. A read with `content=0` carries no `updatedAt`, so the version field comes only from a content read. When the block has changed since that read, the door refuses with `409` and `memory:stale-write`, writes nothing, and says: \"This block changed at <current>, after the version you read (<expected>). Nothing was written. Re-apply your change to detail.current and retry with its updatedAt.\" Send no version and the write lands as before.\n- Both of those doors check the title and the content against a list of banned words. The list is yours to set, and a preference you never set is not the same as one you set to nothing: absent means the door was never asked to check, empty means you asked it to check against nothing. A refusal names every word it caught and the field that overrides it, `allowBannedWords: true`. The house voice says which words and why.\n- `POST /memories/{id}/edges` links two blocks you own. The body takes `verb`, `toBlockId` and `reason`. All three are required; omit one and the call returns `400`.\n- `PATCH /memories/{id}/edges` changes an existing edge's reason. It takes `verb`, `toBlockId` and the new `reason`. The verb and the two ends are the edge's address, not fields you can edit — send a different verb and you get a `404`, because you have named an edge that does not exist.\n- `DELETE /memories/{id}/edges` takes `verb` and `toBlockId`, no `reason`.\n- `PUT /memories/{id}/topics/{topicId}` adds a membership. Sending it twice does nothing the second time.\n- `POST /memories/recordings` keeps a recording. It stands on any one of three things: held audio, a source it lives at, or a transcript. The transcript arrives whole, speakers and turns together.\n- `PUT /memories/{id}/speakers` corrects who a voice belongs to, keyed by the speaker label the transcript already carries.\n- `POST /spans` creates a span, and a block joins one when it is created.\n\n## Sharing\n\nEverything is private until granted. `POST /memories/{id}/grants` makes a block public; `DELETE /memories/{id}/grants/public` withdraws it.\n\nWhat you grant is the block, not the account, so a tree can be half published. Absence of a grant is the privacy state, and the server enforces it: a stranger reading a private tree gets the same answer as a stranger reading a tree that does not exist.\n\n## The door for questions\n\nWhen a published tree cannot answer something, `POST /by/{handle}/questions` leaves the question with its owner. Signing in is required — a question carries its asker. There is no live reply, by design. The answer is the tree changing before the asker comes back.\n\n## Who is reading\n\nThree kinds of caller reach this surface:\n\n- **The tree's owner** — sees everything, private included.\n- **A signed-in person** — sees what was granted to them, and writes only to their own account.\n- **Anybody without a credential** — sees only public blocks, at `/by/{handle}` alone.\n\nThe server decides which you are from the credential. Nothing asks you to declare it.\n\n**Probe.** Asked to show exactly what a block said before its last edit, what can a session that has read only this block answer, and why? It has to get there without hunting for a history route first.","capturedAt":"2026-08-31T02:44:24.227Z","createdAt":"2026-08-31T02:44:24.228Z","updatedAt":"2026-09-03T12:47:22.326Z","spanId":null,"contributor":null,"audiences":[{"kind":"public","createdAt":"2026-09-02T12:11:25.779Z"}]},"links":[{"direction":"in","verb":"references","reason":"only if you need to run a command or call a route: sign-in, capture, read, link, share","node":{"id":"43fffd4b-9661-47dc-a173-7fd01f5d010b","handle":"43ff","type":"note","title":"Lattris — a memory store for your life","aiSummary":"What Lattris is, for a person and for the AI they bring: knowledge held as linked blocks the person owns, how this tree is read, and where everything else lives. Hand an AI this block first.","href":"https://lattris.com/api/lattris/by/4/memories/43fffd4b-9661-47dc-a173-7fd01f5d010b?links=1"}},{"direction":"in","verb":"references","reason":"only if you are at step 4 and need the write doors — creating a node, giving it its summary, linking two blocks","node":{"id":"cbabaa5a-c1ae-471f-ad55-01db5ca4e547","handle":"cbab","type":"note","title":"Creating a recipe — start here","aiSummary":"How to turn a practice into a walkable recipe in your own account: what a recipe is, where it lives, the five steps — gather evidence, capture sources first, distill, author and wire, prove with a probe — and what honesty asks when you absorb someone else's knowledge instead of linking to it.","href":"https://lattris.com/api/lattris/by/4/memories/cbabaa5a-c1ae-471f-ad55-01db5ca4e547?links=1"}}]}