Meetings
Meetings let a project or group capture a meeting transcript and store it server-side. The stored copy is DLP-redacted under the strictest of the caller’s org policies, participants can be resolved to org members or personas, transcripts can be extracted into action items and decisions, and those action items can be promoted to tickets. Transcripts age out under an org-level retention window.
The meetings feature is available on every tier. Group-scoped meetings additionally require the project_groups feature (Team+), and promoting an action item to a ticket requires agent_tickets (every plan).
Every meeting belongs to exactly one of:
- a project (
project_id), or - a group (
group_id).
That is the meeting’s scope. Its owning org is taken from the scope; a personal project has none.
Access follows the scope’s normal standing:
| Action | Project meeting | Group meeting |
|---|---|---|
| List / get | project read (or a service key with meetings:read + project allowlist) |
group reader (user key) |
| Upload | project edit (or a service key with meetings:write + project allowlist) |
group editor or admin |
| Delete | uploader or project admin (a service key’s backing user must be one of these) | uploader or group admin |
| Override participant | project editor | group editor or admin |
Group meetings always require a user key. There are no org-wide meetings.
Upload and DLP
Section titled “Upload and DLP”POST uploads a meeting with a title (1–200 chars), content (the transcript text), an optional meeting_at / duration_minutes, and a source (manual_paste or file_upload). Set extract: true to run action-item extraction in the same request. WebVTT and SRT content has its cue timing stripped to Speaker: text lines whichever source it arrives with, so a caption file pasted as text is stored the same as one uploaded as a file (the signature check is exact; plain text is never changed). In the dashboard, a pasted transcript you don’t name gets its first line as the title when that reads like a heading, otherwise Meeting on <date>; you can edit it before saving.
The request body is streamed with a hard cap of 1 MiB; the normalised content is capped at 512 KiB and 10 000 lines. Over the cap the upload is refused with 413 meeting_too_large.
Before storage the title and content are normalised (newlines collapsed in the title, CRLF → LF, NULs and lone surrogates stripped, trailing whitespace trimmed per line) and scanned under the strictest union of every org the caller is an active member of, plus the scope org. Secrets are always scanned. A block-mode finding refuses the upload with 422 dlp_blocked (counts by finding type). In every other mode — redact, warn, or no policy at all — secrets are redacted before storage and counted in dlp_findings. The stored title and content are the only copy kept — the raw text is never persisted.
Re-uploading identical content still stores a new meeting, with warnings: ["duplicate_of: meet_…"] pointing at the earlier one.
Participant resolution
Section titled “Participant resolution”Each named participant in the transcript is resolved over the meeting’s scope (its org), in order:
- an exact email matching an active org member,
- an exact persona name (project personas first, then the org’s personas the caller can see),
- a display name matching exactly one active member,
- the exact email of an active external contact of the org,
- a name or alias matching exactly one active external contact,
otherwise the participant stays unresolved. Extraction never creates a contact: unresolved names wait in the org’s People to confirm queue until a person creates the contact or adds the name as an alias. A persona that represents a member fills both persona_name and user_id. Personal meetings have no org, so only a project persona resolves; otherwise participants stay unresolved until overridden.
A project or group editor can override any participant manually with exactly one of user_id, persona_name or contact_id (an active contact of the meeting’s org), which sets the resolution method to manual and records who made the override.
Action-item extraction
Section titled “Action-item extraction”Extraction turns a stored transcript into a list of participants and items (each an action or decision) with one LLM call. Run it on upload with extract: true, or later with POST /api/v1/meetings/{meeting_id}/extract.
The model is given the meeting date (its meeting_at, else the day it was stored) so relative due dates such as “by Friday” resolve to the right day, and is asked for names, owners, due dates and 1-based line citations, but everything it returns is validated against the stored text before it is persisted: participants are resolved server-side (same order as participant resolution), and each item must cite a valid line range (at most 40 lines apart). An item whose owner matches no participant (or whose owner isn’t named in the cited lines) is kept with no owner: owner_participant_id is null and owner_hint carries the owner text the model gave (DLP-redacted, never used to assign anyone). The dashboard shows it as Owner unknown and asks you to choose an assignee when you create a ticket from it. dropped_items counts only entries that fail validation (bad kind, line range, date or field types).
Extraction uses the same LLM key resolution and monthly token budget as the session helper. With no key configured it returns 409 llm_key_required; an exhausted org budget returns 429 helper_budget_exceeded. An organization can run 4 extractions at a time across all servers by default (a personal project counts against its owner); beyond that extraction returns 429 org_concurrency_limit with Retry-After (see Tenancy and Isolation). An upload with extract: true is still stored and its response carries the warning extraction_skipped: org_concurrency_limit. AI usage is recorded as meeting_extract, separately from helper chats, and counts against the same org budget. The provider call has a 240-second deadline (reasoning models can need over two minutes on an hour-long transcript); past it the meeting is marked failed with timeout. A provider failure or an unparseable response marks the meeting extraction_status: "failed" with a classified extraction_error and leaves existing items untouched.
Re-running replaces pending items and every non-manual participant; promoted / dismissed / linked items and manually overridden participants are kept, so a re-extract never undoes a promotion, a dismissal or a link.
Existing tickets: suggest, never auto-link
Section titled “Existing tickets: suggest, never auto-link”Extraction also looks for tickets that already exist. Before the model call the server shortlists up to 30 tickets that match the transcript text (a deterministic BM25 ranking over the newest 500 tickets by last update) and passes their id, status and title to the model in a delimited block; ticket titles are treated as untrusted text exactly like transcript lines. Candidates come from the meeting’s own project, or for a group meeting from the group’s member projects you can read. Cancelled tickets are never candidates; done or closed tickets are, if they were resolved in the last 90 days, so “this was already fixed” shows up.
The model may name one shortlisted ticket per item. Any other id is dropped. When the model names none, the server compares the item’s own text with ticket titles and suggests the best match only when it is clearly ahead (a weighted word-overlap score of at least 0.5, at least two shared words, and a clear lead over the next ticket). The item then carries suggested_ticket (id, project_id, title, status), suggestion_source (model or text) and suggestion_score. Visibility is checked on every read: if you can no longer read the ticket from this meeting’s scope, the suggestion is simply absent.
Nothing is linked automatically. In the dashboard a pending item with a suggestion shows Looks like tk_… · title · status with three choices:
- Link ties the item to that ticket: the item becomes
linked, the ticket gets a comment (Raised again in meeting "<title>" on <date>, lines L<a>–<b>: <item text>, DLP-redacted with your org’s strictest policy) and a timeline event, and any reminders on the item also show on the ticket. - Not this one hides the suggestion; the item stays pending.
- Create new is the usual promote flow below.
Any pending item can also be linked to a ticket you pick (Link to ticket…), with or without a suggestion. The ticket must be in the meeting’s project (for a group meeting, a member project you can read) and you must be able to comment on it; any other ticket is refused with the same 422 ticket_not_linkable, which never says whether the ticket exists. Unlink puts a linked item back to pending; the comment stays on the ticket (comments are append-only) and the ticket’s timeline records the unlink.
Promote to a ticket
Section titled “Promote to a ticket”A pending action item (never a decision) can be promoted to a ticket. First build a confirmable draft with POST /api/v1/meetings/{meeting_id}/action-items/{item_id}/promote/preview, then confirm with POST .../promote (body: title ≤ 200, description, priority, kind, optional assigned_to / assigned_user_id / due_date, and for a group meeting target_project_id). The preview also returns jira_publish_available (true only when the plan includes Jira, you can edit the target project, the org has an enabled Jira connection and the project is mapped; computed from the database, never by calling Atlassian). For a group meeting it is judged for the project passed as ?target_project_id= and is false without one. The draft title is the item’s first sentence; the description carries the item text plus a From meeting … provenance header and the quoted source lines.
The target project is the meeting’s own project for a project meeting, or an editable project the caller chooses from the group for a group meeting (omitting it returns 422 target_project_required). Confirming creates the ticket, flips the item to promoted with promoted_ticket_id, and — with publish_to_jira: true — publishes it to Jira. A concurrent promote is refused with 409 item_not_pending.
Dismiss a pending item with POST .../dismiss (a second dismiss returns 409 item_not_pending) and bring it back with POST .../restore (409 item_not_dismissed if it was not dismissed).
Reminders
Section titled “Reminders”Extraction never creates reminders, but an action item or decision with a due date carries a suggested_reminder (title, due_date, due_at at 09:00 UTC on that date, the cited lines, and owner_user_id when the item’s owner resolved to a person). POST /api/v1/projects/{project_id}/meetings/{meeting_id}/items/{item_id}/reminder (or /api/v1/groups/{group_id}/… for a group meeting) creates it, pre-filled from the suggestion; pass title, due_at, timezone or owner to change it. The reminder points back at the item and its lines. When the item is later promoted, its reminders are linked to the new ticket and also show on the ticket. GET …/meetings/{meeting_id}/reminders lists the reminders on a meeting and its items that you can see (your own, or ones you set for someone else). Group meetings follow the group’s visibility rules.
sfs meeting is a thin client of the REST routes below; the server enforces every access rule. Without --project or --group it uses the project of the current repository.
sfs meeting upload standup.vtt --title "Standup" --date 2026-10-29 --extractsfs meeting upload --stdin --title "Planning" --group pg_abc123 < notes.txtsfs meeting list --query roadmapsfs meeting show meet_0123456789abcdef # numbered transcript, participants, itemssfs meeting resolve-participant meet_… mp_… --persona atlassfs meeting resolve-participant meet_… --file people.txt # mp_…,user:<id> | persona:<name> | contact:<id> per linesfs meeting promote meet_… mi_… --assignee atlas --due 2026-11-05 # shows the draft, asks to confirmsfs meeting promote meet_… mi_… --target-project proj_… --yes # group meeting, no promptsfs meeting link meet_… mi_… tk_… # tie to an existing ticket (asks first)sfs meeting link meet_… mi_… # use the item's suggestionsfs meeting unlink meet_… mi_…A file upload is stored with source=file_upload; --stdin uses manual_paste. .vtt and .srt cue timing is stripped server-side either way. promote always prints the ticket draft first and asks before creating it unless --yes is given; link likewise shows the ticket and the comment it will add before linking. action-items lists each pending item’s suggestion and each linked item’s ticket. See the CLI reference for every command.
MCP tools
Section titled “MCP tools”Seven tools cover the same flows for agents: upload_meeting_transcript, list_meeting_transcripts, get_meeting_transcript, extract_meeting_action_items, promote_meeting_action_item, link_meeting_action_item and search_meetings. A project is picked by git_remote or the workspace, a group by group_id (user keys only). promote_meeting_action_item and link_meeting_action_item are preview only unless confirm=true; confirming a link also needs the ticket_id the preview returned. Extraction (extract_meeting_action_items, or extract=true on upload) uses your own LLM key. See MCP Server. Chat threads use the same tools; see Chat threads.
Session helper
Section titled “Session helper”The session helper can read meetings you have access to: search_meetings, list_recent_meetings, get_meeting (numbered lines) and list_my_meeting_actions (pending action items owned by you or by a persona that represents you). It is read-only, honours the conversation’s scope, and cites passages as [meeting:<id> L<first>-<last>], which are checked against the meeting’s real line count before the answer is shown.
Retention and deletion
Section titled “Retention and deletion”DELETE soft-deletes a meeting. A scheduled retention sweep hard-deletes:
- live meetings older than their org’s
general.meeting_retention_days(default 180 days, configurable 1–3650), and - soft-deleted meetings older than 30 days.
Participants and action items are deleted with the meeting. Uploads, deletes and manual overrides are recorded in the org audit trail (ids and counts only — never transcript text); personal meetings are audited too, with a null org_id.
REST API
Section titled “REST API”| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/v1/projects/{project_id}/meetings |
meetings:write |
Upload a project meeting |
| GET | /api/v1/projects/{project_id}/meetings |
meetings:read |
List project meetings, newest first (?q= substring search); each entry carries extraction_status, extraction_error, pending_action_count and decision_count |
| POST | /api/v1/groups/{group_id}/meetings |
user key, group editor/admin | Upload a group meeting |
| GET | /api/v1/groups/{group_id}/meetings |
user key, group reader+ | List group meetings, newest first (?q=); same entry fields as the project list |
| GET | /api/v1/meetings/{meeting_id} |
meetings:read |
Get a meeting with content, participants and action items |
| DELETE | /api/v1/meetings/{meeting_id} |
meetings:write |
Soft-delete a meeting |
| PUT | /api/v1/meetings/{meeting_id}/participants/{participant_id} |
user key | Manually override a participant |
| POST | /api/v1/meetings/{meeting_id}/participants/resolve-bulk |
user key | Override up to 100 participants at once (body {"participants": [{"participant_id", one of "user_id" / "persona_name" / "contact_id"}]}), all or nothing: any refused item means nothing changes and a 422 bulk_item_failed lists each item. Works for chat threads with ?kind=chat |
| POST | /api/v1/meetings/{meeting_id}/extract |
meetings:write |
Extract participants and action items (returns dropped_items) |
| POST | /api/v1/meetings/{meeting_id}/action-items/{item_id}/promote/preview |
meetings:write |
Draft a ticket from an action item |
| POST | /api/v1/meetings/{meeting_id}/action-items/{item_id}/promote |
meetings:write |
Promote an action item to a ticket (agent_tickets) |
| POST | /api/v1/meetings/{meeting_id}/action-items/{item_id}/dismiss |
meetings:write |
Dismiss a pending action item |
| POST | /api/v1/meetings/{meeting_id}/action-items/{item_id}/restore |
meetings:write |
Restore a dismissed action item |
| POST | /api/v1/meetings/{meeting_id}/action-items/{item_id}/link/preview |
meetings:write + tickets:write |
The ticket and the comment Link would add (body {"ticket_id"}, omitted = the item’s suggestion); writes nothing |
| POST | /api/v1/meetings/{meeting_id}/action-items/{item_id}/link |
meetings:write + tickets:write |
Link a pending item to an existing ticket (comment + timeline event); 409 item_not_pending if it is no longer pending |
| POST | /api/v1/meetings/{meeting_id}/action-items/{item_id}/unlink |
meetings:write + tickets:write |
Put a linked item back to pending (409 item_not_linked otherwise) |
| GET | /api/v1/meetings/{meeting_id}/linkable-tickets |
meetings:write + tickets:write |
Tickets an item can be linked to (?q= title or id prefix, limit ≤ 50); cancelled tickets left out |
| POST | /api/v1/meetings/{meeting_id}/action-items/{item_id}/dismiss-suggestion |
meetings:write |
Hide the item’s suggested ticket (Not this one); the item stays pending |
| POST | /api/v1/projects/{project_id}/meetings/{meeting_id}/items/{item_id}/reminder |
reminders:write |
Create the reminder an item suggests |
| POST | /api/v1/groups/{group_id}/meetings/{meeting_id}/items/{item_id}/reminder |
user key, group reader+ | The same for a group meeting |
| GET | /api/v1/projects/{project_id}/meetings/{meeting_id}/reminders |
reminders:read |
Your reminders on a project meeting and its items |
| GET | /api/v1/groups/{group_id}/meetings/{meeting_id}/reminders |
user key, group reader+ | Your reminders on a group meeting and its items |
Org setting
Section titled “Org setting”GET /api/v1/orgs/{org_id}/settings # includes meeting_retention_days (null = platform default 180)PUT /api/v1/orgs/{org_id}/settings # {"meeting_retention_days": 365}