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.
Base URL and authentication
All endpoints live underhttps://api.oncortex.ai/api/v1. Authenticate with a service credential as a bearer token:
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 aRetry-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).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=truemarks the item as held by the agent that is calling: it arrives asin_progresswithclaimedByset toagent, 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 arrivespendingand Cortex files it under the workspace’s monthly ingestion limit. A held item nobody finishes within 30 minutes moves toneeds_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}(orskillin the JSON body or as a form field) pins the filing skill:ingest,ingest-meeting,ingest-doc,ingest-conversationoringest-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 sameskillargument is on your agent’supload_to_inboxtool.?retain=none(orretainin 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 itscreate_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:
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 carriessection, 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 aspersonal 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.