Read Sentra's organization memory from any agent or service: meetings, email threads, entities, fact chunks with citations, relationships, and the memory agent.
Basics
Host. https://api.sentra.app. Every path starts with /external/v1.
Authentication. Send your key as Authorization: Bearer sk_sentra_… on every request. Generate a key in the Sentra app under Settings, API Access. Keys inherit the owner's access, so the API never returns anything that person could not see in the app.
Rate limit. 120 requests per minute per key, shared with the MCP server. Requests over the limit receive 429.
Machine-readable spec. OpenAPI document. Prefer MCP for agents: connect the Sentra MCP server.
Entities
List Entities
GET /external/v1/entities
List entities (people, companies, projects, etc.) the API key's user can see, grouped by type. Visibility is per-user: two keys in the same org may return different lists. Use entity_type to filter by type and search to find entities by name; use cursor for pagination. search is case-insensitive and matches both the entity's name (canonical_name) and its aliases (other names, spellings and abbreviations recorded for the entity). With search, the entities in each group are ranked best match first, and each one carries match_type and matched_text (the name or alias that matched). The match types, from strongest to weakest:
- substring: the search text is part of the name or of an alias.
- compact: the search text matches when spaces and punctuation are ignored (for example "AcmeCorp" and "Acme Corp").
- token: every word of the search text is in the name or in one alias, in any order (for example "Smith John" and "John Smith").
- fuzzy: the name or alias is similar to the search text but not the same (for example another spelling). A fuzzy match is a CANDIDATE, not a confirmed match: it is often a different entity with a similar name (for example "Daniel" and "Danielle"). Before you use a fuzzy match, read its chunks and confirm that they are about what you are looking for. Fuzzy matches always rank below the other match types. There are no fuzzy matches when the search text contains a digit.
With search, when total_is_lower_bound is true, the search stopped early and there can be more matches than total_entities: use cursor for the next page. Each returned entity carries a UUID id that you must pass verbatim to get_entity_chunks / get_entity_relationships / fetch entity:<id>. Each entity carries a status you MUST respect: confirmed means it is likely to be a real, valid entity. proposed means the OPPOSITE, it is an UNCONFIRMED, hypothetical suggestion the org has NOT yet accepted (e.g. a proposed project is NOT a real project on the roadmap; it is a candidate a human still has to approve). NEVER present a proposed entity as a confirmed fact: if you mention one, you MUST explicitly label it as proposed / unconfirmed / not yet accepted, and prefer confirmed entities as the source of truth. rejected means a human explicitly declined it, do not rely on it.
| Parameter | In | Type | Description |
|---|
| entity_type | query | string[] | Filter by entity type slug. Repeat entity_type to pass multiple types. |
| updated_since | query | string | Only include entities with at least one visible chunk at or after this timestamp. A chunk's time is when its source content happened (a message, a meeting), not when Sentra ingested it. A synced document's chunks carry its last-modified time when its source reported one (so a newly synced older document can add chunks before the bound); otherwise they carry the time Sentra ingested the document. **Must be timezone-aware** (include Z or an explicit offset like +00:00); naive datetimes are rejected with an actionable error. |
| limit | query | integer | Optional page size for cursor pagination. |
| cursor | query | string | Opaque cursor returned by the previous entities response. |
| search | query | string | Optional case-insensitive search on the entity's canonical_name and its aliases: a substring, the same text with spaces and punctuation ignored, all words in any order, or a similar spelling (fuzzy), ranked best match first in each group. Whitespace-only values are treated as no filter. Mirrors the in-app /entity-memory/entities route and the desktop Spotlight typeahead so the public API, the in-app surface, and the Spotlight share one search contract. |
List Important Entities
GET /external/v1/entities/important
Return the user's important entities (projects, companies, topics, and the like, not people) as an unranked set: each item carries the entity id, type, and name, and the items are sorted by name. The set carries no scores and no ranks by design, the underlying scores are computed from org-wide data, so exposing them would leak activity the caller cannot see. Visibility is per-user: a user sees only entities they can access. This is a bounded top-N read: count is the number of returned items, and there is no total or pagination cursor.
| Parameter | In | Type | Description |
|---|
| limit | query | integer | The maximum number of entities to return (1-100). |
| entity_type | query | string[] | Restrict to these entity type slugs. |
List Entity Types
GET /external/v1/entities/types
Return the org's configured entity type slugs and display names. Cheap lookup to resolve a slug from list_entities.
Get Entity Chunks
GET /external/v1/entities/{entity_id}/chunks
Return ACL-filtered fact chunks for one entity. Prefer this over get_meeting for cross-meeting questions, chunks are concise and include source citations. entity_id must be a UUID returned by list_entities (not a display name or slug); malformed values are rejected with an actionable error.
| Parameter | In | Type | Description |
|---|
| entity_id (required) | path | string | |
| limit | query | integer | Page size; max 100. |
| offset | query | integer | |
| source_types | query | string[] | Optional filter restricting chunks to a subset of source types. Repeat source_types to pass multiple values. Omit to return rows from every source the caller can see. Unknown values are rejected with an actionable error. Allowed values: ['basecamp', 'chat_update', 'clickup', 'confluence', 'email', 'erpnext', 'file', 'github', 'google_chat', 'hubspot_call', 'hubspot_company', 'hubspot_contact', 'hubspot_custom_object', 'hubspot_deal', 'hubspot_lead', 'hubspot_line_item', 'hubspot_meeting', 'hubspot_note', 'hubspot_quote', 'hubspot_task', 'hubspot_ticket', 'jira', 'linear', 'manual', 'meeting', 'personal_email', 'slack', 'teams_channel', 'teams_chat', 'zendesk']. |
Get Entity Relationships
GET /external/v1/entities/{entity_id}/relationships
Return extracted links and co-attendance for one entity. Co-attendance defaults to the last 180 days. entity_id must be a UUID returned by list_entities; malformed values are rejected with an actionable error.
| Parameter | In | Type | Description |
|---|
| entity_id (required) | path | string | |
| max_links | query | integer | Page size for LLM-extracted link rows. Pagination is cursor-based: the response carries next_links_cursor when more rows are available, pass it back as links_cursor to fetch the next page. |
| links_cursor | query | string | Opaque cursor returned as next_links_cursor on a previous response. Omit to fetch the first page. Malformed cursors are rejected with an actionable error. |
| max_counterparts | query | integer | |
| max_events_per_counterpart | query | integer | |
| co_attendance_since | query | string | Inclusive lower bound on source-event time for co-attendance aggregation. **Must be timezone-aware** (RFC 3339 with Z or an explicit offset like +00:00); naive datetimes are rejected with an actionable error. Defaults to 180 days before co_attendance_until (or before now if co_attendance_until is also omitted). Does NOT affect extracted links. |
| co_attendance_until | query | string | Inclusive upper bound on source-event time for co-attendance aggregation. **Must be timezone-aware** when specified; naive datetimes are rejected with an actionable error. Omit for no upper bound. |
| source_types | query | string[] | Optional filter restricting links AND co-attendance to a subset of source types. Repeat source_types to pass multiple values. Omit to return rows from every source the caller can see. Unknown values are rejected with an actionable error. Allowed values: ['basecamp', 'chat_update', 'clickup', 'confluence', 'email', 'erpnext', 'file', 'github', 'google_chat', 'hubspot_call', 'hubspot_company', 'hubspot_contact', 'hubspot_custom_object', 'hubspot_deal', 'hubspot_lead', 'hubspot_line_item', 'hubspot_meeting', 'hubspot_note', 'hubspot_quote', 'hubspot_task', 'hubspot_ticket', 'jira', 'linear', 'manual', 'meeting', 'personal_email', 'slack', 'teams_channel', 'teams_chat', 'zendesk']. Co-attendance is only populated by the meeting adapter today, so a filter that excludes meeting returns an empty co_attendance list. |
Swimlanes
List Swimlanes Endpoint
GET /external/v1/swimlanes
List the organization's swimlanes (org-level project views over memory), each with the API key user's cached live-summary when one exists. Reading the list does not trigger generation.
Create Swimlane Endpoint
POST /external/v1/swimlanes
Create a swimlane. title and description are required; goal, owner_user_id, and stakeholder_user_ids are optional. Owner and stakeholders are Sentra user ids, resolve names to ids with list_org_members first. Creation triggers asynchronous entity discovery.
Takes a JSON request body.
Get Swimlane Endpoint
GET /external/v1/swimlanes/{swimlane_id}
Return one swimlane's own attributes (title, description, goal, owner, stakeholders, discovery state) by id. A person with is_pending_invite: true is an invited person who has not joined; they are not returned by list_org_members and stay on the swimlane when you send their id again in the same list.
| Parameter | In | Type | Description |
|---|
| swimlane_id (required) | path | string | |
Update Swimlane Endpoint
PATCH /external/v1/swimlanes/{swimlane_id}
Update a swimlane's user-authored fields. Every field is optional but at least one must be sent. stakeholder_user_ids accepts [] to clear its Sentra-user subset (external, unresolved people are preserved). Resolve ids with list_org_members. Changing title/description/goal re-triggers discovery. A person with is_pending_invite: true is an invited person who has not joined; they are not returned by list_org_members and stay on the swimlane when you send their id again in the same list. A suggested swimlane must keep at least one active (joined) owner or stakeholder; an update that leaves no active one is rejected. Send owner_user_id: null to clear the owner.
Takes a JSON request body.
| Parameter | In | Type | Description |
|---|
| swimlane_id (required) | path | string | |
Delete Swimlane Endpoint
DELETE /external/v1/swimlanes/{swimlane_id}
Archive (soft-delete) a swimlane for the WHOLE organization, this is destructive and affects every member, not just you. You must pass confirm=true to proceed; a call without it is rejected.
| Parameter | In | Type | Description |
|---|
| swimlane_id (required) | path | string | |
| confirm | query | boolean | Must be true to perform this destructive, org-wide archive. Any other value is rejected. |
List Swimlane Activity Endpoint
GET /external/v1/swimlanes/{swimlane_id}/activity
Return a swimlane's Event Feed: the notable, real events that happened on the project, each typed (deploy_release, decision, milestone, blocker, stall, ...) and badged hard (observed) or soft (inferred). An empty feed is NOT authoritative ('nothing happened') until it has been generated for you: check state, and when it is empty or stale call refresh_swimlane_activity to generate it, then re-read. The feed is returned whole, so limit/cursor are ignored and next_cursor is always null, do not paginate. This read returns the latest cached result immediately with a state and freshness fields (is_stale, generated_at); it never starts generation. When is_stale is true (or no result exists yet), call the matching refresh_* operation to regenerate, then re-read. While a refresh you triggered is in flight, state is generating, re-read until it settles.
| Parameter | In | Type | Description |
|---|
| swimlane_id (required) | path | string | |
| limit | query | integer | |
| cursor | query | string | |
Get Live Summary Endpoint
GET /external/v1/swimlanes/{swimlane_id}/live-summary
Return the API key user's live summary for a swimlane (the generated, ACL-filtered latest updates). This read returns the latest cached result immediately with a state and freshness fields (is_stale, generated_at); it never starts generation. When is_stale is true (or no result exists yet), call the matching refresh_* operation to regenerate, then re-read. While a refresh you triggered is in flight, state is generating, re-read until it settles.
| Parameter | In | Type | Description |
|---|
| swimlane_id (required) | path | string | |
Get Attention Endpoint
GET /external/v1/swimlanes/{swimlane_id}/attention
Return the API key user's active attention items for a swimlane (proactive assessments that the swimlane needs attention). Each item has a type (risk | contradiction | other); typed items (risk / contradiction) also carry an optional evidence payload (why it matters, a primary quote, the event trail, a suggested next step, and, for a contradiction, the two conflicting sides) and a confidence score. confidence is the detection ensemble's vote share (0-1), NOT the probability the risk is real, surface the evidence, do not read it as a truth likelihood. This read returns the latest cached result immediately with a state and freshness fields (is_stale, generated_at); it never starts generation. When is_stale is true (or no result exists yet), call the matching refresh_* operation to regenerate, then re-read. While a refresh you triggered is in flight, state is generating, re-read until it settles.
| Parameter | In | Type | Description |
|---|
| swimlane_id (required) | path | string | |
Get Whats Next Endpoint
GET /external/v1/swimlanes/{swimlane_id}/whats-next
Return the API key user's forward-looking items for a swimlane. This capability is in active development: an empty set is NOT authoritative ('nothing coming up') until the finder ships. This read returns the latest cached result immediately with a state and freshness fields (is_stale, generated_at); it never starts generation. When is_stale is true (or no result exists yet), call the matching refresh_* operation to regenerate, then re-read. While a refresh you triggered is in flight, state is generating, re-read until it settles.
| Parameter | In | Type | Description |
|---|
| swimlane_id (required) | path | string | |
Retry Discovery Endpoint
POST /external/v1/swimlanes/{swimlane_id}/discovery/retry
Re-trigger entity discovery for a swimlane (the explicit 'try again' for a failed discovery). This starts background work and uses compute; do not call it repeatedly. Returns the swimlane with discovery moved to initializing.
| Parameter | In | Type | Description |
|---|
| swimlane_id (required) | path | string | |
Refresh Live Summary Endpoint
POST /external/v1/swimlanes/{swimlane_id}/live-summary/refresh
Force regeneration of the API key user's live summary for a swimlane, bypassing the cache. This starts new AI generation and uses compute (latency and cost); do not call it repeatedly. It returns immediately with state=generating; re-read the matching read operation until state settles.
| Parameter | In | Type | Description |
|---|
| swimlane_id (required) | path | string | |
Refresh Attention Endpoint
POST /external/v1/swimlanes/{swimlane_id}/attention/refresh
Force regeneration of the API key user's attention items for a swimlane. This starts new AI generation and uses compute (latency and cost); do not call it repeatedly. It returns immediately with state=generating; re-read the matching read operation until state settles.
| Parameter | In | Type | Description |
|---|
| swimlane_id (required) | path | string | |
Refresh Whats Next Endpoint
POST /external/v1/swimlanes/{swimlane_id}/whats-next/refresh
Force regeneration of the API key user's forward-looking items for a swimlane. This starts new AI generation and uses compute (latency and cost); do not call it repeatedly. It returns immediately with state=generating; re-read the matching read operation until state settles.
| Parameter | In | Type | Description |
|---|
| swimlane_id (required) | path | string | |
| viewer_now | query | string | The caller's current local time as an RFC 3339 timestamp with offset (e.g. 2026-01-02T09:00:00-08:00). Pass it on a refresh so relative time phrases are correct for you; when omitted the server uses its own clock. |
Refresh Activity Endpoint
POST /external/v1/swimlanes/{swimlane_id}/activity/refresh
Force regeneration of the API key user's Event Feed for a swimlane. This starts new AI generation and uses compute (latency and cost); do not call it repeatedly. It returns immediately with state=generating; re-read the matching read operation until state settles.
| Parameter | In | Type | Description |
|---|
| swimlane_id (required) | path | string | |
| viewer_now | query | string | The caller's current local time as an RFC 3339 timestamp with offset (e.g. 2026-01-02T09:00:00-08:00). Pass it on a refresh so relative time phrases are correct for you; when omitted the server uses its own clock. |
Dismiss Attention Endpoint
DELETE /external/v1/swimlanes/{swimlane_id}/attention/{item_id}
Dismiss one attention item for the API key user (permanent, per-viewer). The item_id is valid only for the generation it was read from, it rotates on every regeneration, so read attention items immediately before dismissing. The response reports dismissed: true when a matching active item was dismissed, false when the id matched nothing (unknown or already-settled).
| Parameter | In | Type | Description |
|---|
| swimlane_id (required) | path | string | |
| item_id (required) | path | string | |
Resolve Attention Endpoint
POST /external/v1/swimlanes/{swimlane_id}/attention/{item_id}/resolve
Resolve one active attention item for the API key user (permanent, per-viewer), the viewer has acted on it and closed it out. Any active item can be resolved; a risk or contradiction the viewer has handled is the primary case. The item_id is valid only for the generation it was read from, it rotates on every regeneration, so read attention items immediately before resolving. The response reports resolved: true when a matching active item was resolved, false when the id matched nothing (unknown or already-settled).
| Parameter | In | Type | Description |
|---|
| swimlane_id (required) | path | string | |
| item_id (required) | path | string | |