Skip to main content
This is a technical reference for developers. If you just want to connect an AI tool, you do not need this: use a connection instead.
Cortex has a small REST API for pushing content into brains from your own scripts and integrations. It is the same surface the automatic capture and webhook features use underneath.

Base URL and authentication

All endpoints live under https://api.oncortex.ai/api/v1. Authenticate with a service credential as a bearer token:
A credential is scoped to one brain and one access level. Writing endpoints need a read-and-write credential.

Rate limits

The API and the agent connection endpoint allow 240 requests a minute per credential, counted in fixed one-minute windows; requests with no credential are counted per client address instead. Over the limit, a request is refused with 429 and a Retry-After header giving the number of seconds until the window resets (60 when that cannot be worked out). A well-behaved client paces itself a little under the limit (the Cortex command line runs at 200 a minute), waits the number of seconds in Retry-After when it does see a 429 before retrying, and saves calls where it can, for example by fetching a brain’s page list once at the start of a bulk import rather than reading each page before writing it.

Push to your default brain

The quickest way to capture something: post to the default inbox, which lands in the credential’s brain (or, for a signed-in user, their single writable brain when they have exactly one).
Set externalId to a stable value so that re-sending the same thing does not create a duplicate.

Say where the document lives

Four optional fields record where a document lives, so the source record shows the link and later versions are linked as revisions: When you give a reference and no externalId, Cortex derives one as {provider}:{itemId}@{version}, so re-sending the same version is a no-op and a new version becomes a new item linked to the earlier one. The response of a GET on the new item carries revisionOf (the earlier item, its status and the pages it was filed to) and revisionDiff, a line diff of the two extractions.

Send the file itself

A JSON body can carry the document as base64 instead of text:
contentBase64 and content are mutually exclusive. The decoded size is capped at 10 MB on this route; larger files go as multipart form data, up to 50 MB, where the same four fields are accepted as form fields. The same rules apply to your agent’s upload_to_inbox tool. Optional query parameters:
  • ?claim=true marks the item as held by the agent that is calling: it arrives as in_progress with claimedBy set to agent, and Cortex’s own filer never picks it up; the caller is expected to write the pages and archive the item. Without it, the item arrives pending and Cortex files it under the workspace’s monthly ingestion limit. A held item nobody finishes within 30 minutes moves to needs_review. See how ingestion is paid for.
  • ?brain={slugOrId} names a destination brain explicitly (it must be one the credential can write to; a brain-scoped credential can only name its own brain).
  • ?path={folder} suggests where the content should file. It is recorded as a hint for the filing pass, not an order; the content itself can never choose its own destination.
  • ?partition={key} names the client (or site, or matter) in a partitioned brain. An unknown or archived key is refused with a 422 that lists the existing keys. Without it, the attendee emails decide, or the item waits as “needs a client”.
  • ?skill={name} (or skill in the JSON body or as a form field) pins the filing skill: ingest, ingest-meeting, ingest-doc, ingest-conversation or ingest-email. A deliberate upload that names its skill skips the automatic filer’s “is this worth filing” check and goes straight to that skill; leave it out for ambient captures (a session hook, say) so the check still runs. A name that is not a skill is refused with a 400 that lists the skills. The same skill argument is on your agent’s upload_to_inbox tool.
  • ?retain=none (or retain in the JSON body or as a form field) asks for the item’s body not to outlive its filing. See retention below.

Retention: ask for the body not to be kept

By default an inbox item’s body is kept: archived with the item, readable in the brain’s Sources, downloadable, and available to replays. Any caller can ask for the opposite, for content that should be read once and then gone: none is the only value that does anything; anything else leaves the item on the default. With it, the body exists only while the item waits to be filed and is destroyed the moment filing ends, whether the item is archived, rejected, deleted or closed unfiled after seven days. What survives is the stub: the filename, the source reference, the content hash, the external id, the pages it was filed to, the dates and the log. A kind: email contract is given retain: none at the door whether or not the caller asks, along with the ingest-email skill and the routing rules that go with mail. Afterwards, GETs on the item report retention (none or keep) and bodyDestroyedAt; GET /api/v1/brains/{brainId}/inbox/{itemId}/download answers 410 with “Not retained: the body of this item was destroyed when filing ended”; an agent’s get_source_file answers with an error instead of the file; and the item’s Sources record shows “Body not kept” with the link to the original in place of the download.

Work with a specific brain’s inbox

Uploads accept either a JSON body (filename, content or contentBase64, optional externalId, targetFolder, skill and the four reference fields above) or multipart form data for binary files, up to 50 MB, with canonicalUrl, provider, providerItemId, version and skill as form fields. The ?path={folder}, ?skill={name} and ?claim=true options work here too. Items carry status, claimedBy (agent, runner or null: who holds an item that is being filed), skillHint (the pinned skill, or null), sourceReference, supersedesItemId and supersededByItemId; a GET on one item adds revisionOf and revisionDiff.

Upload a file with a one-time token

This is the route a connected agent uses to get a file from its shell into the inbox without the bytes passing through the conversation. The agent asks Cortex for a token with its create_upload_token tool (Write on the brain), naming the filename, the brain, the filing skill and, optionally, where the document lives. The token starts with ut_, is good for one brain, one file, one use, for 15 minutes, and can do nothing else; Cortex stores only a hash of it. The tool returns a ready-made curl line:
The body is the file, either raw or as the first part of a multipart form (POST is accepted as well as PUT, and the token may be sent in an X-Upload-Token header instead). Up to 50 MB. The item is created held by the agent (in_progress, claimedBy: "agent") with the skill and source reference pinned, and the response carries what the agent needs next: An unknown or malformed token is a bare 401. A token that has already been used, or has expired, is a 410 with a message saying which. A refused upload (an empty body, say) is a 400.

Inbound mail

This is where forwarded mail arrives. Any brain can be given an inbound address under its Settings, then Ingest sources (Admin or Owner), shaped {brain-slug}-{24 random characters}@in.oncortex.ai. In a client brain, +key before the @ names the client. Mail sent to it reaches Cortex through a mail relay, which posts the raw message here and decides nothing:
An authenticated post always answers 200 with { "outcome", "itemId", "reason" }, whatever happened to the message, so that the relay never bounces anything and the address cannot be used to find out whether a brain exists: A post that arrives while no inbound secret is configured answers 503. Cortex builds the email contract from the message itself: who forwarded it (the forwarding headers, the envelope sender with the usual rewriting undone, then the From header, each checked against the brain’s verified forwarders), the original sender and subject (from the forwarded block for a message forwarded by hand, or from the headers for one forwarded by a rule), the participants, and the new message’s text with quoted history stripped. Attachments are dropped and their names carried through. The item that results has retention none like any mail item.

Pages, timelines and the graph

The page endpoints take a brain-scoped credential and the page path as ?path=. Writes need a read-and-write credential. An edit sent with a stale expectedVersion, or one that lands while someone else is saving, is refused with a 409 and the message “This page was changed by someone else. Reload and try again.”; read the page again and resend.

Reading a page in pieces

A single-page read (?path=) takes four more parameters, so a caller that found the right passage by search is not handed the whole page for it: Every read returns outline, the page’s headings in order. A page over 20,000 characters comes back cut at that point, with its outline and a note, unless whole=true is passed.

What a search hit carries

Each hit in a search response carries section, the text of the chunk that matched (up to 1,500 characters), outline, the page’s headings in order, and abstract, one line on what the page is about (its frontmatter description if it has one, else its first paragraph), alongside the path, title, snippet and score. Most questions can be answered from section alone; when more is needed, read the page with section={heading} rather than whole. The page index (get_index, and brief) carries the same abstract per page, so a caller can choose a page before reading it.

Is a brain still being fed

GET /api/v1/brains/{brainId}/health returns, in one JSON document, the brain’s health numbers, the inbox counts by status and by how items arrived (with the session hook’s transcripts picked out), the brain’s service credentials by label and role with their last use and expiry (never the key), its connected sources with their last run, the state of automatic filing, and findings: one row per check with status (fine, stale, missing or backlog), the fact, the expectation it was measured against and the action that fixes it, worst first. A read-only credential is enough. It is the report behind brain health and cortex health, so a scheduled job can poll it and alert when anything is not fine.

Tasks

Your brain’s task page as a list. A read-only credential can list; adding, completing, moving, deleting and archiving need read-and-write. An edit that lands while someone else is saving the page is refused with a 409; list again and retry.

Your personal brain

One brain per member per workspace is marked as that member’s personal brain: the default destination for an agent’s writes when it can reach several brains, and reported as personal in the brain list an agent sees. The mark belongs to the signed-in user, so this needs a user’s token (a connection or the command line), not a service credential. Marking a second brain moves the mark; any other brain carrying it for you loses it. The brain Cortex created for you when you joined carries it already. There is no Settings switch for this yet.

Partitions

In a partitioned brain the clients (or sites, or matters) are managed here as well as on the Clients page. Roles are the caller’s role on the brain: a service credential carries the role it was issued with, so a Write credential can push and assign, an Admin credential can manage partitions, and only an Owner credential or an owner’s connection can purge. Inbox listing takes ?partition={key} and ?unassigned=true. Search takes ?partition=, ?folder= and ?includeArchived=true (archived partitions are left out of results unless asked for).

Members

A brain’s access list can be managed here as well as on its Members page, so onboarding a person or an agent can be scripted. The caller needs Admin or Owner on the brain: a service credential counts with the role it was issued, so a read-and-write credential is refused with a 403 and can never widen anyone’s access. Only workspace members can be granted (invite them to the workspace first); one grant per person; the last Owner cannot be demoted or removed. Name the person by their user id or their email. A grant for someone who already has access is refused with a 409; a grant for someone who is not in the workspace, a demotion of the last Owner, or an unknown role is a 400 with the reason. There is no agent tool for any of this, on purpose: an agent in a conversation cannot add or remove members.

Receiving from third-party tools

If instead you want an outside tool to push into Cortex (rather than calling the API from your own code), use the webhook route, which is designed for that and has its own authentication.