Ogen
In the docs
On this page

Run your tenant

Plans, keys and roles, the activity record, shadow and evidence mode, deletion.

Manage your tenant

Your tenant page opens on an overview: today’s numbers, what’s waiting on a person and how long it has left, the newest live events and the two decision signals. Each links to its own view — Live, Waiting, Plan, Sources, Actions, Recent decisions, Recent activity and, for an owner, Settings — as tabs on a wide screen, a tab bar on a phone. A link like /tenant.html#waiting opens straight on that view.

Plans

Free, Record, Team, Licensed.

PlanPriceWhat's included
Free$0Live reads against the demo source, demo actions held for a person, what it caught (7 or 30 days), a daily cap.
The daily cap is per tenant, per day — one account, one allowance, however many keys you hold and whichever door the call comes through (the MCP tools and the /v1 routes share the same count). Managing your account is never counted against it: reading your tenant page, listing or minting or revoking a key, starting Record and going to checkout all keep working at the cap. A call refused because your key isn’t permitted to make it, or because your plan doesn’t include it, costs nothing — only a call that actually runs is counted. A HEAD request to any /v1 route that reads a live fact costs exactly what the matching GET would — the count happens before the response is built, so dropping the body on the way out doesn’t make the call free. What the confirm page reads never touches your daily count: each held action carries its own small allowance, so one link, however often it is opened, can never spend your day. When that allowance is gone the page still opens and says it couldn’t read the value just now. Reject still works; Confirm waits for a fresh read.
Record$180 a month or $1,800 a year, prepaid, for up to 5 scopes; then $30 a month or $300 a year per scope. Free until Ogen has held 10 actions.Your own sources (read-only, https), your own actions held for the person who decides them, held-for-a-person confirmation links, the retained record for every scope, unlimited reads.
Team$18,000/year, by conversationUnlimited sources and kinds, multiple keys, long retention, a named contact.
LicensedNegotiatedField-of-use license, hosted or in your own infrastructure.

A save is one time Ogen kept your agent from acting on stale or uncertain data: a read that came back ambiguous or unavailable, or a value caught moving since it was last read. what_it_caught's would_have_acted_on_stale shows your count.

A scope is one kind of action with a named person who decides it — the thing an auditor asks about: who approves refunds, and where is the record? Register a kind with an approver and it is a scope; a kind with no approver, and the demo kinds, are not. Record covers 5. Registering a sixth is refused with "Record covers 5 scopes. Add a scope for $30 a month or $300 a year: POST /api/checkout with scope: 1, or see /docs/operate#plans." Each scope you add runs with the Record month or year you’re in, and is priced in its unit — the year’s when you hold both. Only adding is ever refused: a scope you already have keeps working, and you can change who decides it at any time. An account that had more than 5 before Record keeps every one of them.

Start Record from the pricing section of the home page; it's free until Ogen has held 10 actions, then continues at $180 a month or $1,800 a year. Either is a one-time payment, never a subscription: a month adds 30 days to your plan and a year adds 365, counted from when your plan now ends, so two months bought together are 60 days. Nothing renews by itself. Your tenant page's Plan card shows when your plan is paid until and whether by the month or the year, scopes used and covered, Add a month and Add a year, and Add a scope when you reach the limit. An account that paid $2,400 for Pro keeps that year as a Record year. For Team or Licensed, talk to us; Ogen doesn't publish a support email.

Keys

Self-serve, once you have your first key.

Get a free key from the landing page's signup form — one per email, delivered by email, shown once. From there, manage further keys yourself with any live key of your tenant:

CallDoes
GET /v1/keysLists your tenant's keys: id, label, tier, created/revoked timestamps, last 4 characters, scope — never the key itself.
POST /v1/keys { label?, scope? }Mints a new key at your own tier (a pro key mints pro, a free key mints free). scope is an array of fact-key prefixes this key may reach (e.g. ["order.", "customer.acme."]) — omit or leave empty for the whole tenant, same as every key minted before scopes existed. The raw key is returned exactly once. Requires an owner key or a signed-in person — a plain operator key is refused outright, with "this key is an operator; ask an owner, or sign in". See Roles. At most 25 live keys per tenant: past that the mint is refused (too_many_keys) until you revoke one — and the count and the mint happen in one step, so two requests arriving together at 24 live keys can no longer both be told there is room, and minting is rate-limited per caller like the signup form. An extra key buys no extra daily allowance — the free cap is per tenant, not per key.
DELETE /v1/keys/<id>Revokes one key. A revoked key authenticates nowhere. Your last live key can't be revoked — mint a replacement first, and nor can your last live owner key (last_owner_key). Revoking someone else's key needs an owner key or a signed-in operator, the same as minting; revoking your own key needs nothing more than that key — if you think the credential in your hand has leaked, retire it yourself rather than waiting for an administrator.
POST /api/trialStarts Record for your tenant, free until Ogen has held 10 actions (or reports it if already started). Bearer-key authenticated. Comes back { tier, savesLeft }.
POST /api/checkout { unit?, scope? }Starts payment. With unit ("month" or "year"), it's a Record month or year, plus scope added scopes when you ask for them (without scope, it keeps the scopes you already pay for). An empty body is a year. With scope: n and no unit, it adds n scopes in the unit of the Record term you're in — $30 a month or $300 a year each, the year's when you hold both — and answers 409 no_running_term when no month or year is running. scope is a whole number from 1 to 100. Bearer-key or signed-in. Comes back { url } — send the caller there to pay. Answers 503 pricing_unavailable if pricing for that unit isn't set up yet.

You don’t have to start Record by hand. The first time an owner key, or a signed-in person, calls a Record tool on a tenant that has never had Record, Ogen starts it free and runs the call. The answer says so: "Record started free for this tenant: free until Ogen has held 10 actions, no card, nothing charges by itself." Any other Record call without Record refuses the same way, with a way forward instead of a dead end: "<tool> needs Record. Start Record free, no card: POST /api/trial with your key, or see /docs/operate#plans." When the free start ends, it says so: "Ogen caught 10 for you. Record continues at $180 a month or $1,800 a year, up to 5 scopes."

Everything in this table is also one click on the tenant page's own Settings section (owner sign-in only, 2026-09-25): the same list, "Create a key" and "Revoke", plus the account's contact email, timezone and weekend, its registered approvers with a one-click "Turn off passkeys", and "Sign out everywhere" / "Delete this tenant".

Roles

Every key has one of three roles. Owner: anything. Operator: everyday ACTS (bind a source, teach a fix, decide a hold, commit a gated action) but not ADMINISTERS (mint a key, revoke someone else's key, invite, delete the tenant, register an action or approver, switch gate/observe mode, set the contact address, toggle Assist, or set up/remove a connection or webhook) — owner-only unless the caller is a signed-in person at operator level or above (see "Signed-in access" below). A plain operator key hitting one of these is refused: "this key is an operator; ask an owner, or sign in". Viewer: read-only — recall facts, see what Ogen caught, look at holds and connections; every write is refused with "this key is a viewer; ask an owner", unaffected by signing in. A key minted before roles existed is an owner key; hand a teammate a narrower one with POST /v1/keys { role }. GET /v1/keys shows each key's role; an invitation carries no role (see Invites).

Scopes

A key with a scope can only reach keys under one of its prefixes: recall_live_fact, commit_gated_action (checked on the fact its kind resolves to, not the kind name), register_action, bind_source, and teach_fix all refuse a key outside scope with reason: "out_of_scope". A scoped key can't register an action against a key it can't itself reach. A prefix stops at a separator: customer.acme reaches customer.acme and customer.acme.balance, never customer.acmecorp.balance.

Signed-in access

A client signed in via OAuth (see Quick start) holds no raw key: your tenant carries one signed-in key row per person, shown in GET /v1/keys like any other, under the same tier, cap and scope rules. Signing in again reuses that row; a move to a different tenant, tier or role issues a new one and revokes the old. Revoke a person's access by revoking that row (DELETE /v1/keys/<id>) — their next call answers 401, and a fresh sign-in mints a fresh row only if you allow it on the consent page. A sign-in lands in the tenant its email already has a free key in, else a new one, by the same rule as the signup form. The consent page also accepts an existing ogen_sk_ key to link its tenant instead (a pro key links a pro seat). Tokens are checked live with the identity provider on every request, nothing cached in the pod; the tenant page keeps you signed in via an HttpOnly, same-origin cookie the pod alone can open, turned back into a bearer per request. Signing out ends the session at the identity provider and clears the cookie.

Invites. Sign-in is open, so an invitation now mainly carries the evaluation agreement. A pro tenant invites an email with POST /v1/invites { email } (pro bearer key or signed-in session; owner or signed-in only, like minting). On first sign-in, the consent page shows the Evaluation Confidentiality Agreement; accepting it records the acceptance (version, time, email hash) and only then creates the tenant. GET /v1/invites lists your invites and their acceptance state — the address itself is never stored, only a hash. An invitation carries no role: it opens a new account for whoever accepts it, never a seat in yours; adding someone to an existing account means one of its owners linking their sign-in to an owner key.

The same token works everywhere a key does: every /v1/* route, POST /api/trial, POST /api/checkout, and the tenant page, which has its own "sign in with email" next to the key field. Machine clients — scripts, CI, a stdio bridge — keep using keys. Discovery for OAuth clients lives at /.well-known/oauth-protected-resource.

Activity

An append-only record of what changed on your tenant.

GET /v1/activity?since=&limit= (bearer key, limit capped at 500) lists one row per: a fix taught/reversed/restored, an action or approver registered, a webhook registered/removed, a source bound, gate/observe or evidence mode changed, a key minted/revoked, a held confirmation decided, a trial started, a checkout paid. Each row: { at, actor, kind, subject, detail } — actor is a key id, or the approver's registered name for a decided confirmation; never a raw key or email. A hold decided by a typed address instead is recorded as approver: plus a one-way hash of it. detail.decided_via: "token", "passkey", "name", or "policy" for a release without a person (actor the owner who set the limit, detail.released_under the limit). Since 2026-09-26 a settings, webhook or connection change is written as <tool>:<action> — settings:mode, webhook:set, connections:remove; earlier rows keep the tool name they were written under. detail never carries a credential or a source value. A decided confirmation's row needs Record — free sees every other kind.

Export. Download your whole decision record in one file — every activity event, evidence behind a live read, and each held action's decision (decided_via included) — joined so you can see what was decided and what was seen. One click on the tenant page, or GET /v1/export. A plain JSON file, generated fresh, containing only what this page shows — never a credential or raw source value.

Ledger. One record of every agent ask that reached this pod's door, newest first — what it asked, who asked (a registered caller's own name, when one raised it), what it acted on, who allowed it, and what happened; a row also says whether it came from a decision that was raised (source: "hold", kept 90 days after it's decided or expires) or from an ask that never needed one — it ran, was drafted for a click, was refused outright, or was only shadowed (source: "event", kept 30 days). One residual: a "too many open at once" refusal never raised a hold and leaves no row (docs/data-map.md has the full account). Filter by caller, kind, resource or date on the tenant page's Ledger tab, or with GET /v1/ledger?since=&until=&caller=&kind=&resource=&limit= (JSON, or &format=csv for a spreadsheet) — both downloads follow the same filters. Download the whole (filtered) thing signed — GET /v1/ledger/export — so you, your auditor, or a counterparty in a dispute can check the file you're holding is exactly what Ogen produced under the filters named in the file, with no Ogen credential of your own: the one call the check itself makes is fetching Ogen's public verification key, never anything about your rows. See how to verify a ledger export below.

How to verify a ledger export. The signed document carries a signature block (alg, kid, typ, jws, sha256) at the end — a detached signature over a digest of the document itself, not embedded inside it, so it proves both that Ogen produced the file and that it has not changed by even one byte since. To check it with your own tooling: fetch the public key set from GET /.well-known/ogen-ticket-key (the same set every release ticket already verifies against — never a second key); take the document as given and drop only its own signature field; serialize what's left with keys sorted and no extra whitespace; compute its SHA-256 digest (hex, lower-case) and check it equals signature.sha256; verify signature.jws — a standard EdDSA JSON Web Signature (RFC 7515/8037) — against the key whose kid matches; and check the verified payload names the same sha256 and the same tenant as the document. One more check worth making before any of that: confirm the raw file text is exactly the canonical JSON.stringify of its own parsed value (re-serialize what you parsed, no pretty-printing, and compare byte-for-byte to the file) — this catches a duplicate key anywhere in the document, top level or nested inside a row, and an escaped key name a parser would resolve to an existing key, any of which could otherwise hide what was actually signed. If every step passes, the file is exactly what Ogen produced, unchanged, under the filters and scope the document itself names (both ride inside the same signed digest) — read it as "the whole ledger" only when every filter is null and scope is "all"; otherwise it is honestly a slice. rows_truncated: true means more matching rows exist beyond the file (never merely that a fetch came back full); evidence_truncated: true is separate and narrower — only some rows' fact-read evidence was capped; scan_ceiling_reached: true means the scan itself stopped at its own 20,000-row ceiling before it could prove the file is complete — treat it as a provable prefix, and narrow the filter for a complete download. Ask your Ogen contact for a copy of the reference verifier (a small, dependency-free script) if you would rather run it than write your own.

The person the agent acts for. A kind registered with approver: "principal" (Actions & holds) waits for whoever the ask itself names, never a registered approver — named only by the business's own operator or owner key, after its own system reads its own customer record; a caller can never open one, whatever it claims. Every such row carries a display label ("buyer"), never the address, in the ledger's own principal field, in decided_by, and in the activity log; the address itself is never stored anywhere on this pod in a form that could be read back. A principal decides only through the one mailed link's token — no typed name, no passkey, no "email me a new link."

Release without a person. On the tenant page, Settings → Release without a person sets, per kind, the amount below which it releases without asking (owner only); a key's own ceiling can only lower that. Neither overrides a disagreement — a person decides whatever the amount. Every such release shows in your record as released by the limit, never as a person's decision — on the Live list, in the log, the export and the action.released webhook — and is left out of time-to-decision numbers.

Your personal data. GET /v1/export?scope=personal (owner key or signed-in session only) downloads the personal data Ogen holds about your own account — your contact email, your keys' labels and roles, your registered approvers, and the signed-in identities linked to your tenant — separately from the team-wide record above.

Proof

what_it_caught reports what Ogen did for your tenant over a trailing window: what your agent asked, what waited for a person, what a person decided. The same numbers for the demo tenant are public, no key needed, on the proof page. Your tenant page shows two of its signals under "Recent decisions": "Decided within 10 seconds of the hold this week" and "The last 10 holds were confirmed as they stood". Neither one releases anything.

Shadow mode

settings (action "mode") with "observe".

Observe mode watches without holding: every would-be held/escalated action is counted instead, and registered webhooks still fire with would_have: "held" | "ran". Nothing is gated while it's on. Switch back with { "action": "mode", "mode": "gate" }.

Switching TO observe needs acknowledge: true and confirm: "nothing will be held" in the same call — either missing refuses it. Every commit_gated_action result carries the active mode, the tenant page banners while observe is on, and the contact (settings, action "contact") is emailed once when the switch lands, naming who and when.

Evidence mode

settings (action "evidence") / export_evidence.

While on, every recall_live_fact stores one row: the key, when, its resolved/ambiguous/unavailable status, and a fingerprint — a keyed record of what the read saw, checkable later with your secret, never the value itself. Give your own secret or let Ogen mint one (returned once, in the settings result — store it, never shown again). export_evidence hands back your stored rows (up to 1,000) to verify offline — Ogen never re-derives or checks a fingerprint after writing it.

Deletion

Self-serve, from your own key.

CallDoes
DELETE /v1/tenantOwner-only. Schedules deletion 24 hours out. Comes back { scheduled: true, effective_after }. Calling it again just moves the clock.
POST /v1/tenant/undeleteOwner-only. Cancels a scheduled deletion within the window. Comes back { cancelled } — false, not an error, if nothing was scheduled.

The moment the 24 hours pass, every key of that tenant stops working: any bearer-authed call answers 401 { error: "tenant_deleted" } right away. The deletion itself — keys, bindings, registered actions, approvers, webhook, evidence, plan, and activity all removed, and Memory's own state (bindings, taught fixes, the action ledger) reset to blank — happens within a few minutes of the 24 hours passing, or on your next request, whichever comes first.

Local mode

Run the pod on your own machine.

Local mode is available to Team customers. To test a server against this same contract without it, use the public conformance suite below.

Conformance

This contract isn't proprietary to one server.

A public conformance suite can be run against any memory server that implements the same bind/recall contract, not just this one — the same three-outcome shape (resolved / ambiguous / unavailable), the same never-guess posture on a gated action. Run it against Ogen, or against your own implementation.