Skip to content

Manage organisations through the API

The concept behind organisations is explained in Organisations and customers.

On this page you administer your own organisation and the organisations below it with an API key: create an organisation for a customer, read and rename organisations, and create or revoke keys. You use it when you set customers up from your own system rather than by hand.

Your key needs the management capability (Switch it on). You do the same with a mouse in Manage organisations in the console.

This capability does not grant access to a child organisation's files, prompts, skills or knowledge. Create a key there and use it to work with that organisation's data. Management permissions and data access are checked separately.

Switch it on

Your organisation has to permit management, and the key has to enable it. An administrator does both in the console (Permitted capabilities, Capabilities); a key that already holds management registers another one with it in the body:

{ "name": "Platform automation", "capabilities": { "management": {} } }

The capability takes no configuration — an empty object is the whole of it. Check what your own key holds with GET /key. Without management, every route on this page answers 404, the same answer an unknown path gives (Troubleshoot a 404).

The routes come in pairs. A path with no organisation in it means your own — GET /organisation, POST /keys, GET /admins — and a path naming one means an organisation below you: GET /organisations/{id}, POST /organisations/{id}/keys. Your own id spelled the long way answers the same as the short path.

Management permissions

Your key administers its own organisation and every organisation below it, and the two are not the same job. You run the organisations you were given to run; you may not promote, suspend, re-parent or delete the one you are.

Operation Your own organisation An organisation below you
read it (GET /organisation, GET /organisations/{id}) yes yes
name, timezone, data_retention_days, trace_retention_days, memory_retention_days, tracing_enabled, routing_config_id, assist_model, analysis_model, metadata yes; routing_config_id, assist_model and analysis_model unless key management is reserved yes
inheritable_keys (which keys of metadata the organisations below read) yes yes
computer_internet (whether its computers may reach the internet) yes; 403 while an organisation above has it off yes; the same
read its own document, only (GET /organisation/metadata, GET /organisation/metadata/effective) yes -- and needs no management at all —
read its effective document (GET /organisations/{id}/metadata/effective) — yes
status, permitted_capabilities, admin_restrictions, monthly_cost_cap_eur, monthly_cost_cap_until (the end of this month, for a cap for this month only), monthly_cost_cap_counts (branch or own: whether the cap counts the organisations below), monthly_cost_cap_counts_erased (true or false: whether a cap that counts the branch keeps counting what a deleted organisation used this month) 403 naming the field yes; a ceiling may not pass the ceiling of the organisation it sits directly under
parent_id, audit_retention_days, legal_hold 403 naming the field 403; Artific's alone, wherever the organisation sits
create an organisation under it yes yes
delete it 403 yes
list and read its keys yes yes
register, edit, rotate and delete its keys yes, unless key management is reserved yes
list its administrators yes yes
invite, re-invite and remove administrators 403 yes
read and write its policy (GET/PUT/DELETE /organisation/policy, GET/PUT/DELETE /organisations/{id}/policy) yes; writing unless key management is reserved yes
read its policy's published versions (GET /organisation/policy/versions, GET /organisation/policy/versions/{version}, and the same under /organisations/{id}/policy/versions) yes yes
try a policy document before writing it (POST /organisation/policy/decisions, POST /organisations/{id}/policy/decisions; Try a policy document) yes, unless key management is reserved yes
read and write its label vocabulary (GET/PUT/DELETE /organisation/label-vocabulary, GET/PUT/DELETE /organisations/{id}/label-vocabulary) reading with any key; writing unless key management is reserved yes
read and write its identity providers (GET/PUT /organisation/subject-issuers) yes; writing unless key management is reserved —
read, fill and empty its brand images (GET /organisation/assets, PUT/DELETE /organisation/assets/{slot}; Brand images) yes —
read and change its allowed hosts (GET/POST /organisation/allowed-hosts, DELETE /organisation/allowed-hosts/{host}; Allowed destinations) yes; changing returns 403 when key management is reserved —
read and write its email domains and sign-in providers (/organisation/email-domains, /organisation/sign-in-providers) yes (Set up your own sign-in provider with a key) —
read its lifecycle events (GET /control-events, GET /organisations/{id}/control-events) yes yes

The identity-provider and allowed-hosts rows are about your own organisation rather than about one below it: the providers whose token you may send (Build for people) and the hosts your own calls reach. What an organisation below you has there you read with a key you register in it. Its policy document you read and write from above, at /organisations/{id}/policy: send a whole document, as for your own, and start from GET /organisation/policy/default. The hosts an organisation below may reach are the permit rules of kind egress_host in that document; the rules of the same kind in your own document, and in the documents above you, limit it too (Policy). Name the version you read in If-Match when you write, so you do not overwrite a change someone saved after your read (Hand out, rotate and withdraw keys).

Versions and drafts of the policy. Every PUT and DELETE of a policy document is published at once as its next numbered version, and a key lists and reads the published ones at /organisation/policy/versions. A draft is a person's, on the management twin only: POST /tenants/{id}/policy/versions with document and "draft": true writes one, POST /tenants/{id}/policy/versions/{version}/publish puts it in force and DELETE /tenants/{id}/policy/versions/{version} discards it; the write and the publish name the published document's version in If-Match. POST /tenants/{id}/policy/versions/{version}/decisions tries a version with a key of the organisation and up to 50 questions, answered under that version and under the published document; it decides nothing for real and writes nothing. No key reads a draft, and no request is decided under one (Policy). A key tries a document it sends inline instead (Try a policy document before you write it).

A refusal on one of those fields is a problem document naming it, for example Only an operator or a parent organisation's administrator may change 'status'. Nothing is applied in part: a body that carries one field you may not set changes none of them.

Anything outside your reach answers 404. An organisation that is not yours and not below you is indistinguishable from one that does not exist, whichever route names it, so the tree cannot be walked for ids.

A descendant's usage, its audit trail and its routing configurations are within reach of the same key: see Usage, audit and routing configurations. The deployment's price list is not: it is Artific's, and your organisation reads a price and never writes one.

Read and rename your own organisation

curl -s "$CORE_URL/organisation" -H "Authorization: Bearer $CORE_KEY"

The answer is the organisation as the console shows it: its id and name, where it sits (parent_id), how it is kept apart (isolation) and which database it lives on (database_ref), the capabilities it permits, its caps, its retention windows and what its parent has reserved.

PATCH /organisation changes the fields of the table above that your own organisation may set:

curl -s -X PATCH "$CORE_URL/organisation" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"name": "Northwind Trading", "data_retention_days": 90}'

data_retention_days is the window over your organisation's own content — threads, responses, files, artifacts and memory records — and null keeps it for ever (Data retention). It is your organisation's decision about its own data, which is why your key may set it while it may not touch the window over the audit trail. routing_config_id names one of your organisation's own fallback configurations, or null for none (Fall back to another model).

tracing_enabled says whether Core records what it sends a model for your organisation, and trace_retention_days gives traces a shorter window of their own than data_retention_days (Traces). memory_retention_days gives memory records a window of their own, longer or shorter. Under it a record is counted from its last write or confirmation, so a fact you keep confirming stays, and an open commitment is never deleted; null leaves memory in data_retention_days, counted from when each record was created. assist_model names the model behind Describe what you need, or null for the deployment's default (Helper model). The same model answers every turn of your own helper that names no model. analysis_model names the model of improvement analyses, under the same rules, and never falls back to assist_model (The analysis model).

computer_internet says whether your organisation's computers may reach the internet (Computers). A new organisation below yours starts with it on when it counts for yours, and off otherwise; send "computer_internet": false when you create it to start it off anyway. Your organisation decides it for itself after that: your key may switch it on, and so may anyone above you. It counts only while every organisation above yours has it on too, so switching yours off switches it off for every organisation below you, and switching it on under an organisation that has it off is 403. Switching it off does not end what is running: a computer keeps its open network until its lifetime ends, the tunnels it has open through Core's proxy close at the proxy's next check (about every 30 seconds), and DELETE /computers/{computer_id} ends it sooner. The answer carries computer_internet_in_force, whether it counts right now, and computer_internet_available, whether this deployment offers such computers at all; while that is false the switch is kept and does nothing.

curl -s -X PATCH "$CORE_URL/organisation" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"computer_internet": true}'

Switch it on only when you accept what it means: code a model or a script runs there can send what it was given, files included, to any host on the internet.

timezone is an IANA zone name such as Europe/Amsterdam, in which your prompts' built-in date and time values are read when a request or schedule names no zone of its own (Timezone). A new organisation is set to UTC; send "UTC" to go back to it, because null leaves the zone as it is. A zone Core does not know is 422 on timezone.

metadata is an opaque object of your own, at most 16 KiB serialised — Core stores it, returns it and never reads it. It is where a platform built on Core keeps its own branding: colours, copy, whatever it needs; its images go to Brand images instead. Leave the field out to keep what it has, and send {} to clear it.

Every read of your organisation answers metadata_version beside it. Send it back in If-Match on the PATCH and Core refuses the whole update with 409 (reason_code: "stale_version") when someone changed metadata after your read, writing nothing; read again and repeat the change. Without the header the PATCH replaces what is stored:

curl -s -X PATCH "$CORE_URL/organisation" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -H 'If-Match: "3f9a..."' \
    -d '{"metadata": {"theme": {"primary": "#0a5"}}}'

Nothing of it flows down unless you say so. inheritable_keys lists the top-level keys of your metadata that the organisations below yours may read as their own — at most 32, each named once, replaced whole, [] for none. A key you do not list stays yours alone:

curl -s -X PATCH "$CORE_URL/organisation" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"metadata": {"theme": {"primary": "#0a5"}, "billing_note": "internal"},
         "inheritable_keys": ["theme"]}'

GET /organisation/metadata/effective answers an organisation's effective document in one call: its own keys, plus each key it lacks from the nearest organisation above it that lists that key and holds it. An organisation above that lists a key but does not hold it is passed over, and the one above that is asked.

{"metadata": {"logo_text": "Northwind", "theme": {"primary": "#0a5"}},
 "inherited_from": {"theme": null}}

inherited_from names, for every key that came from above, the organisation it came from — or null when your credential does not reach that organisation. A key of your own organisation never reaches above it, so on this route every value is null; read GET /organisations/{id}/metadata/effective with a key higher up to see the ids. A suspended organisation above still lends its keys; a deleted one lends nothing, and neither does anything above it.

Unlike everything else in this section, GET /organisation/metadata and GET /organisation/metadata/effective need no management: any key of your organisation reads them, and so does a subject token narrowed to one person, so a browser-side frontend can read its own branding with whatever credential it was handed (Build for people).

Create an organisation below you

An organisation's name is unique among the organisations under the same parent; creating, renaming or moving one into a name a sibling already has is refused 409. Duplicates that already exist stay until someone renames them.

POST /organisations names its parent, and the parent is any organisation you already reach — your own, or one further down:

curl -s -X POST "$CORE_URL/organisations" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"name": "Northwind Retail", "parent_id": "ten_4Kd9...",
         "permitted_capabilities": ["threads", "knowledge", "management"]}'
Field What to send
name what the organisation is called; the only required field
parent_id the organisation it comes to sit under. Leave it out, or send null, and you are asking for an organisation that sits under nothing, which is Artific's to create (403)
permitted_capabilities the ceiling for every key this organisation will have. It has to be inside the ceiling of the organisation you named as its parent; the first name outside it is refused with '<name>' is outside the capability ceiling of the organisation this one sits under.
isolation shared unless you were told otherwise. schema gives the new organisation a database schema and role of its own, and is yours to choose whatever your own organisation uses
database_ref do not send it. Naming a database is an operator's alone, and a key is never an operator; the new organisation defaults to the same physical database as the organisation you named as its parent. Sending the field is refused 403, even when it names that same database

The answer is 201 with the new organisation and a Location header. Its status is provisioning while Core prepares it; poll GET /organisations/{id} until it reads active. You may register keys in it straight away, and they answer calls once it is active (what the statuses mean). Until then a call with one of its keys answers 409 problem+json with the title Conflict and the detail The tenant is not ready., without a Retry-After header (the model-call routes carry the same message in the vendor's error object): try again a little later.

You administer what you just created from the moment this call answers.

status is always the organisation's own. Suspending an organisation also stops every organisation below it, and such an organisation keeps status: "active": read suspended_by_ancestor beside it. true means an organisation above it is suspended, so its keys answer 403 and its runs, schedules and knowledge work are stopped until that one is reactivated (Suspend). The field does not say which organisation it is.

permitted_capabilities is likewise the organisation's own stored ceiling, the value you write. A narrowing above does not change it: read capabilities_in_force beside it, the capabilities its keys and administrators may use now, which leaves out whatever an organisation above no longer permits, pseudonymisation and trust_envelope included. The field is read-only and does not say which organisation withholds a capability.

DELETE /organisations/{id} erases an organisation below you and everything it owns, in the background, with status on deleting until it is gone. While it is deleting, its keys answer the same 409; once it is gone, its keys are gone with it and answer 401. An organisation that still has live organisations under it is refused 409: erasure never cascades, so erase those first. Your own organisation is 403 — an organisation does not erase itself.

Work inside an organisation below you

These routes administer organisations, never their content. To read a child's knowledge, store a prompt there or send a turn on its behalf, register a key in that child and call as that key:

curl -s -X POST "$CORE_URL/organisations/ten_9Fb2.../keys" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"name": "Retail ingest", "capabilities": {"knowledge": {}}}'

The secret is in that response and nowhere else. A key of the child reaches the child's own data and nothing above it: GET /key names the child, and asking that key about the organisation that created it answers 404. Give it management as well and it administers the child and whatever sits below that, which is how an application nests one level further down.

Hand out, rotate and withdraw keys

/keys is your own organisation's, and /organisations/{id}/keys is a child's. Both sets carry the same seven operations: list, register, read, edit, rotate, create or replace the callback secret (POST …/keys/{key_id}/callback-secret) and delete.

curl -s -X POST "$CORE_URL/keys" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"name": "Checkout service", "capabilities": {"threads": {}}, "monthly_cost_cap_eur": "250.00"}'
  • The secret appears once. secret is in the registration and the rotation response and in no other answer; Core keeps its hash and the twelve-character secret_prefix. Lose it and rotate.
  • What you may switch on is your organisation's ceiling, management included. A capability outside it answers 409 with capability '<name>' is not permitted for this tenant, and so does one whose dependency is not enabled beside it: memory needs knowledge, runs needs threads, artifacts needs files, evaluations needs agents.
  • Rotation ends the previous secret at once. POST /keys/{key_id}/rotate answers with the new one; there is no window in which both work. Rotating the calling key itself is allowed, and it ends the key that made the call: the response is the last thing the old secret earns.
  • You may bring the secret yourself. Registration and rotation take an optional secret in the body ({"secret": "ack_..."} on the rotate route), which must have the exact shape Core issues: ack_ and 43 URL-safe base64 characters, 32 random bytes from a secure generator. Another shape answers 422 on secret, and so does a secret some key already has. The response carries it once, as it carries a generated one; leave the field out and Core generates it. Terraform uses this so a key's secret never enters its state.
  • PATCH /keys/{key_id} narrows a key, its own included, which is how an application takes capabilities away from itself before handing a turn something risky. Its own monthly cap is the exception: monthly_cost_cap_eur sent for the calling key itself answers 403 naming the field, null included, because a key never sets its own ceiling. Another key holding management, or an administrator in the console, sets it.
  • A policy write can name the version it read. A policy document is replaced whole, so of two writers that both read it and both write, the second removes the first's change and nobody notices. Every read of a key answers policy_version, and every read of an organisation's policy answers version; an organisation without a document has a version too. Send that value in the header If-Match on the write (PATCH of a key, PUT or DELETE of an organisation's policy) and Core refuses it with 409 when the stored document changed in between. Nothing is written: read again and repeat your change. Without the header the write replaces whatever is stored.
    • The problem body of that refusal carries reason_code: "stale_version". Switch on that field and not on the status: the same routes answer 409 for other reasons, and those bodies carry no reason_code.
    • A PATCH of a key that sends If-Match is refused whole when the key's document changed, also when its body carries no policy.
    • If-Match: * matches whatever is stored.
    • Adding or removing an allowed host changes the organisation's version: the list of hosts is part of the same document.
  • expires_at ends a key without deleting it. Set it on registration or on a PATCH, always in the future — a past instant answers 422. Send null on a PATCH to lift an expiry the key already has; leaving the field out of a PATCH keeps whatever it has. Past that instant the key is refused exactly as an unknown one is (401, the same body), whether presented as a secret or as a subject token minted from it. Rotation never resets it: rotation replaces the secret, not the lifetime.
  • A key never outlives the key that registers or edits it. Once your own key carries an expires_at, every key it creates or edits — its own included — must carry one too, no later than its own: you may shorten your own key's expiry, or set one where it had none, but not extend or clear it, and the same holds for any key you hand out. Sending a wider value answers 403, not 422 — the value is not malformed, an operator or an administrator may set the same one without being refused. A key with no expiry of its own puts no ceiling on anyone.
  • DELETE stops further calls with that key, and ends every subject token minted from it at once (A token for the browser). What it created stays with the organisation.

The console shows and does the same things (Keys); a key registered from the API is an ordinary row there.

Usage, audit and routing configurations

Seven routes under /organisations/{id}/... answer for an organisation exactly what /tenants/{id}/... answers for an administrator: GET .../usage, .../usage/summary, .../usage/caps, .../usage/by-subject, .../usage/by-key, .../audit/events and .../audit/events/export. Except for .../usage/by-key, they take the same parameters (key_id, from, to, interval=day on the summary; limit on the by-subject read) and answer the same shape as the routes in Usage and audit through the API. There is no separate path for your own organisation: /organisations/{your own id}/usage works just as well, since your key reaches its own organisation as one of its own roots (Management permissions).

GET .../usage/by-key has no counterpart under /usage: it says what each key cost. It takes only from and to; with neither named, the window is the current calendar month in UTC. items has one entry per key with usage in the window, carrying key_id, rows, cost_eur and unpriced, ordered by cost descending; a key that used nothing is not listed. keyless is the same entry for what came in without a key, such as what somebody started from the console, with key_id null; it is always present, at zero when there was nothing. The top-level cost_eur is the total of all entries together. An unpriced above zero means the amount is a floor.

curl -s "$CORE_URL/organisations/ten_9Fb2.../usage/summary?interval=day&from=2026-09-01T00:00:00Z&to=2026-10-01T00:00:00Z" \
    -H "Authorization: Bearer $CORE_KEY"

Routing configurations work on the same two paths: /routing-configs is your own organisation, /organisations/{id}/routing-configs one below it — list, create, read, update and delete, and list and read a configuration's published versions (.../{config_id}/versions), as /tenants/{id}/routing-configs offers an administrator, whose create and update also take draft: true and who alone publishes and discards a draft (Fall back to another model):

curl -s -X POST "$CORE_URL/organisations/ten_9Fb2.../routing-configs" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"name": "default", "chains": [{"primary": "gpt-5.6-sol", "fallbacks": ["claude-sonnet-5"]}]}'

Try a policy document before you write it

Before you PUT a policy document, ask how it would decide. Send the document you are about to write, the id of a key of that organisation, and up to 50 questions: a kind, a name, an action where the kind takes one, the labels the thing carries and, for a person, her attributes. The document is a whole one, as for a PUT: a kind it writes no rule over is refused, so start from GET /organisation/policy/default. The example below is cut short for that reason.

curl -s -X POST "$CORE_URL/organisation/policy/decisions" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"key_id": "key_01K...",
         "document": {"rules": [
             {"effect": "permit", "kind": "prompt", "match": {"names": ["private-*"]}},
             {"effect": "permit", "kind": "egress_host", "match": {"names": ["api.jansen.nl"]}}]},
         "questions": [{"kind": "prompt", "name": "private-welcome", "action": "use"},
                       {"kind": "egress_host", "name": "api.jansen.nl"}]}'

Each answer says, under draft, what the document you sent would decide, and under published what the document in force decides now, with allowed, the reason, whose document decided (organisation, key, ancestor or default) and the rule. A host is answered as an outbound call would be, and nobody dials it. For an organisation below you, the same body goes to POST /organisations/{id}/policy/decisions, with a key of that organisation as key_id.

The try refuses what the PUT would refuse, with the same answer: a label outside the organisation's vocabulary is 422 naming the same field, so is a live check naming a judge model the organisation cannot use, and a reservation of key management is the same 403. It writes nothing: no version, no event, no draft. A stored draft is a person's and only the management console tries one.

Reserved key management

The organisation above yours can reserve key management to itself (Reserved). It binds your key exactly as it binds your administrators: registering, editing, rotating and deleting on your own organisation answer 403 with This organisation's parent has reserved key management to itself., and listing and reading your keys are untouched. Setting routing_config_id is refused for the same reason, and so is any write on /routing-configs of your own organisation — it is all key configuration.

Below you nothing is withheld, because there you are the organisation above.

Reserving usage figures or the audit trail binds your key the same way: GET /organisations/{your own id}/usage and the other five answer 403 the moment your own organisation is the one reserved, and a plain 200 for an organisation below it. GET /usage, GET /audit/events and GET /key — your key asking for its own data without naming an organisation — are never affected by a reservation.

Who administers an organisation

GET /admins lists the people who administer your own organisation, with their status. That is all your own organisation offers: who administers it is decided from above it, so a key cannot invite someone into the organisation that issued it.

Below you, the whole set is yours:

Call What it does
GET /organisations/{id}/admins who administers that organisation. An empty page is a real answer
POST /organisations/{id}/admins invite someone by email. created says whether an account was made, invite_sent whether the mail went out
POST /organisations/{id}/admins/{user_id}/invite send the invitation again, and repair one that stopped half way
DELETE /organisations/{id}/admins/{user_id} take that person's administration of that organisation away. Their account is untouched

Someone listed with active: false accepted nothing yet, or was interrupted; re-sending the invitation is the repair (Admins).

Management events

GET /control-events lists what happened to your own organisation: its own creation, its renames and retention changes, its keys registered, updated, rotated and deleted, its administrators invited and removed, and its exports asked for and downloaded. Each event carries event, created_at, request_id and actor — your key's own id when a key did it, or operator:… when a person did.

An event about an organisation below you belongs to that organisation's trail, not to yours: the child you created finds its own creation on its trail. Read that trail from above with GET /organisations/{id}/control-events, which takes the same limit, cursor and order and answers the same shape; an organisation outside your reach is 404.

A parent that reserved your audit trail has not reserved this — an organisation's own lifecycle is the one record it always keeps. The same events are the console's Recent events and its management audit trail (Audit).

Brand images

Your organisation can keep up to 250 images in Core — a logo, a favicon, a picture for a sign-in page, a light and a dark logo for each theme of an embedded assistant — and show them anywhere, also to people who hold no key. Each image sits in a slot, a name you choose: a lowercase letter, then up to 31 lowercase letters, digits or hyphens (logo, favicon, sign-in). Core never reads the name.

Put an image in a slot by sending the file itself as the body. Core accepts PNG, JPEG and WebP up to 512 KiB, and decides the type from the file's own bytes, not from the Content-Type you send. SVG is not accepted.

curl -s -X PUT "$CORE_URL/organisation/assets/logo" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: image/png" \
    --data-binary @logo.png
{"object": "organisation_asset", "slot": "logo", "content_type": "image/png",
 "size_bytes": 18342, "sha256": "9f2c...e41a", "url": "/api/v1/assets/9f2c...e41a",
 "created_at": "2026-10-17T09:12:00Z", "updated_at": "2026-10-17T09:12:00Z"}

The answer is 200 whether the slot was empty or held an image, which the new one replaces. GET /organisation/assets lists every slot with the same fields and without the bytes, and DELETE /organisation/assets/{slot} empties one; it answers 204, also when the slot was already empty.

Answer Meaning What to do
413 the image is over 512 KiB make it smaller
415 the bytes are no PNG, JPEG or WebP convert the file; an SVG has to be rendered to PNG or WebP
422 the body is empty, or the slot name does not fit the pattern send the file, or use DELETE to empty a slot
409 the organisation already fills 250 slots, or it is still being created or deleted empty a slot first, or wait until the organisation is ready

The public address

url is where anyone reads the image, with no key and no token. Put your Core host in front of it, without /api/v1 from CORE_URL, which url already holds: https://core.example.com/api/v1/assets/9f2c...e41a. You can use that address directly in an <img> on a sign-in page, in a mail or in your own frontend, on any domain.

The address is named after the image's content, not after your organisation, so it says nothing about who uploaded it. That also means it changes whenever the image does: after you replace the logo, read the new url and use that. Browsers and other caches may keep an image for a year (Cache-Control: public, max-age=31536000, immutable), so never reuse an old address for a new picture; a new picture always comes with a new address. A reader that sends the ETag it got in If-None-Match gets 304 and no bytes.

An address answers 404 once no organisation holds that image any more: after you empty the slot or replace its image, unless another organisation happens to keep the same file.

Every image is your organisation's own: an organisation below yours inherits none of them and fills its own slots with a key of its own (Work inside an organisation below you). Filling and emptying a slot appear in your management events as tenant.asset_set and tenant.asset_deleted. The archive you take with you holds the images, and listing and emptying slots are tools on the MCP endpoint (list_own_assets, delete_own_asset); filling one is not, because the image is bytes and not a tool argument. A subject token gets 403 on these routes.

Deleting does not recall copies elsewhere. When you empty a slot, or your organisation is deleted, Core removes its own copy and the address stops answering. A browser, a mail service or a CDN that already fetched the image may keep showing its own copy for up to a year, and Core cannot reach those copies. Do not put anything in a brand image that you may need to withdraw quickly.

Take your data with you

An organisation that leaves takes an archive of everything Core holds for it. Ask for it, wait until it is ready, and download it:

curl -X POST "$CORE/api/v1/exports" -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" -d '{}'
# 202, with Location: /api/v1/exports/exp_…
curl "$CORE/api/v1/exports/exp_…" -H "Authorization: Bearer $KEY"
# status: queued, running, then ready
curl -o archive.zip "$CORE/api/v1/exports/exp_…/download" -H "Authorization: Bearer $KEY"

The archive is a zip. manifest.json names the format and its version, the rows in each file and what is deliberately not in it; organisation.json is your organisation's settings and policy document; tables/ holds one JSON Lines file per kind of record — conversations with their messages, responses, files and artifacts, knowledge, memory, prompts, skills, agents, tools, evaluations, schedules, the audit trail and usage — and blobs/ the bytes of every file. Your brand images are in it too, their details under control/ and the images themselves under files/. Secrets are never in it, encrypted or not: your connectors' and MCP servers' credentials are entered again at your new provider.

Send {"subject": "anna"} and the archive holds only what was written for that one person: that is how you answer an access or portability request (GDPR Articles 15 and 20). Below you, the same works on /organisations/{id}/exports. A subject token gets 403 on every one of these calls: the archive is asked for by the backend that holds the key.

The archive can be downloaded for export_retention_hours, 72 hours by default, as often as you need; after that the download answers 409 and you ask for a new one. Every download is a tenant.export_downloaded event, with who took it and how many traces the archive carried, in your organisation's management events (GET /control-events). If the organisation above you reserved the audit trail or usage, your archive leaves those rows out and says so in withheld.

A download that was cut off resumes where it stopped: send Range: bytes=<n>-, where <n> is the number of bytes you already have (curl -C - -o archive.zip ... does this), and the answer is 206 with the rest. Each resumed request is a download of its own, with its own tenant.export_downloaded event. A range that starts at or past the end of the archive answers 416 and is not recorded; curl -C - on a file that already downloaded completely gets that answer.

Asking, reading and listing are tools on the MCP endpoint too (create_own_export, get_own_export, list_own_exports); the download is not, because an archive is bytes and not a tool result.

Administer over MCP

Every operation on this page is also a tool on the key endpoint, with no configuration (Use Core from an MCP client). The tool is named after the operation: get_own_organisation, update_own_organisation, create_organisation, register_organisation_key, rotate_own_organisation_key, invite_organisation_admin, list_organisation_control_events. A key without management is offered none of them.

Inside a turn running on Core they are the same tools under a core__ prefix — core__create_organisation, core__register_organisation_key — so an agent can set an organisation up in one turn (Give an agent the platform). A registration answers with the new secret, and a tool result is part of the turn: the secret passes through the model and is kept with what the turn stored. Register keys from your own application when that matters.

Keep management to few keys

A key holding management grants what an administrator grants: any capability inside your organisation's ceiling, management among them. Such a key can therefore register a second key that administers as much as it does. That second key is an ordinary row in the console and its registration is in the trail, so nothing is hidden — but whoever holds the first secret decides it. Treat that secret as you would an administrator's password.

In practice that means one key for administration rather than management added to a key that is also serving turns, a monthly cap on it like any other key, and rotation the moment it has been anywhere it should not have been. Every one of its actions is in the trail above, under its own key id.