← The Karsa Manual

The Karsa API

You don't have to memorize a big endpoint list. Karsa's API is uniform and generated: the moment you expose a collection, it gets a full CRUD API, a schema, and CRUD events — automatically. Learn the one pattern below + the handful of special endpoints, and you can drive the whole system. Everything is under $KARSA_API_BASE (already project-scoped — never hard-code a host or /api/...).

The uniform CRUD pattern

For any collection {c} (its slug — spec, task, deal, …):

GET    ${KARSA_API_BASE}/{c}             → list resources
POST   ${KARSA_API_BASE}/{c}             → create   { frontmatter, body? }
GET    ${KARSA_API_BASE}/{c}/{id}        → read one
PATCH  ${KARSA_API_BASE}/{c}/{id}        → update   { patch, body? }
DELETE ${KARSA_API_BASE}/{c}/{id}        → delete

Every resource has the same envelope

Whatever the collection, a read returns these baseline fields (plus the type's own fields):

| field | meaning | |---|---| | id | the resource id ({type}_{ULID}) | | _t | its type | | created_at / created_by | when + who created it | | updated_at / updated_by | when + who last changed it |

created_by/updated_by is user or a subscriber id (sber_…) — that's what a subscriber's updated_by: { $ne: "$self" } filter keys on (see Automation).

Types, schemas, and CRUD shapes

A collection enforces a type, and a type has a schema — so the server knows every CRUD shape derived from it. Inspect them:

GET ${KARSA_API_BASE}/type-registry            → all types in this project
GET ${KARSA_API_BASE}/type-registry/{type}     → one type: its descriptor, base
                                                  JSON Schema, and the derived
                                                  variants:
    variants.read     — the full resource (base + envelope)
    variants.create   — the create input  (base minus the server-managed fields)
    variants.update   — the update patch   (any subset)
    variants.events   — {type}.created / .updated / .deleted payloads

Expose a new type (a type/<name>/schema.json) or a collection over it, and its CRUD API + schema + events are immediately live — nothing else to wire.

The special (non-CRUD) endpoints

A small set of endpoints aren't plain resource CRUD:

Storage

Automation (see Automation)

Find + observe

Secrets — declare, ask, use (never handle the value)

When you need a credential (an API key, a token), you never ask for the value in chat and never put it in a file or env var. Instead:

For a CLI instead of HTTP: . Declare a secret with a Sentence-case title, a description, and a usage policy that scopes where it may go: `` POST ${KARSA_API_BASE}/secret { "frontmatter": { "title": "Resend API key", "description": "Send deal-flow notification emails.", "policy": { "via": "http", "allow": [ { "host": "api.resend.com" } ] } } } → { "id": "secret_…" } ` For a CLI instead of HTTP: "policy": { "via": "tool", "tools": ["gh"], "inject": { "env": "GH_TOKEN" } }. 2. **Ask the user** to open the secret's link and paste the value — e.g. *"I need the Resend API key — please add it here: secret_…"*. They fill it through a masked field; it's encrypted and you're notified (a secret.updated event, status set). You never see the value. 3. **Use** it by reference — the daemon substitutes it and makes the call; the value never reaches you: ` POST ${KARSA_API_BASE}/egress { "method": "POST", "url": "https://api.resend.com/emails", "headers": { "Authorization": "Bearer {{secret_…}}" }, "body": { … } } ` or for a tool: POST ${KARSA_API_BASE}/tool-run { "tool": "gh", "args": [...], "use": ["secret_…"] }`.

The daemon refuses to send a secret anywhere its policy doesn't allow (so a key bound to api.resend.com can't be redirected elsewhere). GET ${KARSA_API_BASE}/secret lists secrets (names + has_value, never the value). Do not ask the user to paste a key into chat, and don't try to read a secret's value — there is no endpoint that returns it.

Discover the whole surface

The complete, machine-readable contract is generated live — including the per-collection CRUD for this project's collections and every type's variant schemas:

GET ${KARSA_API_BASE}/openapi.json

That's the source of truth if you need an exact request/response shape. But for everyday work: the uniform CRUD pattern + the special endpoints above is the whole game.