Skip to main content

Create a space

space_id is not a settable field. Sending one is rejected with 422. So is a space_type that is not on the list above — a request asking for a type Anona does not have is refused rather than quietly creating a plain space. Response 201 Created
space_type in the response is what was applied, not what you asked for. It is null when nothing was applied: when you sent nothing, when you sent blank, and also when a type was requested and could not be applied. The space is created and usable either way — applying a type never fails the create — so this field is how you tell a seeded space from a plain one without a second call. If it comes back null after you asked for a type, either set the pieces yourself or delete the space and create it again.
The space_id is the name you chose. There is no separate generated id. The name you pass on create is the identifier every other endpoint takes, so pick it deliberately. Every memory operation requires it.
A name containing characters that are not URL-safe produces a space_id containing them too. That is legal, but the id then has to be percent-encoded everywhere it appears in a path: my space becomes /v1/spaces/my%20space. Sticking to letters, digits, - and _ avoids the problem entirely.

Space types

A new space is an empty box. A space type fills some of it in at creation: what the space is for, what its extraction pass should keep, how it weighs what it is told, and two standing questions worth keeping an answer to. Pass one as space_type on create. That is the only moment a type can be applied — there is no endpoint that converts an existing space, because everything a type sets is already settable on its own. A dial a type does not move stays at its default of 3. The dials are moved only where the use case really differs, and they change how a space weighs what it is told when it synthesizes — never what a plain retrieve returns. See space profile.

What a type sets, and how to change it

Three things, all of which stay editable afterwards. A space type is a starting point, not a mode. Nothing is locked, nothing behaves differently later because of the type you chose, and a space you seeded is the same kind of space as one you configured by hand. It writes configuration only: no memories are created, and your own writes are unaffected. space_type is also not stored as a property of the space, and it is not a field of the read responses at all: GET /v1/spaces/{space_id} and GET /v1/spaces omit it rather than reporting it null, because there would be nothing to report — what a type did is the mission, the guidance and the models it wrote, and each of those reads back on its own endpoint.
Both of those PUTs are full replaces. Read the current settings and send them back with your edit to keep what the type wrote; sending only the one field you care about clears the rest.

The starting models are empty on purpose

A seeded space’s two models have no content until the space has memories worth reading. This is expected, not a failed apply.
Each one is created with mode: "delta" and refresh_on_new_memories: true, so it writes itself the first time newly recorded memories are consolidated, and rewrites itself as the space learns more. Until then GET /v1/spaces/{space_id}/models lists them with content: null. Generating an answer at creation would have been worse than empty. The space is seconds old and holds nothing, so the pass would have spent your credits to store “I have no information about this” as the model’s content — and that sentence then reads as the answer, to you and to any agent reading the model, until something replaces it. An empty model is honest about what the space knows; a confident empty one is not. Record something and let it consolidate, then read the models back. To fill one sooner, POST /v1/spaces/{space_id}/models/{model_id}/refresh writes it now; that call is billed like reason.

What each type starts with

Model ids are stable, so a seeded model can be refreshed, edited or deleted by id.

List the space types

No API key and no credits: it is a static catalogue and it names no customer data. Read it rather than hardcoding the list, which goes stale the first time a type is added or reworded.
Response 200 OK
One entry per type, in the order a picker should show them, with blank last. mission is the exact text the type writes to the space profile — worth reading before you choose, and worth editing afterwards if it is nearly right. disposition carries only the dials that type moves, and models only their names and questions; the answers are written in the space itself.

List spaces

Response 200 OK

Get space details

Response 200 OK

Delete a space

Response 204 No Content.
Deleting a space permanently destroys the space and every memory in it. This cannot be undone.

Space members

Spaces can optionally have named members within your organization.
The membership endpoints are control-plane calls: they need a dashboard session with the owner or admin role, not an API key. An API key gets 401 session_required; a developer or viewer session gets 403 insufficient_role.
The org-wide state exists for spaces that pre-date membership: creating a space through the API or dashboard automatically writes a member row for the creator, so a new space is member-only from the moment it exists.

Invited over Slack

A space bound to a Slack channel also has people who were offered it over Slack and have not taken it. They have no Anona account yet, so they are listed separately rather than as half-built members:
Response 200 OK
state is pending (asked, no answer yet), declined (said no) or expired (fourteen days passed). Anyone who accepted is absent, because they hold a real membership row and the member listing above already names them. There is no email field and there will not be one: the Slack roster is read through users:read, never users:read.email. display_name is null for a roster synced before names were stored.

Rules

A rule is a standing instruction the space must follow whenever it answers a question. It is not a preference and it is not a tone setting — those are the disposition dials on the space profile, which only shift how an answer is worded. A rule applies to every answer the space gives, and it overrides what the memories say:
A rule
That holds even when a memory says the deal is done. Rules shape every answer Anona synthesizes for you: reason, asking about one end user, and the answers memory models keep. They do not touch retrieve or the drop-in proxy: a search returns the memories it matched, and the proxy hands your own prompt to your own model with memories added to it. Whatever shapes those answers is yours.
Rules are owner-only and free. They consume no credits, deliberately — an organization out of credits must still be able to switch off a rule that is producing bad answers.

List the rules on a space

Response 200 OK
Highest priority first. Inactive rules are listed too: they are stored and still editable, they are simply not applied.

Add a rule

Response 201 Created, carrying the stored rule in the shape above.

How many rules a space can have

A space may have 25 active rules at once. A create or an activation that would make it 26 is refused with 422 too_many_active_rules. The cap counts active rules only, so switching one off always frees its slot without deleting anything:
Every active rule is written into every answer the space composes, so the cap is what stops a space from spending more of an answer on its own rules than on its memories. Well under it is the better place to be: a rule that is not worth the room is worth switching off.

Edit or remove a rule

PATCH is a partial edit — only the fields you send change, and the rest are left exactly as they are. That is deliberate: switching a rule off or lifting its priority should never mean resending the text of something enforced on every answer, where a typo is expensive. A PATCH that names no field is refused with 422 empty_update rather than treated as a no-op.
Response 204 No Content. Answers already given are unchanged; later ones stop obeying it.

Which rules shaped an answer

POST /v1/reason returns rules_applied, naming the rules that actually applied to that answer:
It carries the id and the name, and deliberately not the rule’s text. The text is your own configuration — you can read it once from GET /v1/spaces/{space_id}/rules — while this rides every answer, and a space may hold 25 active rules of 2,000 characters each. An empty list is a real finding, not a missing field: it says no rule applied. That is what separates the memories are wrong from a rule said otherwise, which need opposite fixes.

Scoping a rule

tags narrow when a rule applies. Most rules want none.
  • A rule with no tags applies to every question the space is asked. This is the usual case, and the one to reach for.
  • A tagged rule applies only to a question filtered to matching tags with tag_groups. A question that names no filter is answered under the untagged rules alone, so a tagged rule can never reach a question that did not ask for it.
  • A question scoped to one end user (user_id, agent_id, session_id) is likewise answered under the untagged rules alone.
A tag starting with anona: is refused with 422 reserved_tag. That namespace carries scope, and a rule able to claim it could pin itself to one end user’s questions.

From the SDKs

Both clients have async_ twins in Python and are promise-based in TypeScript. update_rule / updateRule send only the fields you pass.

In the SDKs

Listing, listing types, creating, fetching and deleting a space are wrapped in both clients (Python also ships async_ variants). Membership management is dashboard/session-only, so it is deliberately in neither API-key SDK.

Error responses

There is no 429 here. The space endpoints are control-plane calls: they spend no credits and are not rate-limited, so they carry none of the X-RateLimit-* headers either. See Usage. See the full error reference.

Next steps

Memories API

Store and manage memories.

Errors

The complete error code reference.