Authorization: Bearer sk_atoll_... unless noted. The public
MCP plugin profile also accepts OAuth 2.1 access tokens bound to the Atoll MCP
resource; those connections select one explicitly authorized agent profile on
each actor-dependent call.
The Google Chat link-token endpoint requires an authenticated human web session,
and the Google Chat event callback requires a Google-signed OIDC ID token.
Authentication
For agent keys,
auth.projectAccess[] reports effective view, edit, or admin access. Project-scoped agents intentionally remain org guests, membership changes are read live, and standard agent keys normally have scopes: []. OAuth-bound agent responses also include non-secret auth.oauth provenance fields documented in the fields reference. profile_ref is an opaque selector, not a credential.
Organizations
Projects
A successful project create also creates Backlog, Todo, In Progress, and Done
columns; a Default board view containing those columns; and All Tasks, My
Tasks, and Recently Updated custom views. If any default cannot be created, the
transaction rolls back and no partial project remains.
Project access
Adding a project member accepts
{ "memberId": "...", "accessLevel": "view" | "edit" | "admin" }. If accessLevel is omitted, Atoll defaults it to edit; invalid values return 400.
Issues
Issue-centric initiative links follow both resource boundaries. The collection
read requires access to the issue, omits linked initiatives the caller cannot
read, and returns
200. For project-bound issues, linking and unlinking require
edit/admin access to the issue project, which must already be linked to the
initiative. Eligible non-guests may link or unlink writable projectless issues.
Every mutation also requires edit/admin access to every project linked to the
initiative. Directly requested unreadable resources on mutations are concealed
as 404.
Project reads and writes have different authorization thresholds. Visibility or
view access permits reads; issue creation requires an explicit effective
edit or admin grant on the target project.
Task Activity returns { data, items, limit, offset, hasMore }. limit defaults
to 50 and is clamped to 1..100; offset defaults to 0. The timeline
contains meaningful task history only, including issue lifecycle, comments,
assignees, labels, dependencies, initiative/target links, GitHub PR links and
updates, attachments, and subtasks. Notification, webhook, realtime, and delivery
records are separate concerns and are excluded. Pre-contract rows may provide
partial history because older events are not fabricated or backfilled.
The existing issue-detail route accepts a UUID, bare number, #number,
ATOLL-number, TSK-number, or an unambiguous project-derived prefix. It filters candidates
through the caller’s current project access before deciding whether the
reference is unique and never matches titles or partial text. Invalid, missing,
and ambiguous references return structured recovery data without disclosing
inaccessible candidates. The two initiative issue-link POST routes below also
resolve issue references and persist canonical UUIDs; the initiative
milestone-link POST route resolves a milestone UUID or exact name; other
mutation routes remain UUID-addressed.
For project-bound issues, PR-link reads require project access and creation
requires edit or admin access. Eligible non-guests may read and create links
for projectless issues. Authorization is bound to the issue’s current parent
before child reads or writes and occurs before URL parsing or GitHub metadata
lookup. Atoll accepts canonical GitHub pull request URLs, extracts the
owner/repo/number, and rejects malformed or non-PR URLs with 400. When GitHub
metadata is available, Atoll stores the current PR title, status, canonical URL,
and head SHA during attach; webhooks keep linked PR state fresh after subsequent
GitHub events.
Issue list filters include status, priority, projectId, assigneeId, teamId, milestoneId, q (title and description for full issue lists), open, includeArchived, orderBy, orderDir, limit, offset, and the optional response shape controls below. open=true excludes terminal statuses done and cancelled, plus archived issues, while preserving custom and other non-terminal statuses. It takes precedence over includeArchived.
Compact issue views (view=board or view=list) also support board-toolbar filters: assignee (unassigned or member ID, including multi-assignee links), initiativeId, scope (mine or blocked), and q over title plus issue number.
Full issue-list items include the canonical project-prefixed identifier and
collision-free projectSlug for project issues, or null for projectless
issues. Compact view=board and view=list items do not include these fields.
Main list endpoints keep their legacy resource-key JSON by default. Add shape=envelope or response_shape=cli to opt into the CLI-compatible envelope: { resource, items, total, limit, offset, nextOffset, truncated, hint }.
Create, update, and bulk-create accept recurrenceDays as a unique array of
weekday values from mon through sun when recurrenceType is weekly.
Issue reads normalize the selected days into calendar order and expose both
recurrence_days and recurrence_schedule: { type, interval, days }.
Unrelated issue updates preserve the existing recurrence schedule.
Comments, subtasks, dependencies, labels, and attachments
When a blocker changes projects, PATCH the issue with one explicit
dependencyReleaseMappings: [{ dependencyId, releaseColumnId }] entry for each
blocking dependency. Destination columns must belong to the new project; the
transaction rejects incomplete mappings and projectless moves with blockers.
The top-level aliases dependency_release_mappings and legacy
releaseColumnMappings, plus item aliases dependency_id and
release_column_id, remain supported.
The dependency GET response contains blocking and blockedBy arrays. Each
entry has id, createdAt, and an authorized issue projection with id,
number, identifier, projectSlug, title, and status. issue is null for an
inaccessible target; a projectless authorized target remains visible with both
issue.identifier: null and issue.projectSlug: null. Each entry also includes
releaseColumnId (with release_column_id compatibility alias), a releaseColumn projection, and satisfied when the blocking issue is authorized. A blocking target projection may be issue: null independently when the dependent target is inaccessible. POST defaults the
release point to the blocking project’s done column; PATCH accepts
{ "releaseColumnId": "uuid" } or { "release_column_id": "uuid" }.
The blocking issue must belong to a project; a projectless issue may be the
blocked target.
The dependency-release migration backfills existing dependencies to the
blocking project’s done column before enforcing the persistent reference.
During a rolling deployment, compatibility reads may omit release fields from
older rows; treat missing release metadata as the legacy open-blocker behavior
until the migration is applied.
Issue comments inherit issue project permissions: guests cannot access comments on unprojected issues, and write operations require write access to the issue’s project.
Comment bodies accept Markdown/plain text or existing rich-text HTML. Atoll stores and returns comment bodies as sanitized HTML. If sanitization leaves no visible text or safe media, the request returns 400 with body is required for direct comments or comment_body is required for issue updates with comment_body.
Structured mentions are recommended for agents and integrations. Direct comments accept mentions: [{ "member_id": "member-id" }]; issue update comments accept comment_mentions: [{ "member_id": "member-id" }]. Use the stable Atoll org member ID, not an auth user ID or display name. Markdown and HTML atoll:member links remain backward-compatible.
List-comment responses include comments[].mentioned_members, an array of
{ id, display_name, type } recipient summaries for persisted mentions. The
array is empty when none are recorded. The single-comment route does not
currently include this list-only enrichment.
Responses that create comments include outcome.persistence: { status: "persisted", comment_id } and outcome.mentions, with the legacy top-level mentions alias. created counts new notification rows, deduped counts idempotently reused rows, and notification_rows.status: "failed" reports notification setup failure without changing persisted comment state. transport.dispatch: "scheduled" means asynchronous Google Chat work was scheduled, not delivered; this also covers repairing a missing durable delivery row. already_scheduled means an existing delivery row was found and no new scheduling was needed. transport.final is null while any final delivery is unknown, and is mixed when all recipient deliveries are terminal but differ. Inspect recipients[].transport.final for mixed results. transport.error exposes a safe error code and retryable flag when status lookup or scheduling fails. Each skipped target includes member_id and reason.
Set reply_to_comment_id to reply to a non-deleted comment on the same issue.
List, create, and single-comment responses expose that ID plus a shallow
reply_to_comment parent containing its id, body, author identity, and
source_metadata. Replies remain flat in presentation; a reply cannot change
its parent after creation. Structured mentions continue to fan out normally,
and the parent author receives a direct-reply notification unless they authored
the reply.
Only agent-authored comments can set source_metadata. It is a strict object
with harness, at least one of thread_id or session_id, and optional
host_id; unknown fields are rejected. These identifiers are routing context,
not a place for credentials, prompts, or arbitrary runtime state. Omit
source_metadata unless the host exposes a real thread or session ID, and
never invent one.
Attachments also inherit issue project permissions: project-scoped reads
require project access, while upload and delete require edit or admin.
Guests cannot access attachments on unprojected issues; non-guests follow the
org-level issue rule and may read, upload, and delete them.
List and upload responses include id, filename, file_size, mime_type,
uploaded_by, created_at, and a relative url; storage bucket/path fields
are not exposed. Resolve url against the Atoll base URL and resend the same
authentication. This is an authenticated API path, not a public or transferable
storage URL. Clients that previously consumed absolute public URLs must migrate.
Empty files return 400; files over 10 MiB return 413. Declared images must
be signature-valid PNG, JPEG, GIF, or WebP. SVG and other declared image types
return 400. Non-image files are accepted, but only validated raster images
are served inline with nosniff; other files download as
application/octet-stream. Content responses are private and no-store.
Upload durably prepares the exact reconciliation input before sending bytes to
Storage, activates it after upload, and only then attempts the attachment row.
If request-time outcomes remain unverified, the service worker resumes the
state machine. Cleanup is tombstoned under the same object lock as creation
before bytes are removed.
User deletion atomically retires surviving create work before removing the row,
and tombstone expiry performs a final idempotent Storage removal.
Direct attachment deletion and permanent issue, project, or organization
deletion commit the attachment-row or parent cascade first and atomically queue
both transitional and private bucket paths for cleanup. A
service-authenticated worker processes bounded due jobs every 15 minutes and
retries provider failures. Direct deletion also attempts cleanup immediately;
when it is deferred, the endpoint returns 202 with
{ "ok": true, "cleanup_pending": true }. Parent deletion returns after
durable queueing, so physical object cleanup may still be pending.
Milestones
Project-bound milestone reads require effective project access. Create and update
require
edit or admin project access. Unreadable milestones are concealed as
404; insufficient write access on a readable project returns 403. Milestone
deletion remains restricted to organization owners and admins. Create requests
require name and may include description, due_date/dueDate, and
status (active or closed); the status is persisted atomically with the
milestone.
Goals, KPIs, and initiatives
KPI collections are organization-wide Strategy resources. Owners and admins
can read and write them. Other non-guest organization members can read KPIs,
snapshots, and redacted per-KPI sync metadata, but cannot create, update,
delete, or record snapshots. Guest and project-scoped agents receive
403 for
the collection; direct KPI routes, snapshot routes, and per-KPI sync read/draft
routes conceal unreadable objects as 404. Per-KPI sync draft creation and
proposed-config validation require a readable KPI and keep the existing
draft-only, no-network agent restrictions.
Detail responses include read-only relationship projections:
GET /api/orgs/{id}/initiatives/{initiativeId}embedsinitiative.kpi_impactsonly for linked KPIs readable by the caller. Unreadable KPI relationships are omitted.GET /api/orgs/{id}/kpis/{kpiId}embedskpi.initiative_impactsfor visible initiatives across all initiative statuses, using project-aware initiative filtering.
attributed_to_initiative_id for measurement attribution.
| GET | /api/orgs/{id}/initiatives/{initiativeId}/targets | List initiative targets |
| POST | /api/orgs/{id}/initiatives/{initiativeId}/targets | Create target |
| GET | /api/orgs/{id}/initiatives/{initiativeId}/targets/{targetId} | Get target |
| PATCH | /api/orgs/{id}/initiatives/{initiativeId}/targets/{targetId} | Update target |
| DELETE | /api/orgs/{id}/initiatives/{initiativeId}/targets/{targetId} | Delete target |
| GET | /api/orgs/{id}/initiatives/{initiativeId}/targets/{targetId}/issues | List readable target issue links, including readable projectless issues for non-guests |
| POST | /api/orgs/{id}/initiatives/{initiativeId}/targets/{targetId}/issues | Link issue by UUID, number, #number, ATOLL-number, TSK-number, or unambiguous project-derived prefix; the issue project must already be linked to the initiative, while eligible non-guests may link writable projectless issues |
Initiative issue-link POST bodies accept an issue UUID, bare issue number,
#number, ATOLL-number, TSK-number, or an unambiguous project-derived prefix. Initiative
target issue-link POST bodies use the same formats. Initiative milestone-link
POST bodies accept either the milestone UUID or its exact name. Resolution is
limited to the initiative’s authoritative project scope; cross-organization,
cross-project, unreadable, and projectless-milestone references return 404.
Successful writes store the canonical resource UUID. Missing identifiers return
400 with the route-specific *_id is required message; malformed identifiers
return 400 with Invalid resource identifier; ambiguous references return
409 with Resource identifier is ambiguous; concealed or missing references
return 404 with Not found; and resolver failures return 500 with
Failed to resolve resource access.
| DELETE | /api/orgs/{id}/initiatives/{initiativeId}/targets/{targetId}/issues/{issueId} | Unlink issue from target; a project-bound issue’s project must already be linked to the initiative, while eligible non-guests may unlink writable projectless issues |
| GET | /api/orgs/{id}/initiatives/{initiativeId}/targets/{targetId}/milestones | List readable project-bound target milestone links; projectless milestones are unsupported |
| POST | /api/orgs/{id}/initiatives/{initiativeId}/targets/{targetId}/milestones | Link milestone to target; its project must already be linked to the initiative, and projectless milestones are unsupported |
| DELETE | /api/orgs/{id}/initiatives/{initiativeId}/targets/{targetId}/milestones/{milestoneId} | Unlink milestone from target; its project must already be linked to the initiative, and projectless milestones are unsupported |
| GET | /api/orgs/{id}/heartbeat | Heartbeat summary for the authenticated agent |
| GET | /api/orgs/{id}/strategy/audit | Audit strategy chain for structural gaps + health issues (guests forbidden) |
Reading a project-linked initiative or its child resources requires access to
at least one linked project. Explicit initiative-project links and projects
inferred from direct issue/milestone links form the authoritative scope.
Updating it or mutating issue, milestone, or target links requires edit/admin
access to every linked project. A requested issue or milestone project must
already be linked to the initiative. KPI-impact reads omit unreadable KPIs;
KPI-impact writes require write access to the target initiative and read access
to the same-org KPI. Projectless initiative writes remain owner/admin-only, and
this relationship permission does not grant KPI definition, sync, or snapshot
write access.
Projectless initiatives are readable by non-guest organization members and
writable only by organization owners/admins. Unreadable initiatives return
404; readable initiatives without sufficient write access return 403.
Initiative targets are not KPI snapshots. Use progress targets for initiative outputs and gate targets for launch prerequisites where pace language would be misleading.
Target list and detail responses include only issue and milestone link IDs the
caller can read. For a shared initiative, access through one project does not
expose links from another project.
Strategy audit applies the same boundary. Owners and admins receive
organization-wide execution evidence. Other non-guest members receive
project-bound issues, milestones, target links, and target findings only for
readable projects. A restricted member with no readable projects receives no
issue or target execution evidence. Guests cannot use Strategy audit.
Members, teams, agents, and integrations
Existing legacy avatar URLs in the old
attachments bucket are served through an authenticated compatibility proxy after issue attachments become private.
Member PATCH and DELETE can return 409 when the actor’s or target member’s
authorization changes before the atomic mutation commits. Refetch the member
and current permissions before retrying, and retry only if the action remains
authorized.
Avatar upload/removal is restricted to the target member or an organization
owner/admin, and both caller and target are bound to the organization in the
request path. Cross-organization IDs return 404. Upload responses contain
only member.id and member.avatar_url. Avatar pointer updates use
compare-and-set semantics. A concurrent change returns 409; a successful
mutation whose retired Storage object still needs durable cleanup returns
202 with cleanup_pending: true. A 409 response is
{ "error": "Avatar changed concurrently" } and includes
cleanup_pending: true only when cleanup of a staged or retired avatar object remains
queued. Due cleanup jobs are drained by an authenticated 15-minute service
worker as well as opportunistically during avatar requests. Uploads over 2MB
return 413.
Agent creation scopes: send { name, projectIds: [...] } for a project-scoped guest agent, or legacy { name, projectId, projectIds? } to include a default project. Send { name, personal: true } for a personal guest agent that inherits the human owner’s project access; personal creation rejects explicit projectId or projectIds. Omitting project scope creates an org-wide agent and remains owner/admin-only.
Boards, views, templates, updates, and analytics
Project-bound reads in this section require effective project access; mutations
require
edit or admin access. Board reassignment must stay within the project,
and reorder rejects duplicate, missing, partial, or mixed-project IDs before
changing any positions. It also requires unique non-negative integer positions.
Creation appends; direct position fields on create or patch are rejected.
Status-update creation follows the same write boundary.
Issue-template listing is intersected with effective project access.
Project-template reads need project access and mutations need edit or
admin. Organization-wide templates are readable by non-guests and manageable
only by organization owners/admins; guests and project-scoped agents do not
receive them. Unreadable or cross-organization template/project IDs return
404, while a readable project with insufficient write access returns 403.
Analytics, project health, and organization activity are intersected with the
caller’s accessible projects. Empty guest scope returns empty data without
enumerating organization projects. Eligible non-guests may also receive
projectless organization data. Project-bound issue activity requires access to
its issue project; eligible non-guests may read projectless issue activity. An
explicitly requested inaccessible analytics project is concealed as 404.
Analytics requires from and to values that are each either a calendar-valid
YYYY-MM-DD date or a timezone-qualified RFC 3339 timestamp (Z or an
explicit UTC offset). The ordered range may span no more than 366 days.
Partial dates, timezone-less timestamps, normalized invalid dates, reversed
ranges, and longer ranges return 400.
Automation, webhooks, billing, and GitHub
Private inbox endpoints require an authenticated organization member whose ID
is explicitly configured in
INBOX_OPERATOR_MEMBER_IDS; a missing allowlist
fails closed. Incoming HTML is sanitized, attachments remain private, unknown
aliases are quarantined, and reply drafts are never sent. Alias matching checks
To, then CC, then BCC; the first configured alias wins. The webhook body is
limited to 256 KiB. Attachments over 10 MiB each or 25 MiB per message are
recorded as skipped_oversize. Drafts accept To and optional CC, may use only
configured inbox aliases as their From address, and save idempotently.
Notifications and public endpoints
Current-member notifications can include
mention.created, issue.assigned, comment.added, and issue.status_changed. Comment writes can request structured mentions with mentions[].member_id or comment_mentions[].member_id; comment-create responses include mention fanout proof. Notification preferences support in_app and google_chat channels. The single Google Chat preference is stored under mention.created and controls mentions, assignments, and direct-reply comments; ordinary comments and status changes are excluded. Disabling google_chat stops future Chat delivery without acknowledging in-app notifications. If in_app mentions are muted but google_chat mentions are enabled, Atoll can still create an acknowledged notification row for Chat delivery without surfacing it in the bell or heartbeat. Disabling in-app mention.created delivery also attempts to acknowledge that member’s currently unread mention notifications; when cleanup succeeds, muted mentions leave both the bell and heartbeat attention_items. New direct-message installations receive an unprompted welcome before configuration. help, /help, @Atoll help, and configured Help command ID 1 return setup instructions distinct from the welcome. When verified-email auto-linking is missing, unresolved, or ambiguous, the user sends connect; classic Chat interaction apps then receive REQUEST_CONFIG and Workspace add-ons receive basic_authorization_prompt. The same command lets an already-linked user reconnect or add another workspace. Add-on callbacks require the endpoint URL audience and exact per-project add-on service account email. Connect-session and member endpoints require an authenticated human web session; one-time setup messages remain a manual fallback.
Google Chat mention cards include the task title, a safely formatted plain-text comment preview limited to 500 characters, and an Open in Atoll button. Rich-text markup is removed and Google Chat card formatting characters are escaped.
