whoami
Who this credential acts as: user id, name, email, org id, role, the teams the caller belongs to, and the permission grants the credential actually carries. Start here to find out what you are allowed to do.
Telos progressively discloses its action catalog: search capabilities, inspect one exact schema, then read or write. Agents avoid loading every model field up front; authorization, attribution, and service behavior stay identical to REST.
POST /api/mcp speaks the Streamable HTTP MCP transport. Auth is the same bearer token you use for REST: Authorization: Bearer telos_live_… (or an OAuth app token, telos_oat_…, limited to its granted scopes). Rate limits apply as on REST, and every action is gated by the calling key's capabilities — a read-only key does not discover unavailable writes, and a direct attempt returns a permission error.
https://www.telos-app.com/api/mcpThree steps to wire Telos into an MCP client. The same endpoint and token work for any client that speaks the Model Context Protocol — pick yours in step 2.
Issue a key from inside the app at Settings → API Keys. Keys start with telos_live_ and carry the role that decides which tools and fields you see — copy it now, it's shown only once. The endpoint is the same origin you use Telos on, with /api/mcp appended — there's no per-account host. For this workspace that's https://www.telos-app.com/api/mcp, which is what the snippets below use.
Select your client. Most speak Streamable HTTP natively, so you give them the endpoint and token directly — only Claude Desktop's config launches local commands, so it bridges through mcp-remote.
Register the endpoint with one CLI command — run it from any project, or add --scope user to make it available everywhere.
claude mcp add --transport http telos \
https://www.telos-app.com/api/mcp \
--header "Authorization: Bearer telos_live_…"Add this to claude_desktop_config.json (Settings → Developer → Edit Config), then fully restart the app. The token lives in env because mcp-remote splits --header on the first space, which would otherwise break Bearer <token>.
{
"mcpServers": {
"telos": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://www.telos-app.com/api/mcp",
"--header",
"Authorization:${TELOS_AUTH}"
],
"env": {
"TELOS_AUTH": "Bearer telos_live_…"
}
}
}
}Add an [mcp_servers.telos] table to your Codex config. It takes the name of an env var holding the token, so export TELOS_TOKEN=telos_live_… in the same shell.
# ~/.codex/config.toml
[mcp_servers.telos]
url = "https://www.telos-app.com/api/mcp"
bearer_token_env_var = "TELOS_TOKEN"Add a workspace .vscode/mcp.json. The ${input:telos_token} reference makes VS Code prompt for the key the first time the server starts, then store it securely.
// .vscode/mcp.json
{
"servers": {
"telos": {
"type": "http",
"url": "https://www.telos-app.com/api/mcp",
"headers": { "Authorization": "Bearer ${input:telos_token}" }
}
},
"inputs": [
{
"type": "promptString",
"id": "telos_token",
"description": "Telos API key",
"password": true
}
]
}In a client, the Telos tools should now appear in the tool list. To check the endpoint and token directly, send the protocol handshake with curl — a 200 with a result confirms both connectivity and auth; a 401 means the key is missing or invalid:
curl -sN https://www.telos-app.com/api/mcp \
-H 'Authorization: Bearer telos_live_…' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'Once connected, ask the agent to call whoami to confirm which user and role the key resolves to. Then ask it to use find_tools for the job you want done.
MCP clients initially load only five small schemas. Domain actions stay behind a catalog and disclose their field names only when the agent selects one. The normal sequence is:
find_tools({ query: "create an insight", mode: "write" })
inspect_tool({ name: "create_insight" })
call_write_tool({ name: "create_insight", arguments: { … }, idempotencyKey: "meeting-42-insight-1" })Auto-rendered from the source registry. Find these actions with find_tools; their schemas appear only after inspect_tool.
Who this credential acts as: user id, name, email, org id, role, the teams the caller belongs to, and the permission grants the credential actually carries. Start here to find out what you are allowed to do.
Ranked title and body matches across visions, strategies, objectives, metrics, opportunities, tasks, customers, insights, teams, and users, each with its ref and parent. Only the entity types you hold the read capability for are searched, so a narrow key sees a narrow result set. Needs at least 2 characters and returns at most 40 results; use the per-entity list actions to page a single type.
List strategic visions with cursor pagination: the company vision first, then product visions. Optionally filter by kind (company, product).
Get a vision by UUID or by its per-org ref (VIS-12).
Version snapshots of a vision, newest first, with cursor pagination. Every name or narrative edit creates one.
The objectives anchored to this vision, with cursor pagination. Returns objective rows, so it needs objective:read as well as vision:read.
Blast radius of a vision: how many objectives, opportunities and tasks hang off it. Objectives link by FK; opportunities and tasks by their visionPath prefix. 404 when the vision is not one of yours — zero children and no such vision are different answers.
List strategic objectives with cursor pagination. Filter by vision, by the metric they are anchored to, by DRI (pass your own user id from whoami for 'my objectives'), or by status.
Get an objective by UUID or by its per-org ref (OBJ-12).
The complete set of opportunities linked to this objective, with their link metadata, alphabetically by title. Not paginated: it is a roll-up of one objective's trace, ordered on a value derived after the query, so there is no stable cursor to page it on. Use `list_opportunities` for a paged walk of the org's opportunities.
Every objective in the org as a column, plus the objectives and non-cancelled opportunities that claim to move them as rows. Each row carries `serves`: one entry per objective it advances, with kind 'primary' (this is what the row is really for) or 'supporting'. A primary objective→objective link is also the nesting parent. Not paginated: columns, rows and marks are one picture and a page cursor would draw a lying one; it is clamped instead. Use `list_objectives` for a paged walk.
The objective one level up (`parent`), the objectives one level down (`children`), and the supporting ties in both directions (`alsoServes`, `supportedBy`). Nesting is one level deep, so this is the whole tree around an objective, not a slice of it. Not paginated: it is bounded by the org's objective count and read as one shape.
Every open customer insight tied to this objective, each with its estimated delta, committed date and the work items that satisfy it (conditionStatus: no_work, in_progress, delivered, broken). Not paginated: the claim set is assembled from three queries and ordered after the fact, so there is no stable cursor to page it on. Use `list_insights --objective-id` for a paged walk of the same insights.
List metrics tracked by the organisation, with cursor pagination. Filter by steward (pass your own user id from whoami for 'my metrics').
Get a metric by UUID or by its per-org ref (MET-12).
Recorded values for a metric, oldest first, with cursor pagination over the whole series. Optionally filter by date (since).
The catalog of metrics Telos computes from its own data (task throughput, lead/cycle time, time in status, cycle predictability), with whether each is already tracked in this workspace.
The complete set of opportunities that reach this metric through the objectives anchored to it, alphabetically by title. Not paginated: it is a roll-up of one metric's trace, ordered after the query, so there is no stable cursor to page it on. Use `objective list --metric-id` for the objectives themselves, and `list_opportunities` when you want a paged walk. 404 when the metric is not one of yours — an empty list means nobody is betting on it.
Blast radius before deleting or re-anchoring a metric: how many opportunities and objectives it carries, and how many of those opportunities are still live bets.
List strategies with cursor pagination. Optionally filter by status (draft, active, archived).
Get a strategy by UUID or by its per-org ref (STR-12).
Version snapshots of a strategy, newest first, with cursor pagination. Every content edit creates one.
List direction documents with cursor pagination: visions, strategies, product specs and context documents in one store, newest edit first. Filter by kind, vision scope or status. Rows carry the ref and a contentHash of the body, never the body itself. Kinds you cannot read are omitted, so this needs vision:read or strategy:read alongside direction:read to see those two.
Every published version across the direction store, newest first, with cursor pagination. Each row names the document, its ref and kind, the version, who changed it and their note; isCreation marks version 1. Kinds you cannot read are omitted.
Get one direction document by UUID or by its per-org ref (VIS-1, STR-2, SPEC-3, CTX-4). Returns the published body alongside the list fields; the unpublished draft is a separate read.
The unpublished working copy of a direction document, or null when there is none, together with nextVersion: the version number a publish would write.
Version snapshots of one direction document, newest first, with cursor pagination. Every publish writes one, carrying the title and body as they stood.
List opportunities with cursor pagination. Optionally filter by lifecycle state or team.
Get a specific opportunity by id.
The tasks attached to this opportunity, with cursor pagination.
Get everything needed to act on an opportunity in one call: the opportunity and PRD, linked objectives, the customer(s), comments, linked insights, the active template step, and any linked PRs. Sections you lack the read capability for (e.g. customers, insights) come back null. Pass the opportunity ref (e.g. OPP-23).
Get an opportunity's scorecard: the org's active scoring criteria (name, normalized decimal weight, invert), the shared 1-5 values with attribution, and the computed 0-100 total (null until every non-zero-weight criterion is scored). Positive active weights sum to 1 and correspond to the percentages shown in Settings; zero-weight criteria do not affect the total.
The status updates posted on this opportunity, newest first. These are the same rows that reach every linked customer's feed; plain discussion comments are separate.
The four validation risks (value, usability, feasibility, viability) for one opportunity: each risk's aggregate and every teammate's vote with its note.
The org's scorecard criteria (name, weight, invert, order) — the inputs every opportunity scorecard is scored against. Positive active weights are normalized decimal shares that sum to 1 (the Settings surface shows the same values as percentages totaling 100%); a newly added zero-weight criterion is allocation-neutral. Read-only on the wire, and a pure read: defining criteria is a workspace-settings act gated on scoring_criteria:manage and kept in-app, so an org that has never configured scoring answers with an empty list rather than being seeded by your GET.
An opportunity's phases in delivery order. A phase is a milestone, not work: it carries a title, an owner, and one target date, and it spawns no task. Its band on the plan runs from the previous phase's target to its own, and is open ended when it has none. taskCount and doneCount count only leaf tasks - work with no live children - because a task with children is a container and counting both would report the same work twice; cancelled and duplicate work is left out of the ratio entirely, since it can never complete. Set a task's phase with update_task.
How many of an opportunity's top level tasks no phase claims. Work is never hidden by being unphased - it is its own group at the end of the arc - so this is what says whether that group exists at all. Counts top level tasks only, for the same reason list_opportunity_phases counts leaves: a container and its children are one piece of work, not two.
Coverage of the planned work in one window: how much of it carries an explicit estimate, a planning window, an opportunity tie, and current source data. Estimate values are totalled per team and per unit and are never blended across either.
The grouped queue of what is missing from the planned-work read model: unestimated, unscheduled, unteamed and unaligned work, teams mixing estimate units, and source work whose binding has stopped converging. Each group names why it changes the calculation and where to fix it.
Compare one cycle's or Jira sprint's frozen opening plan with the live planned-work collector. Returns unit-preserving distributions, frozen and current coverage, exact addition/removal/change counts, and up to 100 deterministic item details per change kind; estimate origins distinguish explicit values from workspace-default assumptions, and a missing opening baseline is never reconstructed. Arbitrary date ranges remain current-only through planned-work-summary.
List the workspace's concrete task statuses in stable lifecycle and custom position order.
List tasks with cursor pagination. Optionally filter by opportunity, stable lifecycle status, concrete statusId, priority, team, owner, or to work that no cycle holds yet.
Get a specific task by id.
List development updates posted on this task.
True totals for the six stable lifecycle status types under the same filters `list` takes. Custom named statuses deliberately roll up to their parent type, preserving the aggregate answer to 'how much work is in flight'.
Wall-clock totals per workflow status, folded from the task's status-transition ledger. One row per concrete status the task has visited (named custom statuses stay distinct; rows with no resolvable status fall back to the lifecycle type), with `current` marking the status still accruing. Imported history is only as complete as the source provided.
Get everything needed to act on a task in one call: the task spec, its parent opportunity and PRD, the customer(s), comments, linked insights, the active template step, and any linked PRs. Sections you lack the read capability for (e.g. the parent opportunity, customers, insights) come back null. Pass the task ref (e.g. TF-24).
List recurring-task definitions with cursor pagination: each one's schedule (human-readable cadence + next occurrence), team, owner, and template. These are the definitions a background sweep materializes into real tasks; use list to see the tasks they produced.
Get a recurring-task definition by id: the stored row (rrule, template, active flag). The human-readable cadence, next occurrence, team and owner are list-only.
Everything outside Telos attached to one work item, in a single read: `refs` are the outbound links (pull requests, issues, Slack threads, Notion pages, linked Telos chat messages) and `blockers` are the open 'waiting on someone outside the team' flags. Blockers exist for tasks and opportunities only, so an insight always answers with an empty list. Links to messages in rooms you cannot read are filtered out, and every message URL is rebuilt from the room's current slug. Reading an opportunity's or insight's links also requires that entity's read permission, not just task:read.
Coding-agent dispatch history with cursor pagination, newest first. Supply exactly one of taskId (one task's runs, with the repo and branch the agent worked in) or integrationId (one connector's runs, with each run's task ref). The field the other filter would have told you comes back null.
Explicit task estimates grouped by category and evenly split across linked objectives and distinct task-linked customers (insights and onboarding pins). Open cycles return live leaf work and the current goal revision. Completed cycles return frozen close-time shares and the goal revision in effect at cycle end, or state unavailable when no snapshot exists. Missing estimates are counted, never imputed; up to 500 rows per axis with full denominators. No spent time or salaries. Requires task, opportunity, objective, category, customer and insight read access too.
List the organisation's retained cycle windows with cursor pagination. Reading never generates new windows.
Get one cycle window and its lifecycle state.
List the grids cycles are generated on: one row for the organisation in org mode, or one row per cycle-enabled team in team mode. Answers which teamId every other cycle read should be narrowed by.
List one grid's cycle windows newest first, each decorated with derived delivery analytics: lifecycle status, capacity and committed days, scope, work added after start, work spilled out, work completed, success percentage, and the customer commitments falling due inside the window. Closed cycles read their frozen close-time digest, open ones read live bindings.
Resolve the cycle a grid numbers as N, the addressing the /cycles/{grid}/{number} deep links use. The grid is a team slug, or the literal 'org' for the organisation grid. Answers with the cycle plus its team slug and derived lifecycle status.
Get the daily cumulative scope and completed series across a cycle's window, in estimate days. Scope rises as work is bound after the start; completions land on their completion day, with post-window completions clamped onto the final point. Closed cycles read their frozen close-time digest.
Group a cycle's member work by owner, by priority and by team, each with a count and a percentage of the cycle's scope. The composition summary; ask task list with a cycle filter for the rows themselves.
A bounded, searchable window over every retained cycle, grouped by grid and newest window first, for building a cycle selector. Matches name or number, and narrows to the grids of the given teams. The answer carries the total for that scope, so a caller can tell a partial window from a complete one, and any cycle asked for by includeIds rides in a separate `included` array so it cannot disguise a truncated window as a complete one; cycle list is the paginated read that walks them all.
The active and future cycles of every enabled grid, current window first: the only windows a task may be assigned to. Narrow by teamId to one task's effective grid. Retained history is excluded by design and never displaces these rows.
Get the immutable close-time verdict for a committed cycle, if it has closed.
Get the organisation's cycle-planning configuration. Null means it has never been configured.
List the customer promises falling due inside a cycle's window, attributed to its grid, with delivery coverage: delivered, covered, at risk (with breach days), or uncovered.
Read the customer promises of up to twenty cycle windows in one call, each answering exactly what cycle commitments answers for that window: the promises falling due inside it, attributed to its grid, with delivery coverage. Ids outside the organisation are dropped rather than reported. For a planning horizon of cycle columns; ask cycle commitments for one window.
Break a cycle's live leaf work down by owner as assignedMinutes against capacityMinutes available for this cycle: one workday per weekday minus otherCycleMinutes, floored at zero. Other overlapping cycles block their full estimate, even with mismatched dates; touching end dates do not overlap. Unestimated work uses the organisation default; completed work counts and unowned/non-human work is separate. Team membership supplies idle rows, not a separate capacity pool or visibility role. Every human carrying work here or elsewhere gets a row; only the idle tail is capped at 25 with idleOmitted. Top-level assignedMinutes and capacityMinutes include the full roster before that cap.
Read the per-person load of up to twenty cycle windows in one call, with the same remaining capacity and complete roster totals as cycle load. Every other overlapping cycle blocks its full estimated load, including cycles outside this batch; mismatched dates are not prorated. Ids outside the organisation are dropped rather than reported. These are alternative planning views of shared people, not additive team capacity pools.
List the cycle's informational ledger, newest first: scope added after start, promises at risk on entry, promises moved out, and promises unfinished at close.
Preview the selected cycle's standing commitments, rollover effects, and cross-team collisions without mutating work.
Shares of saved work in a calendar month, grouped by category, objective or team. Excludes time away; multi-objective work splits evenly. Filters follow the work's team. Returns up to 500 groups with the complete count and denominator, never estimates or individual identities. Requires organization-wide cost and underlying resource read access.
Work-item breakdown for one category, objective or team in the recorded monthly allocation. Pass key null (the string null in REST/CLI) for unclassified work. Returns up to 200 items with the complete count and denominator; shares are within this group, not the whole company. Uses the same authority, tenant and read-access rules as the summary.
Get the latest immutable proposal or confirmation for the authenticated engineer's ISO week. This personal read never accepts another user id.
The org's members with the employment FTE and team split in force on a day, the defaults applied where no row exists, and which members belong to more than one team with no split at all. Percentages are basis points; 10000 is one whole person.
Every Jira sprint this workspace has discovered, newest start first, with its board, provider id, state and half-open dates. A sprint with no dates is listed but is not a planning window: nothing defaults one for it. Pass a sprint's id as `jiraSprintId` to the planned-work reads to plan in it.
Team and organization capacity for a cycle, a Jira sprint, a date window or a month or quarter range, in person-weeks: employment FTE times team share times the days of the window. Capacity is distributed by estimate coverage and axis share, with the uncovered remainder, the share no team claims and the days no planning window covers reported as their own buckets. Raw estimate totals never cross a team or a window; only person-weeks do.
Read the standing objective and category percentage benchmarks. Pass revision for an immutable saved version with its original labels and effective timestamp; omit for today's goal and live catalogs. Requires objective and category read access too. Null targets are unset; zero is explicit. Each live catalog is capped at 500 items with a total count, prioritizing saved targets; removed saved items remain visible.
Every marker, session, usage segment and allocation one member submitted inside a period, as JSON. The subject defaults to the caller, who always reads their own; any other subject is part of the allocation surface and needs allocation:read.
List customer insights (the customer-voice evidence layer). Filters compose: lifecycle status, demand root (customer), linked work (opportunity/task/objective), review team, recorder, tag, and full-text search over the verbatim. Archived insights are hidden unless includeArchived is set.
Get an insight with its demand root, links, tags, and committed impacts.
The client-voiced blockers on one opportunity or task: open insights whose link to it is flagged blocking. Use it before promising a delivery date.
Embedding-similarity signals for one insight: near-duplicate insights the org already holds, and near-duplicate asks it already REJECTED (with the reason, the category, and who said no). No LLM call. Read this before recording or reviewing a signal so the org does not relitigate a decision it already made.
The pattern board: semantic clusters of open insights that a human-triggered judge has already scored against the vision, strategies and objectives, plus the raw similarity graph (nodes and edges) behind them. `enabled` and `embeddingModelConfigured` tell you whether clustering is running at all; `unjudgedCount` is how many current clusters have no verdict yet; `truncated` means more open insights exist than the newest window considered.
List the org's other stakeholders (named demand voices with no Telos seat), alphabetically, each with how many insights it roots. Use one's id as externalStakeholderId when recording an insight. Returns at most 500; an org past that needs the in-app directory.
List every tag alphabetically with usage counts so you can cluster insights onto existing tags instead of minting duplicates. Follow nextCursor until hasMore is false.
List the themes tags can be grouped under, with the colour each theme lends its tags.
List customers with cursor pagination. Optionally filter by status.
Get a customer by UUID or by ref (e.g. CUS-12).
List the named people on a customer account, primary contact first, then newest. Use a contact's id to attribute an insight to the person who said it.
The opportunities tied to this account, most-active first. `stepInstanceId` is the onboarding milestone the link is pinned to (null when unpinned).
The account's update stream, newest first: notes posted on the customer plus progress posted on any opportunity the account reaches (linked or through its insights).
Every dated promise made to a client across the org (an open insight carrying a deadline), soonest first so overdue leads, resolved to the work delivering it. `atRisk` flags a near deadline with nothing in flight.
The account's ARR level series oldest first: each point is 'ARR was this much from this date'. Deltas are derived between consecutive points, never stored.
The portfolio view of revenue in one call: `stats` totals ARR and account count per lifecycle position (live, pilot, onboarding, prospect), and `waterfall` gives ARR gained/lost/net per month over the requested window, derived from the same level series.
The account's pre-meeting reviews, newest first: what the client had going on in each stored date window. Summaries only; read one with get_customer_review.
One stored review in full: the digest (new asks, shipped work, updates, overdue commitments, delayed work, stuck asks) plus the generated narrative when there is one.
The account's onboarding in one call: every milestone in order with the active one flagged, the ARR tranche each unlocks, and the work sitting on it (opportunities pinned to the milestone plus bespoke tasks). `hasOnboarding` is false when the account was never started.
List teams in the organisation, with cursor pagination.
Get one team: its name, description, ref prefix (the task-ref namespace, e.g. ENG-42) and default PRD template. The roster is a separate read (list_team_members).
The team's roster with cursor pagination: each member's user id, name, email, avatar, membership id, and org role. Salary and weekly-hours fields are included only for a caller holding compensation:read over this team, and are absent otherwise rather than null. Requires team:read.
Who owns insight review for each team. One row per team, optionally narrowed to one team; teams with nobody assigned come back with a null reviewer.
List org members with cursor pagination.
Get one org member's profile: name, email, avatar, org role and weekly hours. Salary fields are included only for a caller holding compensation:read, and are absent otherwise rather than null. Use whoami for the key's own user.
The org's roles with the permission grants each one confers, so you can see who can do what before proposing a change. Creating, renaming, deleting a role and rewriting its grants are in-app only.
List rooms with cursor pagination. Scope 'mine' (default) returns the rooms you are a member of (DMs, group channels, system rooms) with unread counts and your per-room state, most recently active first. Scope 'open' returns every live open group channel in the org — the channel browser — with isSubscribed telling you which ones you are already in; the per-membership fields are null on those rows. Archived channels appear in neither scope.
Get a room by UUID with your membership role. Members always read; any org user reads an open group channel; a private group, DM, or system room without a membership row is forbidden.
List the members of a room you can read, oldest join first, with each member's room role. Requires read access to the room (member, or any org user for an open group channel).
List a room's top-level messages, newest first, with cursor pagination (service-capped at 100 per page). Rows carry author, reactions, attachments, and thread reply counts; fetch a thread's replies with the thread action. Requires read access to the room (member, or any org user for an open group channel).
List the replies threaded under a top-level message, newest first, with cursor pagination. Requires read access to the message's room.
List pinned messages, most recently pinned first, with cursor pagination. Pass roomId to scope to one room you can read; omit it for every pin across the rooms you are a member of. Rows are message rows — read the room's name with get_room.
Search the rooms you are a member of by message body (case-insensitive substring), newest first, with an optional room, author, DM-counterpart, and date filters. Returns the top 40 matches with a snippet around the hit; it is a search, not a paginated list, so narrow with filters rather than paging. A query under two characters needs at least one filter to anchor on.
List a discussion thread, newest first, with cursor pagination. Pass entityType and entityId for an entity's top-level comments, or parentId for the replies threaded under one comment. Rows carry the author, reaction aggregate, and (for top-level rows) the reply count.
List file metadata with cursor pagination. Pass entityType AND entityId together for one entity's attachments (oldest first); omit both for the org-wide manifest (newest first). Bytes never travel this API — `get` returns a short-lived presigned download URL. Rides file:manage because the unfiltered form is the same org manifest the in-app admin surface gates on it.
Get one file's metadata. Once the upload has been scanned and processed (status `clean`) the response carries `downloadUrl`, a presigned GET valid for about 15 minutes; while the file is pending, processing, or failed that field is null and `status` / `processingError` say why.
Bytes currently held against the org's storage quota, the quota itself, and the percentage used. Check this before a large upload: request-upload refuses anything that would cross the quota.
List the org's template templates with cursor pagination. Templates carry no category: categories group tasks and opportunities, not templates.
Get a template template with its ordered steps. The terminal Completed/Cancelled pair is system-managed and not included.
The tasks and opportunities currently running on this template, with counts. Delete refuses while either count is above zero; reassign-and-delete is the way through.
The template a new task, opportunity or customer gets when the caller does not choose one: the flagged default, else the oldest matching template. 404 when the org has none for that entity type.
The live process on a task, opportunity or customer: which step is active, how far it has run, and every step instance in order with its owner and status. This is the read an agent needs before advancing a step or writing a handoff. 404 when the entity carries no template.
List the org's categories in display order (used to group templates and to classify time entries).
Get one category: its label, slug, color and sort order. Categories group tasks and opportunities and classify time entries; the system Bug and Incident categories are present in every org.
List the PRD templates visible to the key's user: shared (org-wide) templates plus those owned by the user's teams. Pass allTeams to read the whole org catalog instead, which additionally requires prd_template:update.
Get one PRD template: its name, markdown content, and team scope (teamId null means shared org-wide). Visible when the template is shared or owned by one of the key user's teams. Writing it into an opportunity is apply_opportunity_prd_template.
List time entries in a date range (from/to, max 366 days), newest first, with cursor pagination. Defaults to the authenticated user's own entries. engineerId reads another engineer's entries and requires the salary read permission (compensation:read); a team-scoped grant admits only engineers on the caller's teams.
Total hours in a date range bucketed by category, across all engineers in the org (optionally filtered to one team's members). Cost reporting: requires an org-wide cost:read grant.
Hydrated time entries for every engineer in a date range (hours, derived cost, engineer, category, target), capped at 5000 rows; `truncated` flags the cap. Not a paginated list: it is one capped snapshot of the whole window, so narrow the range rather than paging. Use list_time_entries to walk your own entries with a cursor. Cost reporting: requires an org-wide cost:read grant.
Sum of hours times each entry's snapshotted hourly rate for every time entry logged against the opportunity. Returns 0 when nothing is logged. Requires an org-wide cost:read grant.
Sum of hours times each entry's snapshotted hourly rate for every time entry logged against the task. Returns 0 when nothing is logged. Requires an org-wide cost:read grant.
Every entry logged against one opportunity, newest first: date, hours, and note. Hours only — the cost of those hours rides cost:read on get_time_entry_cost_for_opportunity. Who logged each entry is individual-grain time, so engineerId/engineerName come back null unless the caller holds compensation:read (which an API key never does): sum the hours, do not attribute them.
The activity log for one entity, newest first, with cursor pagination: who changed which field, from what to what, and when. Requires the read capability of the entity you are asking about. Read it before proposing a change so you know what already happened.
Report whether the authenticated user currently follows an entity, i.e. receives its notifications. False covers both never-subscribed and explicitly muted.
List the authenticated user's notifications, newest first, with cursor pagination. Filter to unread only, pinned only, or a set of notification types.
Count the authenticated user's unread notifications.
List the org's outbound webhook subscriptions. Secrets are write-once and never returned.
List the org's installed integrations: connector type, name, status, health counters, and the non-secret config (watched channels, repo maps). Stored credentials are never returned. Import-only sources are excluded, matching the in-app directory.
Get one integration's connector type, name, status, health counters, and non-secret config. Stored credentials are never returned.
The integration's inbound/outbound delivery ledger with cursor pagination, newest first: kind, status, dedup key, attempt count, error text, and timings. The raw third-party payload and request headers are deliberately withheld — they carry provider verification tokens and signature headers.
The org's bulk-import runs (Linear, Jira, CSV) with cursor pagination, newest first: source, status (pending/running/paused/completed/partial/failed), per-phase counts, error text, and timings. Starting a run is in-app only; use this to find the run to watch.
One import run's durable row: source, status, the sanitized run plan, accumulated per-phase counts, resume checkpoint, error text, and timings. Poll it to watch a run progress; a status of completed, partial, or failed is terminal.
Each write wraps the same scoped service the REST API uses, so every mutation is typed, validated, audited, and attributed to the key's user — never a system actor. Each tool requires a specific capability; supply an idempotencyKey on any write you might retry so a repeat returns the first result instead of writing again.
Point the caller's avatar at an uploaded file. Upload it first with request-file-upload and confirm-file-upload; this takes the resulting file id. Self-scoped: a credential can only set its own avatar.
Create a customer account (name plus optional profile, owner, ARR, and lifecycle status; status defaults to prospect).
Update a customer's profile, owner, ARR, health, or lifecycle status. Moving to churned/lost requires a reason (stored as an insight rooted at the customer); moving to onboarding activates the org's default onboarding template.
Hard-delete an account and its insights. Contacts, events, reviews, updates, and opportunity links go with it; linked opportunities survive.
Add a named person to a customer account (name plus optional title, role, email, phone, and notes). Marking the contact primary demotes the account's existing primary.
Update a contact's details. Promoting a contact to primary demotes the account's existing primary.
Remove a contact from a customer account. Insights attributed to the contact survive and fall back to the customer as their demand root.
Tie an opportunity to an account and set where it sits in the account's onboarding: the call is idempotent and always leaves the link pinned to `stepInstanceId`, so omitting it (or passing null) unpins. Re-call to re-pin.
Remove the tie between an account and an opportunity. The opportunity itself survives.
Post an account note to the customer's feed, authored by the key's user. It notifies the account's subscribers and mirrors into the org's customer-updates channel. Opportunity progress goes through post_opportunity_update instead.
Record what the account's ARR was from a given date. A point dated at or after the latest recorded level also updates the account's current ARR; an earlier one only backfills history. Future dates are rejected: this records what happened, not forecasts.
Remove one point from the account's ARR series (typo repair). The account's current ARR is not recomputed.
Build and store a review of everything that happened on the account between `from` and now: asks recorded, work shipped, updates posted, commitments missed. Writes an LLM narrative when the org has one configured; the digest stands on its own without it.
Put the account on an onboarding template. Starting seeds the milestones and moves a prospect to onboarding; passing a different templateId on an account that already started swaps it. Omitting templateId uses the org's default customer onboarding template.
Move the account to a milestone, naming it either by the account's own step instance (stepInstanceId) or by the template's template step (stepId). The move lands in the account's update feed so every stage change is on the record. This does not end onboarding: use complete_customer_onboarding for that.
End onboarding: lands the account on the template's Completed step, flips a prospect/onboarding/pilot account to live, stamps customerSince, and notifies subscribers. Never resurrects a churned or lost account.
Set what a milestone owns: its DRI, the ARR tranche it unlocks on completion (null clears), and an existing task attached to it. Every field is optional; only what you pass is written. Attaching a task writes the task row, so passing taskId also requires task:update.
Create a new work item as a go-live requirement on a milestone. kind 'task' is bespoke per-client work (data migration, training), requires task:create, and requires teamId because every task belongs to a team; kind 'opportunity' is a product bet, created and pinned to the milestone, and requires opportunity:create. customer:create alone is not enough — the row lands in the work graph. Returns the new item's kind and id. To attach work that already exists, use update_customer_onboarding_step (tasks) or link_customer_opportunity.
Create a strategy (name plus a markdown content body). Starts as a draft.
Update a strategy's name or content. An optional changeNote labels the version this edit creates. Owner-only; ACL enforced by the service.
Publish a draft strategy, making it the active version. Owner-only; ACL enforced by the service.
Archive a strategy, retiring it from the active set. Owner-only; ACL enforced by the service.
Label one snapshot in a strategy's version history. An empty note clears the label, so the version falls back to 'v{n}'. Owner-only; ACL enforced by the service.
Create a vision: the company vision (kind 'company', one per org) or a product vision (kind 'product'). The narrative is the persuasive 2-5 year story, not a slogan.
Update a vision's name or narrative. Edits are versioned.
Label one snapshot in a vision's version history. An empty note clears the label, so the version falls back to 'v{n}'.
Create a direction document of one kind: vision (with a company or product scope), strategy, product_spec or context. Publishing requires a person in the app; an API key or app token gets a draft instead, or a 403 when it asks to publish outright. So a key's new document arrives as a draft with no version behind it.
Update a direction document's title or body. A body edit is a new version, so Publishing requires a person in the app; an API key or app token gets a draft instead, or a 403 when it asks to publish outright. Save a draft instead and publish it in Telos.
Replace a direction document's unpublished working copy with this title and body. The published document is untouched until a person publishes the draft in Telos, so this is the write an agent should reach for.
Commit a direction document's draft as its next version. Publishing requires a person in the app; an API key or app token gets a draft instead, or a 403 when it asks to publish outright.
Throw away a direction document's unpublished working copy. The published document and its history are untouched, so this only undoes edits nobody has published.
Retire a strategy, product spec or context document; its history stays readable. Visions are deleted with a reassignment instead. Archiving changes what the workspace treats as current, so it is a person's act in the app like publishing.
Label one snapshot in a direction document's version history. An empty note clears the label, so the version falls back to 'v{n}'.
Delete a product spec or context document; the owner or direction:delete may. Strategies are archived rather than deleted, and deleting a vision re-roots its objectives, opportunities and tasks, so both are refused here.
Create a metric (name, unit, optional data source and steward).
Update a metric's name, description, unit, data source, or steward.
Permanently delete a metric.
Record an observed value for a metric. recordedAt defaults to now; pass it to backfill history.
Create a system-sourced metric from the built-in catalog. Telos backfills recent history and keeps it current nightly; its readings cannot be entered by hand. Some builtins take params: an optional category scope, or a required status group for time-in-status.
Create a strategic objective under a vision. Requires a DRI. The metric block (metricId, operator, target) is optional: goals with no clean measurement are created without one and can be anchored later.
Update an objective's title, description, deadline, DRI, or status.
Permanently delete an objective.
Replace which metric an objective points at, change its target shape (operator, target, start value), or pass metricId: null to clear the anchor entirely. An objective has at most one metric, and operator and target travel with it.
Set, change or clear one claim in the alignment grid. Pass kind 'primary' for 'this is what the source is really for', 'supporting' for a secondary contribution, or null to remove the link. An objective may sit under at most one primary parent, and nesting is one level deep: a parent may not itself be a sub-objective.
Create a work opportunity. The description seeds the PRD (or a PRD template does, when omitted). Optionally set lead, team, template, category, initial objective links, tags, and values for active scoring criteria. A patternPromotion additionally requires insight:update because it actions the pattern and links evidence atomically. Financial fields are ACL-gated.
Update an opportunity's title, lead, team, PRD body, or tag set. Editing the PRD clears any cached AI synthesis. Lifecycle moves go through set_opportunity_state, write_template_handoff (template steps) or reject_opportunity, not here.
Permanently delete an opportunity and its dependency edges. Prefer reject_opportunity to record a won't-build decision; delete is for mistakes, not outcomes.
Set one scorecard value on an opportunity: a shared 1-5 value for one of the org's scoring criteria (last write wins, attributed to the caller). Use get_opportunity_scorecard for criteria ids and the computed total.
Clear one scorecard value on an opportunity (the total returns to null until re-scored).
Tie an opportunity to an objective (membership only: 'this bet claims to matter for that number'). Impact evidence lives on insights, not the link.
Repoint an opportunity's objective link at a different objective. `objectiveId` names the link you are moving; `newObjectiveId` is where it lands.
Remove the tie between an opportunity and an objective.
Reject an opportunity: skips remaining template steps, lands it on the Cancelled terminal step (or flips a template-less one to cancelled), and records the categorized won't-build decision on the thread.
Post a status update on an opportunity, authored by the key's user. It lands in the opportunity's updates stream and flows into every linked customer's feed. For a plain discussion comment, use create_comment.
Move an opportunity between backlog, in_progress and completed. On a template-bearing opportunity this drives the template (activating resumes the step it paused on, completing lands the Completed terminal step); a template-less one flips its status column. To record a won't-build decision use reject_opportunity, which takes the category and reason this action deliberately has no room for.
Attach a prototype to an opportunity, attributed to the key's user. Provide exactly one of `url` (http(s) only) or `fileId` (a previously uploaded file). Returns the opportunity's whole prototype list.
Detach a prototype from an opportunity by its URL. Returns the remaining prototype list.
Run the org's configured LLM over the opportunity's insights, customers, prototypes and strategy context and return a draft PRD in markdown. The result is cached for an hour (cached=true, ageMs, and staleSinceLastSynth report the cache's age and how many insights landed since); pass bypassCache to re-draft. This returns a draft — it never writes the PRD, which is what update is for.
Record the key's user vote (approve, risky, reject) plus a note on one validation risk. One vote per user per risk, last write wins. Returns all four risks with their recomputed aggregates.
Write a PRD template's markdown into an opportunity's PRD, replacing the body or appending to it. Use list_prd_templates for template ids. This writes the OPPORTUNITY, not the template, so it is gated on opportunity:update; the template's own read ACL still applies.
Post the handoff note a finished phase owes its successor. It lands as an update on the opportunity, so it reads in the same stream as everything else the team said about the work, and it closes the pending handoff on whichever phase was waiting on this one. An empty note is a no-op: the note is encouraged, never required, and skipping leaves the phase's pending handoff open rather than posting a hollow update.
Append a phase to an opportunity's delivery arc. A phase is a milestone, not work: a title, one optional target date, an optional owner. It spawns no task. Phases do not require a template - an opportunity that never ran one can still have them. New phases land last; use reorder_opportunity_phases to place one.
Change a phase's title, target date or owner. Sending a null target date makes the phase open ended, which means the band after it can no longer be anchored. Use list_opportunity_phases for phase ids.
Delete a phase. The work filed under it is not deleted: those tasks become unphased and stay on the opportunity. Remaining phases close the gap so the arc stays contiguous.
Set the delivery order of every phase at once. The list must name each of the opportunity's phases exactly once: a partial order would strand whatever it left out at a position it no longer owns. Order decides the bands, because a phase's band runs from the previous phase's target date to its own.
Create a named status inside one stable lifecycle type.
Rename or recolor a task status. Its lifecycle type cannot change.
Reorder a status within its lifecycle type.
Move remaining tasks to a same-type replacement, then archive the status atomically.
Restore an archived task status.
Create a task using the stable lifecycle status, optionally refined by a concrete workspace statusId (which wins). A teamId is required: every task belongs to exactly one team, which also decides its ref prefix. The task may attach to an opportunity or stand alone. Optionally seed subtasks, owner, category, template, and tags in the same call.
Update a task's fields. Status remains the stable lifecycle vocabulary; statusId optionally selects a concrete workspace status and wins when both are sent. For a bare status move prefer set-status, which also auto-assigns ownerless work.
Post one development update for this task. It appears in Development Updates and reaches every customer linked through the task's insights.
Permanently delete a task. Its time entries, tags, checklist items, and insight links go with it, and its dependency edges are cleared; any subtasks are detached and survive as standalone tasks. To retire work without losing the record, set its status to cancelled instead.
Set a task's stable lifecycle status, optionally refined by a concrete workspace statusId which wins. Moving ownerless work to a todo/in_progress status assigns the caller.
Assign the single accountable owner, or pass null to unassign. Reassigning away from an agent owner cancels its in-flight run; assigning an agent owner dispatches a new one, and confirmedRepo answers the repository prompt that dispatch raises when the target repo is ambiguous.
Return the proposed planning move and blast-radius impact for assigning this task to a cycle. This does not write; submit the returned move through the planning commit step to apply it, or call move-cycle to preview and apply in one step.
Assign a task to a cycle and apply the move, the write assign-cycle only previews. A move that takes live work out of a started cycle a customer promise rides on is refused with PROMISE_BREAKS, and the error carries the breaking {taskId, insightId} pairs: send them back as acknowledgedBreaks to proceed, with announceToCustomers to post the change to each affected customer.
Attach the coding session working this task, so the engineer can resume it later (the task page offers `claude --resume <sessionId>`) and model usage can be attributed. Idempotent per (provider, sessionId, task); call again with inputTokens/outputTokens/costCents totals to report usage — the stored session updates in place. A session attached to several tasks is several refs, each carrying the session's full totals, so spend double-counts until aggregation dedupes by session.
Define a recurring task: an RRULE schedule plus a task template. A background sweep creates one real task for the given team on every occurrence (status todo, due = occurrence + dueOffsetDays). This creates the DEFINITION, not a single task — use create for a one-off. rrule is an iCal RRULE string (e.g. 'FREQ=WEEKLY;BYDAY=MO'); dtstart/tzid default to now/UTC and it never backfills.
Update a definition's schedule, template, or active flag. Editing the schedule recomputes forward from now, never retroactively.
Pause or resume a recurring-task definition. Paused definitions stop materializing tasks; resuming never backfills the paused gap.
Delete a recurring-task definition. Tasks it already created stay; no new ones are generated.
Create a team. The 2-4 character prefix becomes the team's task ref prefix (e.g. ENG-42).
Update a team's name, description, ref prefix, or default PRD template.
Delete a team. Every task belongs to a team, so a team that still owns tasks is refused unless targetTeamId names another team to take that work in the same call; the refusal carries the task count. Opportunities keep their rows with the team unset, task refs stay stable, and memberships and team-scoped config are removed. Refuses to delete the org's only team.
Add a user to a team's roster.
Remove a user from a team's roster.
Update an org member's profile (name, weekly hours). Salary fields require a leadership role and travel as a pair; role changes need org:manage, which API keys never carry.
Create a tag for clustering insights. List existing tags first (list_tags) and reuse them rather than minting duplicates.
Rename a tag or change its colour. Renaming keeps every insight, task, and opportunity attached to it.
Delete a tag and detach it from everything it labelled. Check usageCount on list first: the rows it tagged keep no record of it.
Create a theme to group tags under (e.g. 'Clients', 'Risk'). Its colour becomes the default colour of every tag in it.
Rename a theme or change the colour it lends its tags.
Delete a theme. Its tags survive and fall back to their own colour; nothing is deleted with it.
Post a comment on a task or opportunity, authored by the key's user. It lands on the entity's discussion thread. For an opportunity status broadcast that flows into linked customers' feeds, use post_opportunity_update instead.
Edit one of your own comments; it is marked edited. Only the author may edit a comment, and comments ingested from an integration cannot be edited.
Delete one of your own comments. Its thread replies cascade with it, and their file attachments are swept. Only the author (or an org manager) may delete it.
Mark a top-level comment's thread resolved, or reopen it. Only the comment's author or the owner of the document it hangs off may set this. Resolving records the document's current version, so the thread reads as settled against the version it was settled on.
Hide a top-level comment's thread from the document's comments panel, or bring it back. Only the comment's author or the owner of the document it hangs off may set this. Archiving hides the thread and its highlight; it deletes nothing.
Set whether the authenticated user reacts to a comment with an emoji. Absolute, not a toggle: `on` is the state you want, so a retried call never silently removes the reaction it just added. Returns the comment's full reaction aggregate.
Record a customer insight (one discrete signal, not a whole document). Roots at exactly one demand source: a customer, an internal stakeholder (org user), or an other stakeholder (externalStakeholderId — a named voice with no Telos seat). Optionally set source, moscow priority, deadline, tagIds for clustering, a reviewTeamId to route it into a team's review queue, and attach to an opportunity/task/objective.
Update an insight's verbatim, source, demand root, contact, moscow priority, or deadline. The service keeps exactly one demand root after the merge.
Permanently delete an insight and its links. Prefer the review lifecycle (set-status rejected/archived) to record an outcome; delete is for mistakes.
Link an existing insight to an opportunity, task, or objective (optionally pinned). Call it once per insight to attach a whole cluster of related insights to one opportunity.
Remove the link between an insight and an opportunity, task, or objective. The insight survives; only the tie goes.
Set the flags on an existing insight link: pinned (surface it at the top of the work item's evidence rail) and blocking (this client ask blocks the work). Blocking is opportunity/task only — an objective cannot be blocked by a client ask. Pass at least one flag; omitted flags are left alone.
Move an insight to a review outcome. This is the whole lifecycle in one action: created, review, need_context, active, rejected, delivered, stale, or archived. `rejected` requires a note (the reason, 5-1000 chars) and a rejectionCategory, and publishes the decision to the org and to the customer's feed. `need_context` requires a note (the question, 5-1000 chars) and sends the insight back to whoever recorded it. `archived` hides it from every default list without deleting it. Returns the insight.
Route an insight to a team for review (this moves it to status review and notifies the team's insight reviewer), or pass null to pull it back out of review. Clearing returns a live insight to active when it has linked work and created when it does not; a terminal insight keeps its outcome. Returns the insight.
Replace an insight's tag set (tags are how insights cluster into themes). Pass the full list you want, not a delta; an empty list clears them. Create or look up tag ids with the tag actions. Returns the insight.
Replace the insight's impact ties: which objectives it moves, and by how much. This is the only place a number is typed on an insight; work ties carry no delta. Pass the full list, not a delta; an empty list clears them. Returns the insight.
Deprecated compatibility action for clients from before atomic pattern promotion. It records the pattern as actioned but cannot record a work association because the legacy request carries no target. New callers must create work with patternPromotion or use attach-pattern-work.
Dismiss a scored pattern. It leaves the board and suppresses future candidates that substantially overlap its members, so the same cluster does not come back every run.
Action a suggested insight pattern by attaching its current insights to an existing opportunity or task. The lifecycle transition, work association, and evidence links commit atomically.
Link selected, currently unlinked insights in an actioned pattern to one or more associated live work items. Membership and existing links are recomputed server-side, so stale or manually linked selections are safely skipped.
Create a named demand voice with no Telos seat, for rooting insights. Deduplicated case-insensitively on name: an existing voice with the same name is returned instead of a second row, so the same person typed twice stays one voice.
Create a PRD template (markdown scaffold for opportunity PRDs). teamId scopes it to a team; omit for a shared org-wide template, which requires the prd_template:update capability.
Update a PRD template's name, content, or team scope.
Delete a PRD template. Opportunities that already applied it keep their PRD content.
Subscribe an HTTPS endpoint to outbound events. Deliveries are signed with the secret (t=/v1= scheme); the secret is encrypted at rest and never returned, so store it now.
Change a subscription's target URL, event types, description, or active flag. The signing secret cannot be changed here — it is write-once, so rotate by deleting the subscription and creating a new one.
Emit a synthetic event so you can verify the endpoint and its signature check end to end. Returns the emitted event id; the delivery itself lands at your endpoint, signed with the subscription's secret.
Delete a webhook subscription. Deliveries stop immediately.
Create a template template (name, optional description, optional defaultEntityType). Every template closes with system-managed Completed and Cancelled steps; add real steps with create-step.
Update a template's name, description, or default entity type.
Delete a template template. Refused while active tasks or opportunities are using it.
Append a step to a template, ahead of the terminal pair. Owner defaults to the caller when defaultDriUserId is omitted.
Update a step's title, description, position, owner, or SLA. Terminal steps are system-managed and can't be edited.
Delete a step and close the position gap. Refused for terminal steps and for steps active work is currently on.
Move an opportunity or customer-onboarding template to a step and post a handoff message for it. Jumps to the target step instance (optionally completing the rest) and records the handoff note on the entity's thread. For a subtask sequence inside a task, use write_task_handoff instead.
Repoint every task and opportunity on this template onto the replacement, re-seed step instances for the active ones, and delete the template. Destructive: step progress on the reassigned work resets, so moving tasks also requires task:update and moving opportunities also requires opportunity:update. Use plain delete when nothing is using it.
Put a task, opportunity or customer on a template for the first time and seed its step instances (opportunities also get one schedulable task per phase). Owners resolve from each step's default user or role. Set activateFirstStep false to seed everything pending. Refused when the entity already runs a template; use swap for that.
Replace the template an entity is already running, in one transaction. Destructive: the old step instances are deleted and progress resets, since template steps rarely line up, so it also requires the target entity's own update permission (task:update, opportunity:update or customer:update). No-op when the entity is already on the target template.
Set a step instance's status and/or its owner. Status moves are checked against the allowed transitions, and completing or skipping a step activates the next pending one. To move the pointer to an arbitrary step and announce it, use write_template_handoff instead. This is the running instance, not the template step update_template_step edits.
Create a category. The slug is derived from the label; color is a theme token or hex value.
Update a category's label, color, or sort order. The slug never changes.
Delete a category. The system Bug and Incident categories can't be deleted, nor can a category time entries still reference.
Mark one of your notifications read.
Mark all of your notifications read.
Pin one of your notifications so it stays at the top of the inbox.
Unpin one of your notifications.
Unsubscribe from the entity a notification came from, muting its future notifications for you.
Log time for the authenticated user. Work targets exactly one opportunity, task, or misc category (misc requires a note). Named customer_meeting, internal_planning, other, vacation and sick entryKind values allow untracked entries with a required note; time away cannot carry an investment category. Hours are 0-24 per entry.
Update one of your own time entries (entryDate, hours, note). Only the engineer who logged an entry may edit it.
Delete one of your own time entries. Only the engineer who logged an entry may delete it.
Replace the authenticated user's manual entries in a date window with exactly these rows; an empty rows array clears the manual window. Work targets one opportunity, task or misc category. Named untracked entryKind values require a note and allow an optional misc category, except vacation and sick which cannot carry a category. Rows must fall inside the window, capped at 366 days. Jira-authoritative windows refuse manual replacement.
Create a group channel with at least one other org member; the creator becomes its admin. Visibility 'open' (default) lets any org user read and join; 'private' is invite-only. For a DM use find_or_create_dm.
Rename a group channel or edit its description. Room-admin only; DMs and system rooms cannot be edited.
Archive a group channel, removing it from everyone's room list. Terminal: rooms have no hard delete. Room-admin only; DMs and system rooms cannot be archived.
Get the direct-message room you share with another org member, creating it if it does not exist yet. Idempotent: the same pair always resolves to the same room. You cannot DM yourself, and the other user must be in your org.
Add an org member to a group channel. Room-admin only, and the user must be in your org; DMs and system rooms refuse membership changes. Idempotent: adding an existing member is a no-op.
Remove a member from a group channel. Room-admin only, and an admin cannot remove themselves (hand the room off first); DMs and system rooms refuse membership changes.
Set the authenticated user's own state on a room: join or leave an open group channel (joined), pin it to the top of your room list (pinned), and choose how much it notifies you (notificationLevel). Every field is absolute, not a toggle, so repeating the call is a no-op. Fields apply in the order join, pin, notification level, leave; you must be able to read the room at all, pinning or muting a room you are not a member of is forbidden, and only live (non-archived) open group channels can be joined this way.
Post a message to a room you can read, authored by the key's user. Pass parentId (a top-level message UUID) to reply in its thread; threads are two-level. @Name mentions in the body notify the named org members.
Rewrite the body of one of your own messages; it is marked edited. Only the author may edit a message, and system messages cannot be edited. Accepts a thread reply id from the thread action, including a shared-update room thread, whose replies are comments on the update. Returns the same enriched message shape create and list return.
Delete one of your own messages (soft delete; attachments are swept). Only the author may delete it on this surface. Accepts a thread reply id from the thread action, including a shared-update room thread, whose replies are comments on the update; such a reply is removed outright rather than tombstoned, since the comment rail has no soft delete.
Pin a message to its room, or unpin it. Absolute, not a toggle: pass pinned true or false and repeating the call leaves the same state. Any member who can read the room can curate its pins. Ordinary thread replies pin like any other message; only a shared-update thread reply is refused, because it is a comment on the update and carries no pin.
Set whether the authenticated user reacts to a message with an emoji. Absolute, not a toggle: `on` is the state you want, so a retried call never silently removes the reaction it just added. Returns the message's full reaction aggregate.
Set whether the authenticated user follows an entity and receives its notifications. Absolute, not a toggle: `subscribed` is the state you want, so repeating the call is a no-op. Requires the read capability of the entity you are following. Unfollowing mutes the entity permanently for you — participation (commenting, being assigned) will not silently re-subscribe you.
Step 1 of 2. Reserves the file row and returns `uploadUrl` plus the `fields` of a presigned POST; send the bytes to that URL yourself, then call confirm-upload with the ETag storage returned. `sha256` must be the lowercase hex digest of the exact bytes you will send — it is bound into the upload policy, so storage rejects a mismatch. Passing entityType/entityId attaches the file to that entity as soon as it lands, on the same terms as attach: a shared-update thread reply id resolves to its comment under either spelling and requires membership of the reply's room.
Step 2 of 2. Tells Telos the bytes landed, which moves the file to `processing` and queues the integrity check (size, checksum, MIME sniff) that ends in `clean` or `failed`. Idempotent: confirming an already-confirmed file returns its current state.
Bind an existing file to an entity so it shows on that entity's attachments rail. Idempotent, and one file may be attached to several entities. Passing entityType `message` with a shared-update thread reply id also works and lands the file on the reply's comment, since those replies are comments on the update; either spelling requires membership of the reply's room.
Remove one entity's link to a file. The file itself survives with its other attachments; use delete to destroy it. Message rows report the entity that actually owns their attachments as attachmentEntityType; passing entityType `message` with a shared-update thread reply id also works, since those replies are comments on the update.
Permanently delete a file: the stored object, its thumbnail, the row, and every attachment pointing at it. Irreversible.
Record that a task or opportunity is waiting on a stakeholder, client, or partner. Advisory only: nothing about the schedule moves, it makes the wait visible. Optionally name the customer contact being waited on and why. Flagging an opportunity also requires opportunity:update, not just task:update.
Mark an external blocker resolved: the external party came back. The row is kept with a resolution timestamp rather than deleted, and it stops appearing on the item's external links.
Record that a task, opportunity, or insight originated in a Telos chat message. The permalink is built server-side from the message's own room and thread, so you only supply the message id; you must be able to read the source room. Anchoring on an opportunity or insight also requires that entity's update permission, not just task:update. Idempotent on the same pair.
Stop an in-flight coding-agent run: the provider session is cancelled best-effort and the run lands in the terminal `cancelled` state with a summary. Cancelling an already-finished run returns it unchanged.
Set the organisation-wide minutes counted for an unestimated task in cycle capacity, load and burnup math. It changes what every cycle's numbers mean, so it is workspace administration and requires cycle:manage.
Configure cycle planning for the organisation. Cycle configuration is workspace administration and requires cycle:manage.
Explicitly move unfinished work from an ended cycle to its next window. This can resume a cycle parked because automatic rollover is off.
Capture the one-shot opening commitment digest for a cycle. The baseline cannot be replaced after commitment.
Change the identity, grid, or half-open window of an open, uncommitted future cycle.
Delete an open, uncommitted future cycle only when no task of any status references it.
Replace both standing benchmarks under a revision guard. Each independent distribution may total up to 10000 basis points; omitted targets are unset. Requires an active human member with objective/category read access and allocation-goal management permission. No time ledger or cycle plan is changed.
Append a confirmation revision for the authenticated engineer only. Rows must exactly match the addressed latest revision and total 10,000 basis points. An optional human-entered weekly total atomically replaces that complete week of actual time; every positive allocation must then target a task.
Close the open employment-FTE and team-split periods on a date and append their successors. Closed periods are never rewritten, so a start date inside one is refused; setting the employment FTE additionally requires organization administration. A split is complete: a team left out of it is closed, not kept.
Upload locally parsed CLI task markers, AI coding-session segments and their task allocations. Everything is written under the calling identity: the payload names no user, and no field on it may carry a prompt, a response, a filesystem path or a credential. Idempotent on the hashed session and response identifiers, so re-running the collector closes an open session and adds missing segments without duplicating anything.
Turn local session, response, working-directory and branch strings into the org-keyed HMACs the ingest payload carries. The key itself never leaves the server and is destroyed with the workspace, so a hash correlates nothing across tenants.
Delete the caller's markers, sessions, segments and allocations for every period with no confirmed weekly reconciliation. Evidence behind a confirmed week is retained until its retention clock expires, because the confirmation is the organization's record and cannot be retracted underneath it.