DocsDevelopers
MCP

5 gateway tools. 288 actions on demand.

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.

Connect

Endpoint & auth

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.

endpoint
https://www.telos-app.com/api/mcp
Quickstart

Connect a client

Three 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.

1. Get an API key

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.

2. Register the server with your client

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.

terminal
claude mcp add --transport http telos \
  https://www.telos-app.com/api/mcp \
  --header "Authorization: Bearer telos_live_…"

3. Verify the connection

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:

verify
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.

Progressive disclosure

Find, inspect, call

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:

  1. Call find_tools with a capability such as create an insight.
  2. Call inspect_tool with the exact returned name. This loads that action's real input schema; do not infer field names.
  3. Pass the name and validated arguments to call_read_tool or call_write_tool. Supply an idempotency key for a write that may be retried.
agent flow
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" })
Reference

Discoverable read actions

Auto-rendered from the source registry. Find these actions with find_tools; their schemas appear only after inspect_tool.

Identity & directory · 1

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.

Input
No parameters.
Search · 1

global_search

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.

Input
  • q: stringSearch text; at least 2 characters
  • perTypeLimit?: integer | stringMax results per entity type, 1-20 (default 8)
Vision · 5

list_visions

List strategic visions with cursor pagination: the company vision first, then product visions. Optionally filter by kind (company, product).

Input
  • kind?: "company" | "product"Vision kind filter
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

get_vision

Get a vision by UUID or by its per-org ref (VIS-12).

Input
  • id: uuid | stringVision UUID or ref (VIS-12)

list_vision_versions

Version snapshots of a vision, newest first, with cursor pagination. Every name or narrative edit creates one.

Input
  • id: uuidVision UUID
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

list_vision_objectives

The objectives anchored to this vision, with cursor pagination. Returns objective rows, so it needs objective:read as well as vision:read.

Input
  • id: uuidVision UUID
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

count_vision_children

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.

Input
  • id: uuidVision UUID
Objectives · 6

list_objectives

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.

Input
  • visionId?: uuidVision UUID filter
  • metricId?: uuidAnchor metric UUID filter
  • driId?: uuidDRI user UUID filter
  • status?: "active" | "achieved" | "abandoned"Objective status filter. One of: active, achieved, abandoned
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

get_objective

Get an objective by UUID or by its per-org ref (OBJ-12).

Input
  • id: uuid | stringObjective UUID or ref (OBJ-12)

get_objective_opportunities

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.

Input
  • id: uuidObjective UUID

get_objective_alignment

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.

Input
No parameters.

get_objective_nesting

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.

Input
  • id: uuidObjective UUID

get_objective_claims

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.

Input
  • id: uuidObjective UUID
Metrics · 6

list_metrics

List metrics tracked by the organisation, with cursor pagination. Filter by steward (pass your own user id from whoami for 'my metrics').

Input
  • stewardId?: uuidSteward user UUID filter
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

get_metric

Get a metric by UUID or by its per-org ref (MET-12).

Input
  • id: uuid | stringMetric UUID or ref (MET-12)

list_metric_values

Recorded values for a metric, oldest first, with cursor pagination over the whole series. Optionally filter by date (since).

Input
  • id: uuidMetric UUID
  • since?: stringOnly values recorded at or after this time
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

list_builtin_metrics

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.

Input
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

get_metric_opportunities

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.

Input
  • id: uuidMetric UUID

get_metric_dependents

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.

Input
  • id: uuidMetric UUID
Strategy · 3

list_strategies

List strategies with cursor pagination. Optionally filter by status (draft, active, archived).

Input
  • status?: "draft" | "active" | "archived"Strategy status filter
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

get_strategy

Get a strategy by UUID or by its per-org ref (STR-12).

Input
  • id: uuid | stringStrategy UUID or ref (STR-12)

list_strategy_versions

Version snapshots of a strategy, newest first, with cursor pagination. Every content edit creates one.

Input
  • id: uuidStrategy UUID
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)
Opportunities · 10

list_opportunities

List opportunities with cursor pagination. Optionally filter by lifecycle state or team.

Input
  • state?: "backlog" | "in_progress" | "completed" | "cancelled"Opportunity lifecycle state filter
  • teamId?: uuidTeam UUID filter
  • leadUserId?: uuidLead UUID filter. Pass your own user id (whoami) for 'my opportunities'
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

get_opportunity

Get a specific opportunity by id.

Input
  • id: uuidOpportunity UUID

list_opportunity_tasks

The tasks attached to this opportunity, with cursor pagination.

Input
  • id: uuidOpportunity UUID
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

get_opportunity_context

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).

Input
  • ref: stringOpportunity ref, e.g. OPP-23

get_opportunity_scorecard

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.

Input
  • id: uuidOpportunity UUID

list_opportunity_updates

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.

Input
  • id: uuidOpportunity UUID
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

list_opportunity_risks

The four validation risks (value, usability, feasibility, viability) for one opportunity: each risk's aggregate and every teammate's vote with its note.

Input
  • id: uuidOpportunity UUID
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

list_opportunity_scoring_criteria

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.

Input
  • includeArchived?: boolean | "true" | "false"Include archived criteria (kept for historical scores)
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

list_opportunity_phases

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.

Input
  • id: uuidOpportunity UUID
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

count_unphased_opportunity_tasks

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.

Input
  • id: uuidOpportunity UUID
Tasks · 13

get_task_planned_work_summary

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.

Input
  • teamIds?: string[] | stringTelos team UUIDs to scope the read to; omit for every team
  • cycleId?: uuidCycle UUID as the planning window
  • start?: stringPlanning window start (YYYY-MM-DD)
  • end?: stringPlanning window end, exclusive (YYYY-MM-DD)

get_task_planned_work_gaps

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.

Input
  • teamIds?: string[] | stringTelos team UUIDs to scope the read to; omit for every team
  • cycleId?: uuidCycle UUID as the planning window
  • start?: stringPlanning window start (YYYY-MM-DD)
  • end?: stringPlanning window end, exclusive (YYYY-MM-DD)

list_task_statuses

List the workspace's concrete task statuses in stable lifecycle and custom position order.

Input
  • includeArchived?: boolean | "true" | "false"Boolean
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

list_tasks

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.

Input
  • opportunityId?: uuidOpportunity UUID filter
  • status?: "backlog" | "todo" | "in_progress" | "completed" | "cancelled" | "duplicate"Task status filter
  • statusId?: uuidConcrete workspace status UUID filter
  • priority?: "low" | "medium" | "high" | "urgent"Task priority filter
  • teamId?: uuidTeam UUID filter
  • ownerUserId?: uuidOwner UUID filter. Pass your own user id (whoami) for 'my assignments'
  • noCycle?: boolean | "true" | "false"Only work bound to no cycle: the pool a cycle is planned from
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

get_task

Get a specific task by id.

Input
  • id: uuidTask UUID

list_task_development_updates

List development updates posted on this task.

Input
  • id: uuidTask UUID
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

count_tasks_by_status

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'.

Input
  • opportunityId?: uuidOpportunity UUID filter
  • priority?: "low" | "medium" | "high" | "urgent"Task priority filter
  • teamId?: uuidTeam UUID filter
  • ownerUserId?: uuidOwner UUID filter

get_task_time_in_status

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.

Input
  • id: uuidTask UUID

get_task_context

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).

Input
  • ref: stringTask ref, e.g. TF-24

list_recurring_tasks

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.

Input
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

get_recurring_task

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.

Input
  • id: uuidRecurring-task definition UUID

get_external_links

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.

Input
  • entityType: "task" | "opportunity" | "insight"Work item type the link hangs off
  • entityId: uuidWork item UUID

list_agent_runs

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.

Input
  • taskId?: uuidList one task's runs
  • integrationId?: uuidList one integration's runs
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)
Cycles · 17

list_cycles

List the organisation's retained cycle windows with cursor pagination. Reading never generates new windows.

Input
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

get_cycle

Get one cycle window and its lifecycle state.

Input
  • id: uuidCycle UUID

get_cycle_grids

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.

Input
No parameters.

list_cycles_with_stats

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.

Input
  • teamId?: uuid | nullTeam UUID of the grid to read; omit for the organisation grid
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

get_cycle_by_number

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.

Input
  • grid: stringTeam slug of the grid, or 'org' for the organisation grid
  • number: integer | stringThe cycle's number within that grid

get_cycle_burnup

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.

Input
  • id: uuidCycle UUID

get_cycle_breakdown

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.

Input
  • id: uuidCycle UUID

get_cycle_options

A bounded, searchable window over every retained cycle, grouped by grid and newest window first, for building a cycle selector. Matches name or number. The answer carries the unbounded total, so a caller can tell a partial window from a complete one; cycle list is the paginated read that walks them all.

Input
  • q?: stringMatch against the cycle name or number
  • limit?: integer | stringWindow size (default 100, max 100)

get_cycle_impact_candidates

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.

Input
  • teamId?: uuid | null | stringNarrow to one team's grid; null (the literal string "null" on a query string or CLI flag) for work with no effective team, omit for every enabled grid
  • limit?: integer | stringWindow size (default 100, max 200)

get_cycle_verdict

Get the immutable close-time verdict for a committed cycle, if it has closed.

Input
  • id: uuidCycle UUID

get_cycle_config

Get the organisation's cycle-planning configuration. Null means it has never been configured.

Input
No parameters.

get_cycle_commitments

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.

Input
  • id: uuidCycle UUID

get_cycle_commitments_batch

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.

Input
  • ids: string[] | stringCycle UUIDs, comma-separated on a query string or CLI flag (1 to 20)

get_cycle_load

Break a cycle's live membership down by owner as assigned minutes against that person's capacity for the window: one workday per weekday, the unit estimates are written in. Unestimated work counts at the organisation default and unowned work answers in its own bucket. Everyone carrying work gets a row; the grid's idle members fill the remaining seats and the rest are counted in idleOmitted, so a large roster does not have to be paged. Completed members stay in the load because the window's capacity was spent on them.

Input
  • id: uuidCycle UUID

get_cycle_load_batch

Read the per-person load of up to twenty cycle windows in one call, each answering exactly what cycle load answers for that window: assigned minutes by owner against that window's own capacity. Ids outside the organisation are dropped rather than reported. For a planning horizon of cycle columns; ask cycle load for one window.

Input
  • ids: string[] | stringCycle UUIDs, comma-separated on a query string or CLI flag (1 to 20)

get_cycle_events

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.

Input
  • id: uuidCycle UUID
  • cursor?: uuid
  • limit?: integer | string

get_cycle_impact

Preview the selected cycle's standing commitments, rollover effects, and cross-team collisions without mutating work.

Input
  • id: uuidCycle UUID
  • taskId?: uuid
Insights · 6

list_insights

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.

Input
  • status?: "created" | "review" | "need_context" | "active" | "rejected" | "delivered" | "stale"Insight lifecycle status filter
  • customerId?: uuidCustomer UUID filter
  • opportunityId?: uuidOnly insights linked to this opportunity
  • taskId?: uuidOnly insights linked to this task
  • objectiveId?: uuidOnly insights linked to this objective
  • reviewTeamId?: uuidOnly insights routed to this team's review queue
  • recordedByUserId?: uuidOnly insights recorded by this user (whoami for your own)
  • tagId?: uuidOnly insights carrying this tag
  • search?: stringFull-text match on the verbatim
  • includeArchived?: boolean | "true" | "false"Include archived insights (default false)
  • reviewQueue?: boolean | "true" | "false"Your own review queue: insights routed to teams you are the insight reviewer for, plus your own insights sent back for context. Replaces the other filters when set.
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

get_insight

Get an insight with its demand root, links, tags, and committed impacts.

Input
  • id: uuidInsight UUID

list_blocking_insights

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.

Input
  • itemKind: "opportunity" | "task"The work item's kind
  • itemId: uuidThe work item's UUID
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

get_similar_insight_signals

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.

Input
  • id: uuidInsight UUID

get_insight_patterns

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.

Input
No parameters.

list_external_stakeholders

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.

Input
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)
Tags · 2

list_tags

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.

Input
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

list_tag_themes

List the themes tags can be grouped under, with the colour each theme lends its tags.

Input
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)
Customers · 11

list_customers

List customers with cursor pagination. Optionally filter by status.

Input
  • status?: "prospect" | "onboarding" | "pilot" | "live" | "churned" | "lost"Customer status filter
  • ownerId?: uuidOwner (DRI) user UUID filter; pair with whoami to list your own accounts
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

get_customer

Get a customer by UUID or by ref (e.g. CUS-12).

Input
  • id: stringCustomer UUID or ref (e.g. CUS-12)

list_customer_contacts

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.

Input
  • id: uuidCustomer UUID
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

list_customer_opportunities

The opportunities tied to this account, most-active first. `stepInstanceId` is the onboarding milestone the link is pinned to (null when unpinned).

Input
  • id: uuidCustomer UUID
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

list_customer_updates

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).

Input
  • id: uuidCustomer UUID
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

list_customer_commitments

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.

Input
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

list_customer_arr_history

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.

Input
  • id: uuidCustomer UUID
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

get_customer_arr_summary

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.

Input
  • months?: integer | stringWaterfall window in months (1-24, default 6)

list_customer_reviews

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.

Input
  • id: uuidCustomer UUID
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

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.

Input
  • id: uuidCustomer UUID
  • reviewId: uuidReview UUID

get_customer_onboarding

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.

Input
  • id: uuidCustomer UUID
Teams · 4

list_teams

List teams in the organisation, with cursor pagination.

Input
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

get_team

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).

Input
  • id: uuidTeam UUID

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.

Input
  • id: uuidTeam UUID
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

list_team_ownership_configs

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.

Input
  • teamId?: uuidOnly this team's row
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)
Users · 2

list_users

List org members with cursor pagination.

Input
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

get_user

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.

Input
  • id: uuidUser UUID
Roles · 1

list_roles

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.

Input
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)
Rooms · 3

list_rooms

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.

Input
  • scope?: "mine" | "open"'mine' (default) for your rooms, 'open' for joinable open channels
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

get_room

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.

Input
  • id: uuidRoom UUID

list_room_members

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).

Input
  • id: uuidRoom UUID
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)
Messages · 4

list_messages

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).

Input
  • roomId: uuidRoom UUID
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

list_message_thread

List the replies threaded under a top-level message, newest first, with cursor pagination. Requires read access to the message's room.

Input
  • id: uuidMessage UUID
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

list_pinned_messages

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.

Input
  • roomId?: uuidScope to a single room
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

search_messages

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.

Input
  • q?: stringText to find in message bodies (min 2 chars to match on its own)
  • roomId?: uuidScope to a single room
  • fromUserId?: uuidOnly messages by this author
  • toUserId?: uuidOnly messages in the DM with this user
  • fromDate?: stringOnly messages at or after this time
  • toDate?: stringOnly messages at or before this time
Comments · 1

list_comments

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.

Input
  • entityType?: "vision" | "objective" | "metric" | "opportunity" | "task" | "team" | "user" | "task_assignment" | "webhook_subscription" | "template" | "template_step" | "template_step_instance" | "template_checklist_instance" | "strategy" | "strategy_version" | "vision_version" | "notification" | "comment" | "customer" | "customer_update" | "customer_document" | "insight" | "opportunity_risk" | "file" | "tag" | "tag_theme" | "prd_template" | "room" | "message" | "comment_thread" | "time_entry" | "org" | "scoring_criterion" | "cycle"Entity the comments hang off (with entityId)
  • entityId?: uuidEntity UUID (with entityType)
  • parentId?: uuidRead this top-level comment's thread replies instead
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)
Files · 3

list_files

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.

Input
  • entityType?: "vision" | "objective" | "metric" | "opportunity" | "task" | "team" | "user" | "task_assignment" | "webhook_subscription" | "template" | "template_step" | "template_step_instance" | "template_checklist_instance" | "strategy" | "strategy_version" | "vision_version" | "notification" | "comment" | "customer" | "customer_update" | "customer_document" | "insight" | "opportunity_risk" | "file" | "tag" | "tag_theme" | "prd_template" | "room" | "message" | "comment_thread" | "time_entry" | "org" | "scoring_criterion" | "cycle"Attachment target entity type; requires entityId
  • entityId?: uuidAttachment target UUID; requires entityType
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

get_file

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.

Input
  • id: uuidFile UUID

get_file_storage_usage

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.

Input
No parameters.
Templates · 5

list_templates

List the org's template templates with cursor pagination. Templates carry no category: categories group tasks and opportunities, not templates.

Input
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

get_template

Get a template template with its ordered steps. The terminal Completed/Cancelled pair is system-managed and not included.

Input
  • id: uuidTemplate UUID

get_template_usage

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.

Input
  • id: uuidTemplate UUID

get_default_template

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.

Input
  • entityType: "task" | "opportunity" | "customer"Entity the template runs on

get_template_instance

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.

Input
  • entityType: "task" | "opportunity" | "customer"
  • entityId: uuid
Categories · 2

list_categories

List the org's categories in display order (used to group templates and to classify time entries).

Input
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

get_category

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.

Input
  • id: uuidCategory UUID
PRD templates · 2

list_prd_templates

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.

Input
  • allTeams?: boolean | "true" | "false"Return every template in the org, including other teams' (requires prd_template:update)
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

get_prd_template

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.

Input
  • id: uuidPRD template UUID
Time entries · 6

list_time_entries

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.

Input
  • from: stringRange start (YYYY-MM-DD, inclusive)
  • to: stringRange end (YYYY-MM-DD, inclusive; max 366 days after from)
  • engineerId?: uuidAnother engineer's user UUID; requires compensation:read
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

aggregate_time_entries_by_category

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.

Input
  • from: stringRange start (YYYY-MM-DD, inclusive)
  • to: stringRange end (YYYY-MM-DD, inclusive; max 366 days after from)
  • teamId?: uuidRestrict to one team's members

get_time_entry_costs_for_range

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.

Input
  • from: stringRange start (YYYY-MM-DD, inclusive)
  • to: stringRange end (YYYY-MM-DD, inclusive; max 366 days after from)
  • teamId?: uuidRestrict to one team's members

get_time_entry_cost_for_opportunity

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.

Input
  • opportunityId: uuidOpportunity UUID

get_time_entry_cost_for_task

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.

Input
  • taskId: uuidTask UUID

list_time_entries_for_opportunity

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.

Input
  • opportunityId: uuidOpportunity UUID
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)
Activity · 1

list_activities

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.

Input
  • entityType: "opportunity" | "task" | "customer" | "insight"Entity type. One of: opportunity, task, customer, insight
  • entityId: uuidEntity UUID
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)
Entity subscriptions · 1

get_subscription

Report whether the authenticated user currently follows an entity, i.e. receives its notifications. False covers both never-subscribed and explicitly muted.

Input
  • entityType: "opportunity" | "task" | "comment" | "customer" | "customer_update" | "customer_document" | "insight" | "vision" | "strategy" | "objective" | "metric" | "team" | "template" | "opportunity_risk" | "file"Entity to follow. One of: opportunity, task, comment, customer, customer_update, customer_document, insight, vision, strategy, objective, metric, team, template, opportunity_risk, file
  • entityId: uuidEntity UUID
Notifications · 2

list_notifications

List the authenticated user's notifications, newest first, with cursor pagination. Filter to unread only, pinned only, or a set of notification types.

Input
  • unreadOnly?: boolean | "true" | "false"Only return unread notifications
  • pinnedOnly?: boolean | "true" | "false"Only return pinned notifications
  • types?: string[] | stringOnly return these notification types. JSON array on MCP, comma-separated on REST and CLI. One of: strategy.published, strategy.updated, vision.published, customer.update_posted, customer.went_live, insight.assigned, insight.needs_context, insight.rejected, opportunity.completed, opportunity.cancelled, objective.committed_date_breach, cycle.scope_changed, template.step_assigned, task.handoff, task.agent_run_completed, task.agent_run_failed, task.agent_run_cancelled, comment.mention, comment.thread_reply, doc.mention, room.invited, room.mentioned, community.mentioned, good_vibes.shoutout
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

count_unread_notifications

Count the authenticated user's unread notifications.

Input
No parameters.
Webhooks · 1

list_webhook_subscriptions

List the org's outbound webhook subscriptions. Secrets are write-once and never returned.

Input
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)
Integrations · 3

list_integrations

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.

Input
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

get_integration

Get one integration's connector type, name, status, health counters, and non-secret config. Stored credentials are never returned.

Input
  • id: uuidIntegration UUID

list_integration_events

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.

Input
  • status?: "received" | "processing" | "succeeded" | "failed" | "skipped_duplicate"
  • kind?: "webhook" | "command" | "interaction" | "poll" | "outbound" | "backfill"
  • id: uuidIntegration UUID
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)
Imports · 2

list_import_runs

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.

Input
  • cursor?: stringOpaque pagination cursor from a previous page's nextCursor
  • limit?: integerItems per page (default 50, max 200)

get_import_run

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.

Input
  • id: uuidImport run UUID
Reference

Discoverable write actions

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.

set_viewer_avatar

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.

Requires
none (self-scoped: your own data only)
Input
  • avatarFileId: uuidFile UUID from confirm-file-upload
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

create_customer

Create a customer account (name plus optional profile, owner, ARR, and lifecycle status; status defaults to prospect).

Requires
customer:create
Input
  • name: string
  • logoUrl?: string
  • website?: string
  • content?: string
  • status?: "prospect" | "onboarding" | "pilot" | "live" | "churned" | "lost"
  • ownerId?: uuid
  • arr?: string
  • customerSince?: stringISO 8601 date or datetime
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

update_customer

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.

Requires
customer:update
Input
  • name?: string
  • logoUrl?: string | null
  • logoFileId?: uuid | null
  • website?: string | null
  • content?: string
  • status?: "prospect" | "onboarding" | "pilot" | "live" | "churned" | "lost"
  • health?: "healthy" | "at_risk" | "critical"
  • ownerId?: uuid | null
  • arr?: string | null
  • customerSince?: string | null
  • reason?: string
  • id: uuidCustomer UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

delete_customer

Hard-delete an account and its insights. Contacts, events, reviews, updates, and opportunity links go with it; linked opportunities survive.

Requires
customer:delete
Input
  • id: uuidCustomer UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

create_customer_contact

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.

Requires
customer:create
Input
  • name: string
  • title?: string
  • role?: "economic_buyer" | "champion" | "decision_maker" | "technical" | "end_user" | "executive" | "other"
  • email?: string
  • phone?: string
  • notes?: string
  • isPrimary?: boolean | "true" | "false"Make this the account's primary contact
  • id: uuidCustomer UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

update_customer_contact

Update a contact's details. Promoting a contact to primary demotes the account's existing primary.

Requires
customer:update
Input
  • id: uuidCustomer UUID
  • name?: string
  • title?: string | null
  • role?: "economic_buyer" | "champion" | "decision_maker" | "technical" | "end_user" | "executive" | "other"
  • email?: string | null
  • phone?: string | null
  • notes?: string | null
  • isPrimary?: boolean | "true" | "false"Make this the account's primary contact
  • contactId: uuidContact UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

delete_customer_contact

Remove a contact from a customer account. Insights attributed to the contact survive and fall back to the customer as their demand root.

Requires
customer:delete
Input
  • id: uuidCustomer UUID
  • contactId: uuidContact UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

link_customer_opportunity

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.

Requires
customer:update
Input
  • id: uuidCustomer UUID
  • opportunityId: uuidOpportunity UUID
  • stepInstanceId?: uuid | null
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

unlink_customer_opportunity

Remove the tie between an account and an opportunity. The opportunity itself survives.

Requires
customer:delete
Input
  • id: uuidCustomer UUID
  • opportunityId: uuidOpportunity UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

post_customer_update

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.

Requires
customer:update
Input
  • id: uuidCustomer UUID
  • body: stringThe update text
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

add_customer_arr_point

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.

Requires
customer:create
Input
  • id: uuidCustomer UUID
  • at: stringThe date the level took effect
  • arr: number | stringARR level from that date
  • note?: stringWhy it changed
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

delete_customer_arr_point

Remove one point from the account's ARR series (typo repair). The account's current ARR is not recomputed.

Requires
customer:delete
Input
  • id: uuidCustomer UUID
  • arrPointId: uuidARR point UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

generate_customer_review

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.

Requires
customer:update
Input
  • id: uuidCustomer UUID
  • from: stringWindow start; the window ends now
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

set_customer_onboarding

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.

Requires
customer:update
Input
  • id: uuidCustomer UUID
  • templateId?: uuidOnboarding template UUID; defaults to the org's customer default
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

advance_customer_onboarding

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.

Requires
customer:update
Input
  • id: uuidCustomer UUID
  • stepInstanceId?: uuidOnboarding milestone (template step instance) UUID
  • stepId?: uuidTemplate step UUID, resolved to this customer's milestone
  • message?: stringHandoff note
  • markComplete?: boolean | "true" | "false"Mark the milestone being left as completed
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

complete_customer_onboarding

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.

Requires
customer:update
Input
  • id: uuidCustomer UUID
  • message?: stringGo-live note
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

update_customer_onboarding_step

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.

Requires
customer:update
Input
  • id: uuidCustomer UUID
  • stepInstanceId: uuidOnboarding milestone (template step instance) UUID
  • driUserId?: uuidNew DRI user UUID
  • arrUnlock?: number | string | nullARR unlocked when the milestone completes; null clears it
  • taskId?: uuidExisting task to attach to this milestone
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

add_customer_onboarding_work

Create a new work item as a go-live requirement on a milestone. kind 'task' is bespoke per-client work (data migration, training) and requires task:create; 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.

Requires
customer:create
Input
  • id: uuidCustomer UUID
  • stepInstanceId: uuidOnboarding milestone (template step instance) UUID
  • kind: "task" | "opportunity"Bespoke client work, or a product opportunity
  • title: stringWork item title
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

create_strategy

Create a strategy (name plus a markdown content body). Starts as a draft.

Requires
strategy:create
Input
  • name: string
  • content?: string
  • status?: "draft" | "active" | "archived"
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

update_strategy

Update a strategy's name or content. An optional changeNote labels the version this edit creates. Owner-only; ACL enforced by the service.

Requires
strategy:update
Input
  • name?: string
  • content?: string
  • changeNote?: string
  • id: uuidStrategy UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

publish_strategy

Publish a draft strategy, making it the active version. Owner-only; ACL enforced by the service.

Requires
strategy:update
Input
  • id: uuidStrategy UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

archive_strategy

Archive a strategy, retiring it from the active set. Owner-only; ACL enforced by the service.

Requires
strategy:update
Input
  • id: uuidStrategy UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

rename_strategy_version

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.

Requires
strategy:update
Input
  • id: uuidStrategy UUID
  • version: integer | stringVersion number to label
  • note: stringVersion label; empty clears it
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

create_vision

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.

Requires
vision:create
Input
  • name: string
  • kind?: "company" | "product"
  • narrative: string
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

update_vision

Update a vision's name or narrative. Edits are versioned.

Requires
vision:update
Input
  • name?: string
  • narrative?: string
  • id: uuidVision UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

rename_vision_version

Label one snapshot in a vision's version history. An empty note clears the label, so the version falls back to 'v{n}'.

Requires
vision:update
Input
  • id: uuidVision UUID
  • version: integer | stringVersion number to label
  • note: stringVersion label; empty clears it
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

create_metric

Create a metric (name, unit, optional data source and steward).

Requires
metric:create
Input
  • name: string
  • description?: string
  • currentValue?: number | stringStarting value
  • unit: "percentage" | "count" | "currency" | "ratio" | "duration" | "custom"
  • dataSourceType?: "manual" | "integration" | "system"
  • dataSourceConfig?: object | null
  • stewardId?: uuid
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

update_metric

Update a metric's name, description, unit, data source, or steward.

Requires
metric:update
Input
  • name?: string
  • description?: string
  • currentValue?: number | stringLatest value
  • unit?: "percentage" | "count" | "currency" | "ratio" | "duration" | "custom"
  • dataSourceType?: "manual" | "integration" | "system"
  • dataSourceConfig?: object | null
  • stewardId?: uuid | null
  • id: uuidMetric UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

delete_metric

Permanently delete a metric.

Requires
metric:delete
Input
  • id: uuidMetric UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

record_metric_value

Record an observed value for a metric. recordedAt defaults to now; pass it to backfill history.

Requires
metric:update
Input
  • value: number | stringObserved value
  • recordedAt?: string
  • note?: string
  • source?: "manual" | "integration" | "system"
  • id: uuidMetric UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

track_builtin_metric

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.

Requires
metric:create
Input
  • key: "tasks_completed_per_week" | "task_lead_time_days" | "task_cycle_time_days" | "time_in_status_days" | "cycle_predictability" | "github_prs_per_week" | "github_commits_per_week" | "github_releases_per_week"Catalog key, from list-builtins
  • categoryId?: uuidScope to one task category
  • statusType?: "backlog" | "todo" | "in_progress" | "completed" | "cancelled" | "duplicate"Status group to measure (required for time_in_status_days)
  • userId?: uuidScope a GitHub builtin to one engineer's mapped logins
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

create_objective

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.

Requires
objective:create
Input
  • title: string
  • description?: string
  • deadline?: string
  • visionId: uuid
  • driId: uuid
  • horizon?: "short" | "long"
  • metric?: object
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

update_objective

Update an objective's title, description, deadline, DRI, or status.

Requires
objective:update
Input
  • visionId?: uuid
  • title?: string
  • description?: string
  • deadline?: string | null
  • driId?: uuid
  • horizon?: "short" | "long" | null
  • status?: "active" | "achieved" | "abandoned"
  • id: uuidObjective UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

delete_objective

Permanently delete an objective.

Requires
objective:delete
Input
  • id: uuidObjective UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

set_objective_metric

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.

Requires
objective:update
Input
  • id: uuidObjective UUID
  • metricId: uuid | nullMetric UUID to anchor to; null clears the anchor
  • operator?: "gte" | "lte" | "eq"Comparison operator. One of: gte, lte, eq
  • target?: number | stringTarget value on the metric
  • startValue?: number | string | nullValue the objective started from; null clears it
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

set_objective_alignment_link

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.

Requires
objective:update
Input
  • sourceType: "objective" | "opportunity"What is doing the serving
  • sourceId: uuidObjective or opportunity UUID
  • id: uuidObjective UUID being served
  • kind: "primary" | "supporting" | nullHow hard the source pushes. One of: primary, supporting; null clears the link
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

create_opportunity

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.

Requires
opportunity:create
Input
  • title: string
  • description?: string
  • leadId?: uuid
  • teamId?: uuid | null
  • templateId?: uuid
  • initialState?: "backlog" | "in_progress"
  • categoryId?: uuid | null
  • objectiveLinks?: object[]
  • tagIds?: string[]
  • scores?: object[]
  • dueDate?: string
  • prdTemplateId?: uuid | null
  • patternPromotion?: object
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

update_opportunity

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.

Requires
opportunity:update
Input
  • title?: string
  • leadId?: uuid | null
  • teamId?: uuid | null
  • categoryId?: uuid | null
  • dueDate?: string | null
  • prd?: string
  • id: uuidOpportunity UUID
  • tagIds?: string[]Replace the opportunity's whole tag set; omit to leave tags alone
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

delete_opportunity

Permanently delete an opportunity and its dependency edges. Prefer reject_opportunity to record a won't-build decision; delete is for mistakes, not outcomes.

Requires
opportunity:delete
Input
  • id: uuid
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

score_opportunity

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.

Requires
opportunity:update
Input
  • criterionId: uuid
  • value: integer | stringScore value on the 1-5 scale
  • id: uuidOpportunity UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

clear_opportunity_score

Clear one scorecard value on an opportunity (the total returns to null until re-scored).

Requires
opportunity:update
Input
  • criterionId: uuid
  • id: uuidOpportunity UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

link_opportunity_objective

Tie an opportunity to an objective (membership only: 'this bet claims to matter for that number'). Impact evidence lives on insights, not the link.

Requires
opportunity:update
Input
  • objectiveId: uuid
  • id: uuidOpportunity UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

update_opportunity_objective_link

Repoint an opportunity's objective link at a different objective. `objectiveId` names the link you are moving; `newObjectiveId` is where it lands.

Requires
opportunity:update
Input
  • id: uuidOpportunity UUID
  • objectiveId: uuidObjective the link points at today
  • newObjectiveId: uuidObjective the link should point at instead
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

unlink_opportunity_objective

Remove the tie between an opportunity and an objective.

Requires
opportunity:delete
Input
  • objectiveId: uuid
  • id: uuidOpportunity UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

reject_opportunity

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.

Requires
opportunity:update
Input
  • category: "no_demand" | "not_aligned" | "not_viable" | "not_now" | "duplicate" | "other"
  • reason: string
  • id: uuidOpportunity UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

post_opportunity_update

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.

Requires
opportunity:update
Input
  • id: uuidOpportunity UUID
  • body: stringThe update text
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

set_opportunity_state

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.

Requires
opportunity:update
Input
  • id: uuidOpportunity UUID
  • state: "backlog" | "in_progress" | "completed"Target lifecycle state. One of: backlog, in_progress, completed
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

add_opportunity_prototype

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.

Requires
opportunity:create
Input
  • url?: string
  • fileId?: uuid
  • title: string
  • id: uuidOpportunity UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

remove_opportunity_prototype

Detach a prototype from an opportunity by its URL. Returns the remaining prototype list.

Requires
opportunity:delete
Input
  • url: string
  • id: uuidOpportunity UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

synthesize_opportunity

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.

Requires
opportunity:update
Input
  • id: uuidOpportunity UUID
  • bypassCache?: boolean | "true" | "false"Ignore the cached draft and re-run the model
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

cast_opportunity_risk_vote

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.

Requires
opportunity:update
Input
  • risk: "value" | "usability" | "feasibility" | "viability"
  • vote: "approve" | "risky" | "reject"
  • note: string
  • id: uuidOpportunity UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

apply_opportunity_prd_template

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.

Requires
opportunity:update
Input
  • templateId: uuid
  • mode: "replace" | "append"
  • id: uuidOpportunity UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

write_opportunity_phase_handoff

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.

Requires
opportunity:update
Input
  • fromPhaseId: uuidThe finished phase's UUID
  • note: stringWhat the next owner needs to know
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

add_opportunity_phase

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.

Requires
opportunity:update
Input
  • title: string
  • targetDate?: string | null
  • ownerUserId?: uuid | null
  • id: uuidOpportunity UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

update_opportunity_phase

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.

Requires
opportunity:update
Input
  • title?: string
  • targetDate?: string | null
  • ownerUserId?: uuid | null
  • phaseId: uuidPhase UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

remove_opportunity_phase

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.

Requires
opportunity:update
Input
  • phaseId: uuidPhase UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

reorder_opportunity_phases

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.

Requires
opportunity:update
Input
  • orderedIds: string[]
  • id: uuidOpportunity UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

create_status_task

Create a named status inside one stable lifecycle type.

Requires
task:create
Input
  • type: "backlog" | "todo" | "in_progress" | "completed" | "cancelled" | "duplicate"
  • name: string
  • color: "lavender" | "info" | "blue" | "cyan" | "teal" | "green" | "lime" | "amber" | "orange" | "red" | "rose" | "pink" | "purple" | "indigo" | "neutral"
  • description?: string | null
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

update_status_task

Rename or recolor a task status. Its lifecycle type cannot change.

Requires
task:update
Input
  • id: uuid
  • name?: string
  • color?: "lavender" | "info" | "blue" | "cyan" | "teal" | "green" | "lime" | "amber" | "orange" | "red" | "rose" | "pink" | "purple" | "indigo" | "neutral"
  • description?: string | null
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

reorder_status_task

Reorder a status within its lifecycle type.

Requires
task:update
Input
  • id: uuid
  • afterId?: uuid | null
  • beforeId?: uuid | null
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

archive_status_task

Move remaining tasks to a same-type replacement, then archive the status atomically.

Requires
task:update
Input
  • id: uuid
  • replacementStatusId?: uuid | null
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

restore_status_task

Restore an archived task status.

Requires
task:update
Input
  • id: uuidTask UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

create_task

Create a task using the stable lifecycle status, optionally refined by a concrete workspace statusId (which wins). The task may attach to an opportunity or stand alone. Optionally seed subtasks, owner, team, category, template, and tags in the same call.

Requires
task:create
Input
  • title: string
  • description?: string
  • content?: string
  • status?: "backlog" | "todo" | "in_progress" | "completed" | "cancelled" | "duplicate"
  • statusId?: uuid
  • priority?: "low" | "medium" | "high" | "urgent"
  • opportunityId?: uuid
  • phaseId?: uuid | null
  • teamId?: uuid | null
  • ownerId?: uuid | null
  • categoryId?: uuid | null
  • parentTaskId?: uuid
  • subtaskPosition?: integer | stringPosition among the parent's subtasks
  • subtasks?: object[]
  • templateId?: uuid
  • onboardingStepInstanceId?: uuid
  • estimateMinutes?: integer | stringEstimate in minutes
  • estimateUnit?: "days" | "hours" | "minutes" | null
  • dueDate?: string
  • tagIds?: string[]
  • patternPromotion?: object
  • confirmedRepo?: stringRepository to dispatch an agent owner against, when prompted
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

update_task

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.

Requires
task:update
Input
  • title?: string
  • description?: string
  • content?: string
  • status?: "backlog" | "todo" | "in_progress" | "completed" | "cancelled" | "duplicate"
  • statusId?: uuid
  • priority?: "low" | "medium" | "high" | "urgent"
  • opportunityId?: uuid | null
  • phaseId?: uuid | null
  • teamId?: uuid | null
  • templateId?: uuid
  • categoryId?: uuid | null
  • estimateMinutes?: integer | string | nullEstimate in minutes; null clears it
  • estimateUnit?: "days" | "hours" | "minutes" | null
  • dueDate?: string | null
  • id: uuidTask UUID
  • tagIds?: string[]Replace the task's whole tag set; omit to leave tags alone
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

post_task_development_update

Post one development update for this task. It appears in Development Updates and reaches every customer linked through the task's insights.

Requires
task:update
Input
  • id: uuidTask UUID
  • body: string
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

delete_task

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.

Requires
task:delete
Input
  • id: uuidTask UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

set_task_status

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.

Requires
task:update
Input
  • id: uuid
  • status: "backlog" | "todo" | "in_progress" | "completed" | "cancelled" | "duplicate"
  • statusId?: uuid
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

set_task_owner

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.

Requires
task:update
Input
  • id: uuidTask UUID
  • ownerUserId: uuid | nullNew owner's user UUID, or null to leave the task unassigned
  • confirmedRepo?: stringRepository to dispatch an agent owner against, when prompted
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

assign_cycle_task

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.

Requires
task:update
Input
  • id: uuidTask UUID
  • cycleId: uuidCycle UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

move_task_to_cycle

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.

Requires
task:update
Input
  • id: uuidTask UUID
  • cycleId: uuidCycle UUID to move the task into
  • acknowledgedBreaks?: object[]The {taskId, insightId} pairs a prior PROMISE_BREAKS refusal returned
  • announceToCustomers?: boolean | "true" | "false"Post the schedule change to each acknowledged customer's activity log
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

attach_task_session

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.

Requires
task:update
Input
  • ref: stringTask ref, e.g. TF-24
  • provider: stringSession provider slug, e.g. "claude-code"
  • sessionId: stringThe provider's session id
  • model?: stringModel id used in the session
  • inputTokens?: integer | stringSession input-token total
  • outputTokens?: integer | stringSession output-token total
  • costCents?: integer | stringEstimated session cost, cents
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

create_recurring_task

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.

Requires
task:create
Input
  • name: stringLabel for the recurring-task definition (not the task title)
  • teamId: uuidTeam the generated tasks belong to
  • rrule: stringiCal RRULE string, e.g. 'FREQ=WEEKLY;BYDAY=MO' or 'FREQ=MONTHLY;BYMONTHDAY=1'
  • dtstart?: stringRecurrence start (ISO 8601). Defaults to now
  • tzid?: stringIANA timezone the cadence reads in. Defaults to UTC
  • active?: boolean | "true" | "false"Start the definition running
  • title: string
  • description?: string
  • content?: string
  • priority?: "low" | "medium" | "high" | "urgent"
  • ownerUserId: uuidRequired. Owns every task this definition generates.
  • categoryId?: uuid | null
  • templateId?: uuid | null
  • estimateMinutes?: integer | string | nullEstimate in minutes on each generated task
  • tagIds?: string[]
  • dueOffsetDays?: integer | stringDays after the occurrence the generated task is due
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

update_recurring_task

Update a definition's schedule, template, or active flag. Editing the schedule recomputes forward from now, never retroactively.

Requires
task:update
Input
  • name?: stringLabel for the recurring-task definition (not the task title)
  • teamId?: uuidTeam the generated tasks belong to
  • rrule?: stringiCal RRULE string, e.g. 'FREQ=WEEKLY;BYDAY=MO' or 'FREQ=MONTHLY;BYMONTHDAY=1'
  • dtstart?: stringRecurrence start (ISO 8601). Defaults to now
  • tzid?: stringIANA timezone the cadence reads in. Defaults to UTC
  • active?: boolean | "true" | "false"true resumes, false pauses
  • title?: string
  • description?: string
  • content?: string
  • priority?: "low" | "medium" | "high" | "urgent"
  • ownerUserId?: uuidRequired. Owns every task this definition generates.
  • categoryId?: uuid | null
  • templateId?: uuid | null
  • estimateMinutes?: integer | string | nullEstimate in minutes on each generated task
  • tagIds?: string[]
  • dueOffsetDays?: integer | stringDays after the occurrence the generated task is due
  • id: uuid
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

set_recurring_task_active

Pause or resume a recurring-task definition. Paused definitions stop materializing tasks; resuming never backfills the paused gap.

Requires
task:update
Input
  • id: uuidRecurring-task definition UUID
  • active: boolean | "true" | "false"true to resume, false to pause
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

delete_recurring_task

Delete a recurring-task definition. Tasks it already created stay; no new ones are generated.

Requires
task:delete
Input
  • id: uuidRecurring-task definition UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

create_team

Create a team. The 2-4 character prefix becomes the team's task ref prefix (e.g. ENG-42).

Requires
team:create
Input
  • name: string
  • description?: string
  • prefix?: string
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

update_team

Update a team's name, description, ref prefix, or default PRD template.

Requires
team:update
Input
  • name?: string
  • description?: string
  • defaultPrdTemplateId?: uuid | null
  • prefix?: string
  • id: uuidTeam UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

delete_team

Delete a team. Its opportunities/tasks keep their rows (team unset, refs stable); memberships and team-scoped config are removed. Refuses to delete the org's only team.

Requires
team:delete
Input
  • id: uuidTeam UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

add_team_member

Add a user to a team's roster.

Requires
team:create
Input
  • userId: uuid
  • id: uuidTeam UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

remove_team_member

Remove a user from a team's roster.

Requires
team:delete
Input
  • id: uuidTeam UUID
  • userId: uuidUser UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

update_user

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.

Requires
user:update
Input
  • name?: string
  • salaryMonthly?: number | stringMonthly salary; travels with salaryCurrency
  • salaryCurrency?: "USD" | "EUR" | "GBP" | "CAD" | "AUD" | "CHF" | "SEK" | "NOK" | "DKK" | "JPY"
  • weeklyHours?: integer | stringContracted hours per week (1-80)
  • role?: "engineer" | "pm" | "em" | "executive" | "admin" | "sales" | "operations"
  • orgRoleId?: uuid | null
  • id: uuidUser UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

create_tag

Create a tag for clustering insights. List existing tags first (list_tags) and reuse them rather than minting duplicates.

Requires
tag:create
Input
  • name: string
  • themeId?: uuid | null
  • color?: "red" | "teal" | "orange" | "lime" | "green" | "cyan" | "blue" | "indigo" | "purple" | "neutral" | null
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

update_tag

Rename a tag or change its colour. Renaming keeps every insight, task, and opportunity attached to it.

Requires
tag:update
Input
  • id: uuid
  • name?: string
  • color?: "red" | "teal" | "orange" | "lime" | "green" | "cyan" | "blue" | "indigo" | "purple" | "neutral" | null
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

delete_tag

Delete a tag and detach it from everything it labelled. Check usageCount on list first: the rows it tagged keep no record of it.

Requires
tag:delete
Input
  • id: uuidTag UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

create_tag_theme

Create a theme to group tags under (e.g. 'Clients', 'Risk'). Its colour becomes the default colour of every tag in it.

Requires
tag:create
Input
  • name: string
  • color?: "red" | "teal" | "orange" | "lime" | "green" | "cyan" | "blue" | "indigo" | "purple" | "neutral" | null
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

update_tag_theme

Rename a theme or change the colour it lends its tags.

Requires
tag:update
Input
  • id: uuid
  • name?: string
  • color?: "red" | "teal" | "orange" | "lime" | "green" | "cyan" | "blue" | "indigo" | "purple" | "neutral" | null
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

delete_tag_theme

Delete a theme. Its tags survive and fall back to their own colour; nothing is deleted with it.

Requires
tag:delete
Input
  • id: uuidTag theme UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

create_comment

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.

Requires
comment:create
Input
  • entityType: "task" | "opportunity"
  • entityId: uuid
  • body: string
  • kind?: "user" | "update"
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

update_comment

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.

Requires
comment:update
Input
  • body: string
  • id: uuidComment UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

delete_comment

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.

Requires
comment:delete
Input
  • id: uuidComment UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

set_comment_reaction

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.

Requires
comment:update
Input
  • id: uuidComment UUID
  • emoji: stringEmoji to react with
  • on: boolean | "true" | "false"true adds your reaction, false removes it
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

create_insight

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.

Requires
insight:create
Input
  • customerId?: uuid | null
  • stakeholderUserId?: uuid | null
  • externalStakeholderId?: uuid | null
  • contactId?: uuid | null
  • verbatim: string
  • source: "interview" | "support" | "sales" | "async" | "observation" | "submission" | "community"
  • moscow?: "must" | "should" | "could" | "wont" | null
  • deadline?: string | null
  • reviewTeamId?: uuid | null
  • attachToOpportunityId?: uuid
  • attachToTaskId?: uuid
  • attachToObjectiveId?: uuid
  • pinned?: boolean | "true" | "false"Pin the link created by attachToOpportunityId
  • tagIds?: string[]
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

update_insight

Update an insight's verbatim, source, demand root, contact, moscow priority, or deadline. The service keeps exactly one demand root after the merge.

Requires
insight:update
Input
  • verbatim?: string
  • source?: "interview" | "support" | "sales" | "async" | "observation" | "submission" | "community"
  • customerId?: uuid | null
  • stakeholderUserId?: uuid | null
  • externalStakeholderId?: uuid | null
  • contactId?: uuid | null
  • moscow?: "must" | "should" | "could" | "wont" | null
  • deadline?: string | null
  • id: uuidInsight UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

delete_insight

Permanently delete an insight and its links. Prefer the review lifecycle (set-status rejected/archived) to record an outcome; delete is for mistakes.

Requires
insight:delete
Input
  • id: uuidInsight UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

attach_insight

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.

Requires
insight:update
Input
  • entityType: "opportunity" | "task" | "objective"
  • entityId: uuid
  • pinned?: boolean | "true" | "false"Pin the new link
  • id: uuidInsight UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

detach_insight

Remove the link between an insight and an opportunity, task, or objective. The insight survives; only the tie goes.

Requires
insight:update
Input
  • entityType: "opportunity" | "task" | "objective"
  • entityId: uuid
  • id: uuidInsight UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

set_insight_link

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.

Requires
insight:update
Input
  • id: uuidInsight UUID
  • entityType: "opportunity" | "task" | "objective"The linked entity's kind
  • entityId: uuidThe linked entity's UUID
  • pinned?: boolean | "true" | "false"Pin or unpin the link
  • blocking?: boolean | "true" | "false"Flag or unflag the link as blocking (opportunity/task only)
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

set_insight_status

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.

Requires
insight:update
Input
  • id: uuidInsight UUID
  • status: "created" | "review" | "need_context" | "active" | "rejected" | "delivered" | "stale" | "archived"Destination review outcome
  • note?: stringWhy: the rejection reason, the context question, or a free note on any other transition
  • rejectionCategory?: "out_of_scope" | "duplicate" | "not_aligned" | "not_now" | "insufficient_value" | "other"Required when status is rejected
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

set_insight_review_team

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.

Requires
insight:update
Input
  • id: uuidInsight UUID
  • reviewTeamId: uuid | nullTeam UUID to route to, or null to clear the review team
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

set_insight_tags

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.

Requires
insight:update
Input
  • id: uuidInsight UUID
  • tagIds: string[]The complete tag set for this insight; replaces what is there
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

set_insight_impact

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.

Requires
insight:update
Input
  • id: uuidInsight UUID
  • impacts: object[]The complete impact set; replaces what is there
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

accept_insight_pattern

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.

Requires
insight:update
Input
  • patternId: uuidStable pattern UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

dismiss_insight_pattern

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.

Requires
insight:update
Input
  • patternId: uuidPattern UUID from the pattern board
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

attach_insight_pattern_to_work

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.

Requires
insight:update
Input
  • patternId: uuidStable pattern UUID
  • entityType: "opportunity" | "task"
  • entityId: uuid
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

link_new_insight_pattern_members

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.

Requires
insight:update
Input
  • patternId: uuidStable pattern UUID
  • workItemIds: string[]Pattern-work association UUIDs
  • insightIds: string[]Selected current pattern insight UUIDs
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

create_external_stakeholder

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.

Requires
insight:create
Input
  • name: string
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

create_prd_template

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.

Requires
prd_template:create
Input
  • name: string
  • content: string
  • teamId?: uuid | null
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

update_prd_template

Update a PRD template's name, content, or team scope.

Requires
prd_template:update
Input
  • id: uuid
  • name?: string
  • content?: string
  • teamId?: uuid | null
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

delete_prd_template

Delete a PRD template. Opportunities that already applied it keep their PRD content.

Requires
prd_template:delete
Input
  • id: uuidPRD template UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

create_webhook_subscription

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.

Requires
webhook:create
Input
  • url: string
  • secret: string
  • eventTypes: string[]
  • description?: string
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

update_webhook_subscription

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.

Requires
webhook:update
Input
  • id: uuid
  • url?: string
  • eventTypes?: string[]
  • description?: string
  • active?: boolean | "true" | "false"false pauses deliveries, true resumes them
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

test_webhook_subscription

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.

Requires
webhook:update
Input
  • id: uuidWebhook subscription UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

delete_webhook_subscription

Delete a webhook subscription. Deliveries stop immediately.

Requires
webhook:delete
Input
  • id: uuidWebhook subscription UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

create_template

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.

Requires
template:create
Input
  • name: string
  • description?: string
  • defaultEntityType?: "task" | "opportunity" | "customer"
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

update_template

Update a template's name, description, or default entity type.

Requires
template:update
Input
  • name?: string
  • description?: string
  • defaultEntityType?: "task" | "opportunity" | "customer" | null
  • id: uuidTemplate UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

delete_template

Delete a template template. Refused while active tasks or opportunities are using it.

Requires
template:delete
Input
  • id: uuidTemplate UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

create_template_step

Append a step to a template, ahead of the terminal pair. Owner defaults to the caller when defaultDriUserId is omitted.

Requires
template:create
Input
  • title: string
  • description?: string
  • defaultDriRole?: "engineer" | "pm" | "em" | "executive" | "admin" | "sales" | "operations"
  • defaultDriUserId?: uuid
  • slaTargetHours?: integer | stringHours the step is expected to take
  • id: uuidTemplate UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

update_template_step

Update a step's title, description, position, owner, or SLA. Terminal steps are system-managed and can't be edited.

Requires
template:update
Input
  • position?: integer | stringNew position among the template's real steps
  • title?: string
  • description?: string
  • defaultDriRole?: "engineer" | "pm" | "em" | "executive" | "admin" | "sales" | "operations" | null
  • defaultDriUserId?: uuid
  • slaTargetHours?: integer | string | nullHours the step is expected to take; null clears it
  • stepId: uuidTemplate step UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

delete_template_step

Delete a step and close the position gap. Refused for terminal steps and for steps active work is currently on.

Requires
template:delete
Input
  • stepId: uuidTemplate step UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

write_template_handoff

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.

Requires
template:update
Input
  • targetStepInstanceId: uuid
  • markComplete?: boolean | "true" | "false"Complete the steps being skipped past
  • message?: string
  • mentions?: object
  • mentionLinkUrl?: string
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

reassign_and_delete_template

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.

Requires
template:delete
Input
  • id: uuidTemplate UUID
  • replaceWithTemplateId: uuidTemplate the reassigned work moves onto
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

attach_template

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.

Requires
template:update
Input
  • entityType: "task" | "opportunity" | "customer"
  • entityId: uuid
  • templateId: uuid
  • activateFirstStep?: boolean | "true" | "false"false seeds every step pending
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

swap_template

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.

Requires
template:update
Input
  • entityType: "task" | "opportunity" | "customer"
  • entityId: uuid
  • newTemplateId: uuid
  • activateFirstStep?: boolean | "true" | "false"false seeds every step pending
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

update_template_instance_step

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.

Requires
template:update
Input
  • status?: "pending" | "active" | "complete" | "skipped"
  • driUserId?: uuid
  • stepInstanceId: uuidTemplate step instance UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

create_category

Create a category. The slug is derived from the label; color is a theme token or hex value.

Requires
category:create
Input
  • label: string
  • color: string
  • sortOrder?: integer | stringDisplay position among the org's categories
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

update_category

Update a category's label, color, or sort order. The slug never changes.

Requires
category:update
Input
  • label?: string
  • color?: string
  • sortOrder?: integer | stringDisplay position among the org's categories
  • id: uuidCategory UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

delete_category

Delete a category. The system Bug and Incident categories can't be deleted, nor can a category time entries still reference.

Requires
category:delete
Input
  • id: uuidCategory UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

mark_notification_read

Mark one of your notifications read.

Requires
none (self-scoped: your own data only)
Input
  • id: uuidNotification UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

mark_all_notifications_read

Mark all of your notifications read.

Requires
none (self-scoped: your own data only)
Input
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

pin_notification

Pin one of your notifications so it stays at the top of the inbox.

Requires
none (self-scoped: your own data only)
Input
  • id: uuidNotification UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

unpin_notification

Unpin one of your notifications.

Requires
none (self-scoped: your own data only)
Input
  • id: uuidNotification UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

unsubscribe_notification

Unsubscribe from the entity a notification came from, muting its future notifications for you.

Requires
none (self-scoped: your own data only)
Input
  • id: uuidNotification UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

create_time_entry

Log time for the authenticated user against exactly one target: an opportunity (opportunityId), a task (taskId), or a misc category (miscCategoryId, which requires a note). Hours are 0-24 per entry.

Requires
time_entry:create
Input
  • entryDate: string
  • opportunityId?: uuid | null
  • taskId?: uuid | null
  • miscCategoryId?: uuid | null
  • hours: number | stringHours worked, 0-24 per entry
  • note?: string | null
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

update_time_entry

Update one of your own time entries (entryDate, hours, note). Only the engineer who logged an entry may edit it.

Requires
time_entry:update
Input
  • entryDate?: string
  • hours?: number | stringHours worked, 0-24 per entry
  • note?: string | null
  • id: uuidTime entry UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

delete_time_entry

Delete one of your own time entries. Only the engineer who logged an entry may delete it.

Requires
time_entry:delete
Input
  • id: uuidTime entry UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

upsert_time_entry_range

Replace the authenticated user's entries in a date window with exactly these rows: every existing entry inside [from, to] is deleted first, so an empty rows array clears the window. Each row targets exactly one of opportunityId, taskId, or miscCategoryId (misc requires a note) and must fall inside the window. The window is capped at 366 days.

Requires
time_entry:update
Input
  • from: stringRange start (YYYY-MM-DD, inclusive)
  • to: stringRange end (YYYY-MM-DD, inclusive; max 366 days after from)
  • rows: object[]The complete set of entries for the window
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

create_room

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.

Requires
room:create
Input
  • name: string
  • description?: string
  • memberIds: string[]
  • visibility?: "open" | "private"
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

update_room

Rename a group channel or edit its description. Room-admin only; DMs and system rooms cannot be edited.

Requires
room:update
Input
  • name?: string
  • description?: string
  • id: uuidRoom UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

archive_room

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.

Requires
room:update
Input
  • id: uuidRoom UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

find_or_create_dm

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.

Requires
room:create
Input
  • otherUserId: uuidOrg member to open the DM with
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

add_room_member

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.

Requires
room:create
Input
  • id: uuidRoom UUID
  • userId: uuidOrg member to add
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

remove_room_member

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.

Requires
room:delete
Input
  • id: uuidRoom UUID
  • userId: uuidMember to remove
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

set_room_membership

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.

Requires
room:update
Input
  • id: uuidRoom UUID
  • joined?: boolean | "true" | "false"true joins an open group channel, false leaves it
  • pinned?: boolean | "true" | "false"Pin the room to the top of your list
  • notificationLevel?: "all" | "mentions" | "muted"How much the room notifies you: all, mentions, muted
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

create_message

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.

Requires
message:create
Input
  • roomId: uuidRoom UUID
  • body: stringMessage body (markdown, max 10000 chars)
  • parentId?: uuidTop-level message UUID to reply to in its thread
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

update_message

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. Returns the same enriched message shape create and list return.

Requires
message:update
Input
  • id: uuidMessage UUID
  • body: stringMessage body (markdown, max 10000 chars)
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

delete_message

Delete one of your own messages (soft delete; attachments are swept). Only the author may delete it on this surface.

Requires
message:delete
Input
  • id: uuidMessage UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

set_message_pinned

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.

Requires
message:update
Input
  • id: uuidMessage UUID
  • pinned: boolean | "true" | "false"true pins the message, false unpins it
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

set_message_reaction

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.

Requires
message:update
Input
  • id: uuidMessage UUID
  • emoji: stringEmoji to react with
  • on: boolean | "true" | "false"true adds your reaction, false removes it
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

set_subscription

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.

Requires
none (self-scoped: your own data only)
Input
  • entityType: "opportunity" | "task" | "comment" | "customer" | "customer_update" | "customer_document" | "insight" | "vision" | "strategy" | "objective" | "metric" | "team" | "template" | "opportunity_risk" | "file"Entity to follow. One of: opportunity, task, comment, customer, customer_update, customer_document, insight, vision, strategy, objective, metric, team, template, opportunity_risk, file
  • entityId: uuidEntity UUID
  • subscribed: boolean | "true" | "false"true follows the entity, false mutes it
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

request_file_upload

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.

Requires
file:manage
Input
  • filename: string
  • mimeType: string
  • size: integer | stringByte length of the file you are about to upload
  • sha256: string
  • entityType?: "vision" | "objective" | "metric" | "opportunity" | "task" | "team" | "user" | "task_assignment" | "webhook_subscription" | "template" | "template_step" | "template_step_instance" | "template_checklist_instance" | "strategy" | "strategy_version" | "vision_version" | "notification" | "comment" | "customer" | "customer_update" | "customer_document" | "insight" | "opportunity_risk" | "file" | "tag" | "tag_theme" | "prd_template" | "room" | "message" | "comment_thread" | "time_entry" | "org" | "scoring_criterion" | "cycle"
  • entityId?: uuid
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

confirm_file_upload

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.

Requires
file:manage
Input
  • etag: string
  • id: uuidFile UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

attach_file

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.

Requires
file:manage
Input
  • entityType: "vision" | "objective" | "metric" | "opportunity" | "task" | "team" | "user" | "task_assignment" | "webhook_subscription" | "template" | "template_step" | "template_step_instance" | "template_checklist_instance" | "strategy" | "strategy_version" | "vision_version" | "notification" | "comment" | "customer" | "customer_update" | "customer_document" | "insight" | "opportunity_risk" | "file" | "tag" | "tag_theme" | "prd_template" | "room" | "message" | "comment_thread" | "time_entry" | "org" | "scoring_criterion" | "cycle"
  • entityId: uuid
  • id: uuidFile UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

detach_file

Remove one entity's link to a file. The file itself survives with its other attachments; use delete to destroy it.

Requires
file:manage
Input
  • entityType: "vision" | "objective" | "metric" | "opportunity" | "task" | "team" | "user" | "task_assignment" | "webhook_subscription" | "template" | "template_step" | "template_step_instance" | "template_checklist_instance" | "strategy" | "strategy_version" | "vision_version" | "notification" | "comment" | "customer" | "customer_update" | "customer_document" | "insight" | "opportunity_risk" | "file" | "tag" | "tag_theme" | "prd_template" | "room" | "message" | "comment_thread" | "time_entry" | "org" | "scoring_criterion" | "cycle"
  • entityId: uuid
  • id: uuidFile UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

delete_file

Permanently delete a file: the stored object, its thumbnail, the row, and every attachment pointing at it. Irreversible.

Requires
file:manage
Input
  • id: uuidFile UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

create_external_blocker

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.

Requires
task:update
Input
  • itemKind: "opportunity" | "task"Work item type the blocker hangs off
  • itemId: uuidWork item UUID
  • awaiting: "stakeholder" | "client" | "partner"Who the work is waiting on
  • contactId?: uuid | nullCustomer contact UUID being waited on
  • note?: string | nullWhy the work is waiting
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

resolve_external_blocker

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.

Requires
task:update
Input
  • blockerId: uuidExternal blocker UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

link_message_source

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.

Requires
task:update
Input
  • entityType: "task" | "opportunity" | "insight"Work item type the link hangs off
  • entityId: uuidWork item UUID
  • messageId: uuidSource message UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

cancel_agent_run

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.

Requires
task:update
Input
  • id: uuidAgent run UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

update_cycle_default_estimate

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.

Requires
cycle:manage
Input
  • defaultEstimateMinutes: integer | stringMinutes an unestimated task counts for (1 to twenty workdays)
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

configure_cycle

Configure cycle planning for the organisation. Cycle configuration is workspace administration and requires cycle:manage.

Requires
cycle:manage
Input
  • enabled: boolean | "true" | "false"Whether cycle planning is enabled for the organisation
  • mode: "org" | "team"Whether cycles are organisation-wide or per enabled team
  • autoRollover: boolean | "true" | "false"Whether completed cycles automatically roll unfinished work forward
  • durationWeeks: integer | stringCycle duration in whole weeks (1–8)
  • cooldownWeeks: integer | stringWhole-week gap between adjacent cycles (0–3)
  • upcomingCount: integer | stringNumber of active or future cycles to keep generated
  • startDate: stringUTC calendar anchor for the organisation cycle grid
  • teamConfigs?: object[]Per-team cadence settings when mode is team
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

rollover_cycle

Explicitly move unfinished work from an ended cycle to its next window. This can resume a cycle parked because automatic rollover is off.

Requires
cycle:manage
Input
  • id: uuidCycle UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

commit_cycle

Capture the one-shot opening commitment digest for a cycle. The baseline cannot be replaced after commitment.

Requires
cycle:manage
Input
  • id: uuidCycle UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

update_cycle

Change the identity, grid, or half-open window of an open, uncommitted future cycle.

Requires
cycle:manage
Input
  • id: uuidCycle UUID
  • name?: string | null
  • goal?: string | null
  • startsAt?: string
  • endsAt?: string
  • scope?: "org" | "team"
  • teamId?: uuid | null
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.

delete_cycle

Delete an open, uncommitted future cycle only when no task of any status references it.

Requires
cycle:manage
Input
  • id: uuidCycle UUID
  • idempotencyKey?: stringStable key for safe retries — a repeat returns the first result instead of writing again.