API resources
Public API resources
All examples use the canonical production origin and an example token:
export HAMSTIK_TOKEN='hstk_pat_example_replace_me'export ORG_SLUG='blackboard-studios'export PROJECT_KEY='HAM'Organizations
curl -H "Authorization: Bearer $HAMSTIK_TOKEN" \ "https://hamstik.com/api/v1/organizations?limit=50"
curl -H "Authorization: Bearer $HAMSTIK_TOKEN" \ "https://hamstik.com/api/v1/organizations/$ORG_SLUG"The list is the intersection of the PAT’s resource grants and the user’s
current memberships. A Project-restricted PAT may see only the parent
Organization needed to address that Project. That minimal list entry contains
only id, slug, name, and suspended; it does not authorize a direct
Organization detail read. Suspended Organizations may still appear in the list
with suspended: true, but resource use returns 403 ORGANIZATION_SUSPENDED.
Projects
curl -H "Authorization: Bearer $HAMSTIK_TOKEN" \ "https://hamstik.com/api/v1/organizations/$ORG_SLUG/projects"
curl -H "Authorization: Bearer $HAMSTIK_TOKEN" \ "https://hamstik.com/api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY"Project keys are stable public identifiers. Resolution is case-insensitive and
the response returns the stored canonical key. A Project grant does not act as
a Project role and cannot bypass Organization membership or suspension.
Project projections include revision and nullable archivedAt; detail reads
and successful lifecycle mutations return a quoted "project-<revision>" ETag.
Project lists default to unarchived items and accept archived=true for only
archived items. Archived Projects remain readable and their existing Work Items
remain mutable, but new Work Items and Sprints fail with 409 PROJECT_ARCHIVED.
The acting user’s effective Project role bounds every Project-scoped call:
Projects the user has no access to resolve as 404, reads require
read-only access or above, and mutations (Work Items, Sprints, comments,
attachments) require Contributor access or above. Project Admins may
manage project-scoped resources; Organization-level operations keep their
Organization role requirements. See
Projects for the role model.
Create a Project with an Organization-granted PAT. The caller must be an
Organization administrator or owner, and Idempotency-Key is required. Omit key to
use Hamstik’s canonical suggestion from the Project name:
curl -i -X POST \ -H "Authorization: Bearer $HAMSTIK_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: project-20260906-01" \ -d '{"name":"Website Redesign","key":"WEB","description":"Project scope","color":"#3b82f6"}' \ "https://hamstik.com/api/v1/organizations/$ORG_SLUG/projects"The response is the complete created Project projection. A Project-restricted PAT cannot create a Project because the new Project is not covered by that grant.
Release audit packages
An authorized Project reader can list generated packages with
GET /api/v1/organizations/{organizationSlug}/projects/{projectKey}/release-audit-reports
and page with the returned opaque cursor. A Project manager with project:write
can generate a dossier by posting {"kind":"dossier","releaseVersionId":"..."}
or a UTC change register by posting
{"kind":"register","from":"2026-01-01T00:00:00Z","through":"2026-01-31T23:59:59Z"}
to the same collection. Include an Idempotency-Key. Generation uses the
current Project authorization and stores one immutable report package.
GET .../release-audit-reports/{reportId} returns the exact saved JSON document;
?format=csv returns a ZIP containing the same report.json, a manifest,
and CSV tables. The X-Content-SHA256 response header is the digest of the
exact JSON document bytes. A Project-restricted PAT can only access its granted
Project. Read and export access is recorded. See Release audit reports
for evidence provenance, bounds, format, and retention semantics.
Public user identity and Organization usernames
New user references use the immutable public ID, for example
usr_cPbfeqnghA-RLpDVOMQhHg. Its exact format is
usr_<22 Base64URL characters>: 128 cryptographically random bits encoded as
unpadded Base64URL, validated by ^usr_[A-Za-z0-9_-]{21}[AQgw]$. It is an
opaque URL/API identifier, not a secret. The legacy internal UUID remains only
where v1 compatibility requires it. Email is a private account credential and
is never a public user identifier. A removed account keeps its reserved public
ID, but normal profile lookup no longer finds it.
Usernames are nullable membership identities: a username is unique only inside
its Organization and may differ across Organizations. It is always displayed
with its Organization, never as a global Hamstik handle. GET /api/v1/me
includes publicId and the allowed
organizations: [{id, slug, name, username}] context.
The cookie-authenticated account control
PATCH /api/me/organizations/{organizationSlug}/username manages only the
signed-in user’s current membership and is not a /api/v1 operation. Its
strict body is { "username": string | null }; values are normalized to
lowercase, limited to 1–64 ASCII letters/digits/./_/-, and cannot begin or
end with punctuation. null clears the username. A collision within that
Organization returns 409 USERNAME_TAKEN.
GET /api/v1/me retains its legacy top-level id for v1 compatibility and
also returns the immutable publicId, the caller’s own email, PAT
authentication context, defaultOrganization, and the allowed Organization
array. The Organization array is ordered by Organization name and id. Email
is not returned by member, profile, Work Item, link, activity, or attachment
projections.
Labels
Organization-governed Work Item Attributes have a separate stable-key contract and standalone authorization scope. See the Attributes API guide for discovery, enablement, assignments, projections, and lifecycle.
List labels with GET /api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/labels.
The response is ordered by name ASC, id ASC, uses the standard collection
envelope, and caps limit at 100. Each item is {id, name, color, createdAt}.
Organization administrators and owners can create a label with project:write;
names are trimmed and stored lowercase and the default color is #6366f1.
Attach or detach a label at
.../work-items/HAM-42/labels. Both operations require work_item:write,
Idempotency-Key, and the Work Item ETag in If-Match. The complete Work Item
projection and its new ETag are returned. An already-attached label is a
successful no-op with no revision or activity change; an absent assignment
cannot be detached (404). A label from another Project is also hidden as
404. Actual attach and detach each increment the Work Item revision and
write one label activity event. Assignment accepts either {labelId: UUID} or
the case-insensitive project label name as {label: "frontend"} (the
labelId field also accepts a label name for first-party client compatibility).
Detachment accepts either the label UUID or the URL-encoded label name in the
{labelId} path segment.
Create a Project label with project:write as an Organization administrator or
owner:
{"name":"frontend","color":"#6366f1"}Names are trimmed, stored lowercase, and limited to 100 characters. Color is
optional and defaults to #6366f1; it must be a six-digit hex color. A
case-insensitive duplicate returns 409 CONFLICT.
Teams
Teams come in two distinct kinds, and neither expands Project access:
- Access Teams are Organization-scoped groups of active Organization
members. They are prepared for future permissions; in this release they
grant nothing. Reads require
organization:read; create, update, member add/remove, and delete require the Organization-administration write ceiling (attribute:write) plus owner/admin authorization enforced in-transaction. - Delivery Teams are Project-scoped groups used for planning, ownership,
and reporting. They never grant access. Reads require
project:read; writes requireproject:writeplus effective Project admin, re-derived from live policy and grants inside the transaction.
Routes (replace $ORG_SLUG, $PROJECT_KEY, $TEAM_UUID, and $USER_UUID):
GET /api/v1/organizations/$ORG_SLUG/access-teamsPOST /api/v1/organizations/$ORG_SLUG/access-teamsGET /api/v1/organizations/$ORG_SLUG/access-teams/$TEAM_UUIDPATCH /api/v1/organizations/$ORG_SLUG/access-teams/$TEAM_UUIDDELETE /api/v1/organizations/$ORG_SLUG/access-teams/$TEAM_UUIDGET /api/v1/organizations/$ORG_SLUG/access-teams/$TEAM_UUID/membersPOST /api/v1/organizations/$ORG_SLUG/access-teams/$TEAM_UUID/membersDELETE /api/v1/organizations/$ORG_SLUG/access-teams/$TEAM_UUID/members/$USER_UUID
GET /api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/delivery-teams[?includeArchived=true]POST /api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/delivery-teamsGET /api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/delivery-teams/$TEAM_UUIDPATCH /api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/delivery-teams/$TEAM_UUIDDELETE /api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/delivery-teams/$TEAM_UUIDPOST /api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/delivery-teams/$TEAM_UUID/archivePOST /api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/delivery-teams/$TEAM_UUID/unarchiveGET /api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/delivery-teams/$TEAM_UUID/membersPOST /api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/delivery-teams/$TEAM_UUID/membersDELETE /api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/delivery-teams/$TEAM_UUID/members/$USER_UUIDLists accept limit (1–100) and cursor, returning the standard {items, page} envelope. Each Team item is {id, name, description, revision, memberCount, createdAt, updatedAt} plus projectId and archivedAt for
Delivery Teams. Membership items are {userId, publicId, name, email, addedAt}. Create bodies are {name, description?}; update bodies are
{name?, description?, reason?} and require an exact If-Match Team ETag
("team-<revision>" or *). A duplicate normalized name — case and Unicode
folding are compared, including archived teams — returns 409 CONFLICT
within its scope (Organization for Access Teams, Project for Delivery Teams);
the same name in another Project or in the other team type is allowed.
Mutations are idempotent through Idempotency-Key, and replays re-run the
live authority check: a manager demoted or de-scoped after the original
request cannot replay a cached success. Membership adds return {userId, added} with 201 when created and 200 when the membership already
existed; removals return 204 and are idempotent.
Only active members can join. Access Team members must be active Organization
members; Delivery Team members must additionally hold effective access to the
Project, including inherited default access. Losing Organization access or
Project access removes the affected memberships transactionally and keeps
audit history; restoring access or unarchiving a team never restores members
silently. Deleting an Access Team is refused with 409 CONFLICT while a
registered authorization reference still exists; a Delivery Team can be
hard-deleted only while it has no members and no business-data references
(capacity reporting in particular) — otherwise archive it. Archived Delivery
Teams stay readable and keep their reporting identity but cannot gain members
or be newly selected. Every lifecycle and membership change is recorded in an
append-only audit trail with stable team and member identifiers. See
Teams for the product-level overview.
Boards
GET /api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/boards lists a
Project’s board configuration for project:read. It accepts limit and
cursor and returns the standard {items, page} envelope ordered by
name ASC, id ASC; each item is {id, name, revision, sortOrder, groupBy, columns, filter, createdAt, updatedAt}. A column is {id, title, status, wipLimit} where status is a canonical Work Item status or null for a
status-less column, and wipLimit is a whole number from 1 to 10000 or
null for no limit. groupBy is the board’s persisted swimlane default — one
of none, assignee, priority, parent, or epic (none for boards that
predate grouping); a viewer’s temporary URL override is never persisted and
never appears here.
The official CLI can read this resource through Public API passthrough:
hamstik --json --no-input api request "/api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/boards" --query limit=50Pass the returned page.nextCursor as --query cursor=... for the next page.
WIP limits are advisory configuration. They drive the per-column count/limit
and the At limit/Over limit indicator in the board UI; they never block a
transition, drag, keyboard move, bulk change, import, automation, or API write.
The projection is scoped to a Project the caller is authorized to read and
does not include an Organization identifier. See Boards for the
counting and authorization semantics.
Sprints
GET .../projects/$PROJECT_KEY/sprints lists Sprints with dated Sprints first
(startDate ASC, id ASC) and undated Sprints last. The cap is 100. Each Sprint
is {id, name, state, startDate, endDate, goal, targetPoints, createdAt, updatedAt, revision} and reads return an ETag such as "sprint-1".
Organization administrators and owners create Sprints with project:write and
an Idempotency-Key. New Sprints start in future; only future → active and
active → done are legal. Completing a Sprint with unfinished Work Items
requires {completionAction: {mode: "backlog"}} or a future Sprint target.
The create body is strict: {name, startDate?, endDate?, goal?, targetPoints?};
dates accept RFC 3339 offsets, explicit null clears an optional field, and
endDate cannot precede startDate when both are present. The Sprint
collection limit is 100 and the default page size is 50.
For compatibility, existing Work Item create and update routes still accept a
Sprint in the same Project even when that Sprint is already done; this is a
documented limitation and is not changed by the Sprint API.
POST .../sprints/{sprintId}/transitions also requires If-Match with the
Sprint ETag. GET .../transitions reports requiresCompletionAction for the
active → done edge. Invalid edges return INVALID_SPRINT_TRANSITION, a
second active Sprint returns ACTIVE_SPRINT_EXISTS, missing completion data
returns SPRINT_HAS_UNFINISHED_WORK_ITEMS, and an invalid target returns
INVALID_COMPLETION_TARGET.
Read delivery metrics with GET .../sprints/{sprintId}/report. See Sprint reports for the response fields, denominator semantics, history reconstruction rules, and the paginated change feed.
Read the aggregate capacity planning view with GET .../sprints/{sprintId}/capacity-planning
(scope project:read). The response carries per-member point and time ledgers
(capacity, committed, assigned, remaining, variance, overload status, unknown
counts), the unassigned bucket, Delivery Team roll-ups, the points forecast
(mean rate of the last three completed Sprints) and time projection, derived
risk, the recorded planning baseline, assumptions, and the as-of moment. The
forecast is informational and never blocks workflow.
Manage declared minute availability with GET/PUT .../sprints/{sprintId}/availability
(project:read / project:write; the write keeps capacity’s admin ceiling).
The PUT body is a replacement set: { "members": [{ "userId", "timezone", "optedIn", "minutes": {monday..sunday}, "overrides": [{"date", "minutes"}] }] }
with integer minutes 0–1,440. All rows apply or none do. See
Sprints for the full planning contract.
Release Versions
GET .../projects/$PROJECT_KEY/releases lists the Project’s canonical Release
Versions. The collection supports limit, an opaque cursor, state, and
includeArchived=true; archived versions are hidden by default. A Release
Version has a stable UUID, name, optional display version, description, owner,
target date, release date, state, first release timestamp, archive state, and
revision. A Project cannot have two Release Versions with the same name or
display version after trimming and case folding.
Organization administrators and owners use project:write to create or change
Release Versions. Creation starts in planned. Metadata updates require a
quoted "release-<revision>" If-Match header; mutations also require an
Idempotency-Key. The endpoints are:
GET .../releasesPOST .../releasesGET .../releases/{releaseId}PATCH .../releases/{releaseId}GET .../releases/{releaseId}/transitionsPOST .../releases/{releaseId}/transitionsPOST .../releases/{releaseId}/archivePOST .../releases/{releaseId}/restoreGET .../releases/{releaseId}/scopeThe transition path accepts { "targetState": "in_progress" }, with optional
reason. Allowed edges are planned → in_progress, in_progress → planned,
in_progress → released, and archive from any non-archived state. An archived
Release Version can be restored to its previous state. Release time is retained
across archive and restore. The explicit archive and restore operations take
{} or { "reason": "..." }. Each successful change records an immutable event
in the same transaction. Release Versions start with a basic workflow; optional
governance settings do not affect this default path.
If the Release Version contains unfinished Work Items, a release attempt returns
RELEASE_HAS_INCOMPLETE_SCOPE with a count and sample keys. Retry with
{ "targetState": "released", "confirmIncompleteScope": true } to acknowledge
the incomplete scope. The first release freezes an original Work Item scope
snapshot, including an empty scope, independently of any announcement.
Projects with an opt-in quality gate (see Quality) additionally
evaluate the gate on released. An unmet gate returns QUALITY_GATE_FAILED
with every unmet condition and its evidence under details.unmet; the Release
Version state is unchanged. An administrator may retry with a mandatory
qualityGateOverrideReason (1–2000 characters). The override snapshots the
unmet conditions, the reason, the actor, and the gate revision in immutable
Quality history and never changes test or verification evidence.
GET .../scope returns metadata, complete current-scope counts, and up to 100
Work Items per page. Use its opaque nextCursor to continue; cursors are tied
to the selected filters and sort. q, status, type, priority, and
assigneeId filter the rows. The response includes the frozen original-scope
count and number removed from current scope after first release. Completion
is based on Work Item status; it does not imply deployment.
Work Item memberships are many-to-many. Read them with
GET .../work-items/{workItemKey}/releases. Use
POST .../work-items/{workItemKey}/releases with the Work Item’s "wi-<revision>"
ETag and an Idempotency-Key to add, remove, or replace assignments:
{"mode":"add","releaseVersionIds":["00000000-0000-4000-8000-000000000001"]}This mutation requires work_item:write, rejects versions outside the same
Project or archived versions, and returns the updated Work Item revision and
all current Release Version summaries. Correcting membership after first
release also requires project:write, Project management access,
confirmReleasedScopeCorrection: true, and a nonempty reason. Such a
correction creates a new audit event and leaves the first-release snapshot
unchanged.
For a bounded batch, POST .../projects/$PROJECT_KEY/release-memberships/bulk
accepts up to 50 operations with workItemKey, revision, mode, and
releaseVersionIds. It returns one result per input, including status and
an error for failed items, plus partial: true whenever any item failed. A
failed item rolls back its own changes without discarding successful items.
Release announcements
GET .../releases/{releaseId}/announcements returns the latest published
announcement. GET .../releases/{releaseId}/announcements/{revision} returns a
specific immutable revision. Both require project:read and enforce Project
access. They include the rendered Markdown, Release metadata, categorized Work
Item snapshot, publisher, UTC publication time, and differences from the
original release scope. A later Work Item edit or membership correction does
not change an existing revision. No anonymous announcement endpoint exists.
Project managers with project:write can use the draft workflow:
GET .../releases/{releaseId}/announcements/draftPOST .../releases/{releaseId}/announcements/draftPATCH .../releases/{releaseId}/announcements/draftDELETE .../releases/{releaseId}/announcements/draftPOST .../releases/{releaseId}/announcements/publishPOST draft accepts {} to generate the first draft, or the current revision
to refresh it. A refresh that replaces manual category organization requires
confirmReplaceOrganization: true. The draft response includes diff.added,
diff.removed, and diff.changed Work Item keys measured against current
scope. PATCH draft takes the draft revision, introduction, highlights,
categories, and included Work Item IDs. Publishing takes the draft revision,
optional reason, and confirmEmpty: true when the scope is empty. Changed Work
Item facts or scope return a conflict until the draft is refreshed. Publication
creates a new revision and never overwrites previous content. Drafts are
bounded at 10,000 Work Items; the server rejects larger scopes rather than
omitting rows. Generate and publish requests require Idempotency-Key.
Organization Milestones
Organization Milestones group canonical Project Release Versions for planning.
They never grant Project access. The following routes require milestone:read
for reads and milestone:write plus Organization administration for changes:
GET .../organizations/{organizationSlug}/milestonesPOST .../organizations/{organizationSlug}/milestonesGET .../milestones/{milestoneId}PATCH .../milestones/{milestoneId}GET .../milestones/{milestoneId}/transitionsPOST .../milestones/{milestoneId}/transitionsGET .../milestones/{milestoneId}/releasesPOST .../milestones/{milestoneId}/releasesGET .../milestones/{milestoneId}/eventsMilestones have planned, in_progress, completed, and archived states.
An archived Milestone can return to its preceding state. Mutations require a
numbered "milestone-<revision>" If-Match header and an Idempotency-Key.
Membership mutation accepts releaseVersionId and mode: "add" | "remove";
removal also requires confirmRemove: true. The service rejects a Release
Version outside the Organization or outside the caller’s authorized Projects.
List and member-Release reads are capped at 100 rows per page. Milestone
progress counts distinct Work Items across its accessible Release Versions,
even when one Work Item belongs to multiple versions. Responses label their
counts authorized_projects. Restricted Project names, Release metadata, and
counts are not included. A Project-scoped token grant does not itself grant
Organization Milestone access.
History pages use the opaque nextBeforeEventId UUID as the next request’s
beforeEventId query parameter. The cursor must name a visible event; history
omits inaccessible Project membership events and their global sequence values.
Work Items
List Work Items with repeatable exact filters:
curl -G \ -H "Authorization: Bearer $HAMSTIK_TOKEN" \ --data-urlencode "status=todo" \ --data-urlencode "status=in_progress" \ --data-urlencode "scope=open" \ --data-urlencode "assignee=me" \ --data-urlencode "limit=50" \ "https://hamstik.com/api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/work-items"Supported filters are q, repeatable status, type, and priority,
scope=all|open|closed, assignee=<legacy user UUID>|me|none,
sprint=<sprint UUID>|none, repeatable label, repeatable labelName,
parent=<work-item key>, topLevel=true|false, and updatedAfter=<RFC 3339 timestamp>. The collections also accept overdue=true|false, exclusive
dueBefore and dueAfter RFC 3339 timestamps, sort=updated|dueDate|priority|rank,
archived=true|false, and fields=. Organization and My Work collections
add repeatable project; My Work also accepts only the PAT owner’s current
assignment and defaults to scope=open.
Work Item collection limits are 1–200, with a default of 50. The valid sparse
summary fields are title, type, status, priority, assignee, sprint,
parentId, storyPoints, voteCount, hasVoted, effortRisk,
effortRiskReason, dueDate, archivedAt, createdAt, and updatedAt.
id, key, and revision are always present; Organization and My Work rows
also always include project and organization context. A literal empty
fields= selects the complete summary, while an empty member such as
fields=title,,status is invalid. Authenticated member summaries also expose
voteCount and the caller’s own hasVoted (HAM-59); anonymous public
projections never include personalized vote state.
New Work Item request bodies should use assigneePublicId with the canonical
usr_ value. assigneeId remains accepted for v1 compatibility; the two
fields are mutually exclusive. A public ID resolves only to an active member
of the requested Organization. Unknown enum values, invalid timestamps,
foreign references, and conflicting parent plus topLevel=true are rejected.
All query names are strict. Scalar filters may appear once, and each repeatable
filter accepts at most 100 values.
topLevel also accepts 1 and 0 as aliases for true and false.
Read a complete Work Item and preserve its revision ETag:
curl -i \ -H "Authorization: Bearer $HAMSTIK_TOKEN" \ "https://hamstik.com/api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/work-items/HAM-42"Create one. Idempotency-Key is required and should be newly generated for
each logical mutation:
curl -i -X POST \ -H "Authorization: Bearer $HAMSTIK_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 83e2744d-7609-4b98-a1ec-056cfa93fa72" \ -d '{"title":"Implement CLI authentication","type":"feature","priority":"high"}' \ "https://hamstik.com/api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/work-items"The server supplies the reporter. type, status, and priority default to
task, backlog, and medium. Descriptions are canonical Hamstik Markdown;
existing v1 detail summaries retain the legacy {id, name} shape for
compatibility and never contain an email address. Organization, My Work,
profile, link, activity, and new Work Item mutation projections introduced by
this work use {publicId, name} instead. Existing comment and attachment user summaries
also retain their legacy shape for v1 compatibility; new link and activity
creator/actor summaries use public IDs.
Create and update bodies are strict objects. Create accepts title, optional
Markdown description, type, status, priority, assigneePublicId (or
legacy assigneeId), sprintId, parentId (a parent UUID or Work Item key),
storyPoints, originalEstimateMinutes, remainingEstimateMinutes, dueDate,
effortRisk with effortRiskReason, and isPrivate. Estimates are whole elapsed minutes; null is unknown and 0
is explicitly none. isPrivate defaults to false; set it to true at creation
to keep the item hidden from guests in a public Project. Update accepts the same
mutable fields except status; at least one field is required. Sending
originalEstimateMinutes alone never copies the value into remaining effort:
remainingEstimateMinutes is only changed when it is present in the request. Dates must be RFC 3339 timestamps with offsets, and explicit
null clears nullable fields.
Update only the documented mutable fields. Use the ETag from the latest read:
curl -i -X PATCH \ -H "Authorization: Bearer $HAMSTIK_TOKEN" \ -H "Content-Type: application/json" \ -H 'If-Match: "wi-1"' \ -d '{"priority":"urgent","description":"Updated Markdown"}' \ "https://hamstik.com/api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/work-items/HAM-42"Status is intentionally not a PATCH field. Use transition discovery and the transition endpoint instead. Protected fields and unknown fields fail strict validation.
Cloning a Work Item
Read a same-Project clone preview with work_item:read, then create the clone
with work_item:write and a new Idempotency-Key:
curl -H "Authorization: Bearer $HAMSTIK_TOKEN" \ "https://hamstik.com/api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/work-items/HAM-42/clone"
curl -i -X POST \ -H "Authorization: Bearer $HAMSTIK_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 1f8b4c2e-2a7f-4b1e-9c3a-9a0f6e5d4c3b" \ -d '{"expectedSourceRevision":7,"title":"Copy of Implement CLI authentication","copyRelatedLinks":true,"linkToOriginal":true}' \ "https://hamstik.com/api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/work-items/HAM-42/clone"The preview response contains source (including the revision to echo back as
expectedSourceRevision), editable defaults, copyableLinks, skippedLinks,
and an attachmentNotice when inline attachment references were stripped.
expectedSourceRevision is required; a mismatch fails with 412 PREVIEW_STALE
and no clone is created. Optional fields (title, description, type,
priority, storyPoints, labelIds, assigneeId/assigneePublicId,
sprintId, parentId, dueDate, and attributes) override the defaults.
copyRelatedLinks defaults to false; linkToOriginal defaults to true.
Only relates links whose other end remains authorized in the same Project are
copied, and a selected link that becomes invalid fails the transaction
atomically. The response is the created Work Item with its revision ETag.
Work Item voting
HAM-59 adds an equal-weight vote to every Work Item. A vote never changes priority, rank, status, Sprint, severity, or assignment, and it emits no notification or automation. Any active Organization member with read access may vote, including read-only Project members; there is no voter roster endpoint.
Read the retained count and your own state (work_item:read):
curl -H "Authorization: Bearer $HAMSTIK_TOKEN" \ "https://hamstik.com/api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/work-items/HAM-42/vote"The response is { "workItemId", "voteCount", "hasVoted" }.
Set or remove your vote idempotently with work_item:write, an
Idempotency-Key, and the desired state (there is no toggle endpoint):
curl -X PUT \ -H "Authorization: Bearer $HAMSTIK_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 83e2744d-7609-4b98-a1ec-056cfa93fa72" \ -d '{"voted":true}' \ "https://hamstik.com/api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/work-items/HAM-42/vote"The response is authoritative; retries and concurrent opposing requests commit
one desired state without creating duplicates or underflowing the count. Any
status, including Done, accepts votes while the item and Project are active.
Archived items and Projects keep their count but return 409 for a vote change.
Losing membership revokes authority while retaining prior votes; deleting an
account removes that person’s active votes. Use the SqueakQL search endpoint
with numeric vote_count comparisons (=, !=, <, <=, >, >=) and
ORDER BY vote_count ASC or DESC. Equal counts order by immutable Work Item
ID ascending. Basic collection sort values remain unchanged. Concurrent vote
changes can reorder live pages; use a snapshot export when a stable population
is required.
Time tracking
Estimates and worklogs are canonical elapsed effort in whole minutes. Story
points remain a separate planning estimate and are unchanged. null means the
estimate is unknown; 0 means there is explicitly none.
Read the canonical effort summary for a Work Item:
curl -H "Authorization: Bearer $HAMSTIK_TOKEN" \ "https://hamstik.com/api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/work-items/HAM-42/effort"The response carries originalEstimateMinutes, remainingEstimateMinutes,
timeSpentMinutes (the derived sum of authorized worklogs, never a writable
counter), outstandingMinutes (0 for canonical done),
doneWithStoredRemaining, and a separately labelled
descendantTimeSpentMinutes plus descendantLogCount. The descendant rollup
counts each authorized child’s own logs once and never adds an intermediate
rollup. Unknown estimates stay unknown rather than becoming zero.
Record effort with work_item:write and a new Idempotency-Key. The
authenticated caller is the author; normal creation cannot impersonate anyone:
curl -i -X POST \ -H "Authorization: Bearer $HAMSTIK_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 89f9a0f1-4c77-4f16-9b45-2f2b1f2f9c31" \ -d '{"minutes":120,"workDate":"2026-01-05","comment":"Pairing on the parser"}' \ "https://hamstik.com/api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/work-items/HAM-42/worklogs"minutes is 1–1,440. workDate is a YYYY-MM-DD calendar date supplied by
the user and is never converted between timezones; it defaults to today in the
caller’s configured timezone (UTC fallback). Future dates are rejected;
backdating is allowed and overlapping entries are allowed. The optional
comment is Markdown under the standard comment limit. Supply
remainingEstimateMinutes to change the remaining estimate atomically with the
entry; a worklog write never changes remaining effort on its own.
List authorized worklogs with work_item:read:
curl -H "Authorization: Bearer $HAMSTIK_TOKEN" \ "https://hamstik.com/api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/work-items/HAM-42/worklogs?limit=50"Read authorization requires both current Work Item access and access to each row’s recorded Project. A log recorded in a Project the reader can no longer see is omitted from rows, own-item and descendant totals, and exports; no hidden numeric bucket or subtractable global total is exposed. Read-only Project access may read totals and logs but cannot write.
Edit or delete your own entry with work_item:write. Use the worklog ETag
(If-Match: "wl-2") and a new Idempotency-Key:
curl -i -X PATCH \ -H "Authorization: Bearer $HAMSTIK_TOKEN" \ -H "Content-Type: application/json" \ -H 'If-Match: "wl-2"' \ -H "Idempotency-Key: 5d6b0e64-7a2b-4d2f-bc3c-2f8b1a9a5f6d" \ -d '{"minutes":90,"remainingEstimateMinutes":360}' \ "https://hamstik.com/api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/work-items/HAM-42/worklogs/$WORKLOG_ID"curl -i -X DELETE \ -H "Authorization: Bearer $HAMSTIK_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: a4c1b7d0-3e2f-4a91-8c55-7b0b1d2e3f40" \ -d '{}' \ "https://hamstik.com/api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/work-items/HAM-42/worklogs/$WORKLOG_ID"A contributor may edit and delete their own entries while they retain write
access. An effective Project admin, or an Organization owner/admin, may correct
another author’s entry with a required correctionReason; the correction is
recorded on the row and in the Work Item activity. Deleting or editing a log
never reverses or invents a remaining-effort adjustment. Archived items retain
readable logs, but new or edited logs and estimates require an active writable
Item and Project. See the official CLI for supported passthrough of these
operations.
Transitions
Discover current valid targets before changing status:
curl -H "Authorization: Bearer $HAMSTIK_TOKEN" \ "https://hamstik.com/api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/work-items/HAM-42/transitions"Then send the target status with both concurrency and idempotency headers:
curl -i -X POST \ -H "Authorization: Bearer $HAMSTIK_TOKEN" \ -H "Content-Type: application/json" \ -H 'If-Match: "wi-1"' \ -H "Idempotency-Key: transition-HAM-42-20260906-01" \ -d '{"targetStatus":"in_progress"}' \ "https://hamstik.com/api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/work-items/HAM-42/transitions"The response is the updated full Work Item. A replay has
Idempotency-Replayed: true and does not create another activity or history
event.
Work Item watchers
Read the authenticated member’s watcher state:
curl -H "Authorization: Bearer $HAMSTIK_TOKEN" \ "https://hamstik.com/api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/work-items/HAM-42/watcher"The response includes watched, manualWatch, assigneeOrigin, muted, and
assigned. Use work_item:write and a new Idempotency-Key for each logical
action:
curl -i -X POST \ -H "Authorization: Bearer $HAMSTIK_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: watcher-HAM-42-20260906-01" \ -d '{"action":"watch"}' \ "https://hamstik.com/api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/work-items/HAM-42/watcher"Supported actions are watch, unwatch, mute, and unmute. Only new
comments and status changes produce watched notifications. Assignment adds an
assignee-origin watch unless the assignee has muted the item; reassignment
removes only that origin, preserving a manual watch. Muting does not suppress
direct assignment, mention, or reply notifications. Watcher reads and writes
require current Organization membership and reject archived or deleted
sources.
Comments
Comments are a flat, oldest-first collection. Threading is represented by
parentCommentId; clients can render the tree if desired. Deleted comments
remain as placeholders with body: null and deleted: true.
Each comment also includes mentions, containing the resolved organization
username and its occurrence count. Comment bodies use @username syntax, where
the username belongs to the Organization containing the target Work Item.
Resolution is server-side and is shared by web, Public API, and CLI-originated
requests; clients must not treat a supplied username or user id as an
authorization grant. Only active members who can read the target Project and
Work Item can be resolved. Consequently, some people will not appear in web
autocomplete, and the API never exposes an alternate member-search result for
an ineligible person.
Mention parsing follows Markdown structure. Mentions in inline/fenced code,
URLs, email addresses, escaped or malformed tokens, and unknown usernames are
ignored. Only organization-scoped @username mentions are supported; @all,
@here, teams, groups, arbitrary email addresses, Work Item description
mentions, and external-App mentions are unsupported.
curl -G \ -H "Authorization: Bearer $HAMSTIK_TOKEN" \ --data-urlencode "limit=50" \ "https://hamstik.com/api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/work-items/HAM-42/comments"
curl -i -X POST \ -H "Authorization: Bearer $HAMSTIK_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: comment-HAM-42-20260906-01" \ -d '{"body":"Implementation complete."}' \ "https://hamstik.com/api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/work-items/HAM-42/comments"Comment bodies use Markdown and are limited to 2,000 characters. A reply parent must belong to the same Work Item. Comment creation does not change the Work Item revision. A new comment notifies each newly mentioned eligible member except its author; duplicate occurrences produce one mention reason. Editing notifies only newly added mentions, never repeats unchanged mentions, and does not retract a prior notification. Re-adding a removed mention later is a new event only after the persisted history records the removal. Replies notify the stored parent author, including nested replies, and mention/reply/watcher reasons are deduplicated into one recipient projection.
Changing a username does not retarget existing references. A departed member is rendered with only safe historical mention text. Current authorization is checked again when notification content is read or delivered; notifications do not grant access. If the source comment is removed, its body is omitted from the comment and notification projection, while the thread placeholder and safe unavailable navigation state remain.
Delete an authored comment only when it has no replies:
curl -i -X DELETE \ -H "Authorization: Bearer $HAMSTIK_TOKEN" \ "https://hamstik.com/api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/work-items/HAM-42/comments/$COMMENT_ID"Successful deletion returns 204 No Content. The comment is soft-deleted so
its placeholder remains available to preserve the thread shape. A comment with
replies returns 409 CONFLICT; a comment authored by another user returns
403 FORBIDDEN.
Reactions
Reactions are lightweight, notification-free acknowledgements on a Work Item or
a comment. They are a closed set with stable machine keys and Unicode display:
thumbs_up (👍), heart (❤️), laugh (😂), celebrate (🎉), confused (😕),
and eyes (👀). Custom uploads and arbitrary emoji are not supported.
Reactions are deliberately distinct from votes. They never contribute to vote
counts, priority, ranking, workflow, or automation triggers, and a reaction
never changes the Work Item revision, updatedAt, or activity feed. Any active
Organization member with read access to the target may react, including a
read-only Project member. A user may apply each distinct reaction once per
target and several different reactions to the same target; applying or removing
the same reaction twice is idempotent, and removing a reaction that is not
present succeeds without lowering any count.
Reads return reactions as an array of {key, count, reacted} entries on each
Work Item and comment. count is the live total and reacted states whether
the authenticated caller has that reaction. Counts are aggregated per page, so
listing comments never performs a per-comment query. There is no voter or
reactor directory in this version.
Add or remove a Work Item reaction with work_item:write:
curl -i -X PUT \ -H "Authorization: Bearer $HAMSTIK_TOKEN" \ -H "Idempotency-Key: reaction-HAM-42-20261004-01" \ "https://hamstik.com/api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/work-items/HAM-42/reactions/heart"
curl -i -X DELETE \ -H "Authorization: Bearer $HAMSTIK_TOKEN" \ -H "Idempotency-Key: reaction-HAM-42-20261004-02" \ "https://hamstik.com/api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/work-items/HAM-42/reactions/heart"Add or remove a comment reaction with comment:write. The comment must be on
the Work Item in the path and must not be removed:
curl -i -X PUT \ -H "Authorization: Bearer $HAMSTIK_TOKEN" \ -H "Idempotency-Key: reaction-HAM-42-comment-01" \ "https://hamstik.com/api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/work-items/HAM-42/comments/$COMMENT_ID/reactions/eyes"Both mutations return 200 OK with the target’s authoritative
{reactions} aggregate. An unsupported key returns 400 VALIDATION_ERROR.
Inaccessible items, hidden comments, comments in another Work Item, and foreign
Organization identifiers return 404 NOT_FOUND and reveal no count or state.
An archived Work Item or Project returns 409 CONFLICT and blocks changes while
its reactions remain readable. Removing a comment hides its reactions; a
removed comment is no longer a valid reaction target. Permanent disposal of a
Work Item, Project, comment, Organization, or account removes the affected
relationships.
Work Item Attachments
Upload an attachment as multipart form data. The file field is required, the
normal per-file hosting limit applies, and Idempotency-Key is required:
curl -i -X POST \ -H "Authorization: Bearer $HAMSTIK_TOKEN" \ -H "Idempotency-Key: attachment-HAM-42-20260907-01" \ -F "file=@./design.png" \ "https://hamstik.com/api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/work-items/HAM-42/attachments"The response is 201 Created with the attachment id, sanitized fileName,
contentType, size, createdBy, and createdAt. List metadata and download
the original bytes with the same Work Item path:
curl -H "Authorization: Bearer $HAMSTIK_TOKEN" \ "https://hamstik.com/api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/work-items/HAM-42/attachments"
curl -L -H "Authorization: Bearer $HAMSTIK_TOKEN" \ "https://hamstik.com/api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/work-items/HAM-42/attachments/$ATTACHMENT_ID" \ -o ./design.pngUse the attachment id to remove it:
curl -i -X DELETE \ -H "Authorization: Bearer $HAMSTIK_TOKEN" \ "https://hamstik.com/api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY/work-items/HAM-42/attachments/$ATTACHMENT_ID"The caller must have attachment:read to list or download attachments,
attachment:write to upload them, and attachment:delete to delete them.
Both attachment mutation scopes include attachment:read. A member may delete only an
attachment they uploaded; an Organization administrator may delete any
attachment in the Organization. Successful deletion returns 204 No Content.
Links and activity
Work Item links use GET|POST .../work-items/{workItemKey}/links and
DELETE .../links/{linkId}. Create requests target one Work Item by UUID or
key and use blocks, blocked_by, or relates. Same-Organization
visibility and both PAT resource grants are enforced. Conflicts are
LINK_DUPLICATE or LINK_CONTRADICTION.
The stored blocks relation is rendered as blocks from the source Work Item
and as blocked_by from the inverse Work Item. A stored relates relation is
rendered as relates from either side. Link list entries include the visible
other Work Item, its Project summary, the creator’s {publicId, name} or
null, and createdAt.
GET .../work-items/{workItemKey}/activity returns chronological redacted
activity. GET .../projects/{projectKey}/activity returns newest-first
activity and includes a Work Item summary. Stored detail is never returned
verbatim; deleted or inaccessible references become null.
Organization Work, My Work, and lifecycle
GET /api/v1/organizations/$ORG_SLUG/work-items returns Work Items with
Organization and Project context. GET /api/v1/my/work fixes assignment to
the PAT owner. Both support repeatable Project/status/type/priority/label
filters, due-date and overdue filters, sort=updated|dueDate|priority|rank,
and opaque keyset cursors. asOf is captured on the first page and retained
in the cursor.
Work Items can be soft-deleted by an Organization owner with
DELETE .../work-items/{key} and archived/unarchived by an administrator.
Deleted items are hidden and return NOT_FOUND; archived items remain
readable but are excluded from collections unless archived=true. Archived
items reject Work Item mutations until unarchived.
updatedAfter is not a deletion feed; use the Project activity feed to observe
soft deletions incrementally.
Projects expose revision ETags, update, archive, and unarchive operations.
Archived Projects and their existing Work Items remain readable. Normal child
writes, including new Work Items and Sprints, return PROJECT_ARCHIVED. Archiving or restoring
a Project never changes any child Work Item’s archivedAt.
Authenticated profiles and member directories
GET /api/v1/users/{publicId}, /work, /activity, and /avatar
require profile:read and a PAT. A non-self profile is addressable only
through a current shared Organization that remains visible under the PAT’s
resource grants. The profile summary contains {publicId, name}, an
optional canonical avatar URL, joined date, Organization-paired usernames,
and visibility-scoped statistics. It never contains email, an internal UUID,
or hidden Organization/work data.
GET /api/v1/organizations/$ORG_SLUG/users requires
organization:members:read and returns active {publicId, name, username}
rows ordered by name and public ID. q is a literal substring filter over
name or that Organization’s username, trimmed to 1–200 characters. A
Project-restricted PAT may use this directory only when it has at least one
granted Project in that Organization.
Profile activity includes only Work Item creation, comment creation/replies whose comments still exist, and status transitions to the canonical done status. Historical rows without enough identifiers to prove comment visibility are omitted. Each profile activity item also includes the authorized Organization, Project, and Work Item summaries and is ordered newest first.
Bulk operations and sparse fieldsets
Bulk create, update, and transition routes accept 1–50 strict operations and
return per-item results in input order. Update and transition requests default
to require-revision; last-write-wins explicitly omits revisions.
All Work Item collections accept fields=title,status,.... It is a
comma-separated list of top-level summary fields; unknown or empty members are
validation errors, while literal fields= means all fields. id, key,
and revision are always present, and Organization/My Work context remains
present regardless of fieldset.
Project lifecycle
PATCH /api/v1/organizations/$ORG_SLUG/projects/$PROJECT_KEY requires
project:write, Organization administrator/owner role, Idempotency-Key, and
If-Match: "project-<revision>" (or *). The strict body contains at least
one of name, description, or color; the Project key cannot change.
Successful update, archive, and unarchive responses return the Project
projection and its new Project ETag. Archive and unarchive use strict {}
bodies. Repeating the current archive state is a successful no-op.
Work Item deletion and archival
Delete requires work_item:delete, Organization owner role, an idempotency key,
and If-Match. The optional strict body is { "cascade": false }; omitting
the body has the same meaning. An empty or malformed present body is invalid.
Without cascade, non-deleted descendants return 409 WORK_ITEM_HAS_CHILDREN.
With cascade, the complete descendant tree is soft-deleted atomically and each
deleted Work Item receives its own activity event. Deleted keys are never
reused and direct reads return 404 NOT_FOUND.
Archive and unarchive require work_item:delete, administrator/owner role,
an idempotency key, If-Match, and a strict empty-object body. They return the
updated Work Item and ETag. An already-current state is a no-op without a
revision or activity change. Archived Work Items remain readable and are
returned only with archived=true; scope=all does not include them. Every
other Work Item-scoped mutation is rejected with 409 WORK_ITEM_ARCHIVED.
A Work Item cannot be unarchived while its Project is archived: the request
returns 409 PROJECT_ARCHIVED and the Project must be unarchived first.
Restoring a Project does not unarchive its individually archived Work Items.
Link request and activity detail
Create a link with exactly one of targetKey or targetId:
{"targetKey":"HAM-43","relation":"blocks"}The target must be a visible Work Item in the same Organization, and both
Projects must be covered by the PAT. A self-link is validation failure; a
duplicate is LINK_DUPLICATE; an opposing blocks relationship is
LINK_CONTRADICTION. Create/delete require idempotency keys and write one
activity event only after an actual change. Link deletion returns 204 and
requires the link to touch the requested Work Item.
Activity entries are projections, never stored JSON. The stable detail shapes are:
| Actions | detail |
|---|---|
created, comment_added, comment_replied, comment_edited, comment_deleted, description_changed, archived, unarchived, work_item_deleted |
null |
status_changed |
{from, to} |
assigned |
`{from: user |
priority_changed |
{from, to} |
sprint_changed |
`{from: sprint |
label_added, label_removed |
`{label: label |
link_added, link_removed |
`{relation, otherWorkItem: {id, key, title} |
due_date_changed |
{from, to} with RFC 3339 or null |
title_changed |
{from: string, to: string} |
parent_changed |
`{from: work-item |
attachment_added, attachment_removed |
`{attachment: {id, fileName} |
since is a strictly-after RFC 3339 filter. Work Item activity is oldest
first; Project and profile activity are newest first. A deleted actor is
rendered as actor: null, and inaccessible referenced resources are redacted
to null.
Profile response and filters
The profile summary has this shape:
{ "publicId":"usr_cPbfeqnghA-RLpDVOMQhHg", "name":"Steven Hildreth", "avatarUrl":"https://hamstik.com/api/v1/users/usr_cPbfeqnghA-RLpDVOMQhHg/avatar", "joinedAt":"2026-01-15T14:30:00.000Z", "isCurrentUser":false, "sharedOrganizations":[{"organization":{"id":"...","slug":"blackboard-studios","name":"Blackboard Studios"},"username":"steven"}], "stats":{"projects":6,"workItemsAssigned":7,"workItemsCreated":42,"workItemsCompleted":18,"comments":27}}The four profile endpoints share the same visibility rule. A malformed,
unknown, removed, or non-addressable target is indistinguishable as 404.
The avatar URL is null when no current avatar exists; the avatar endpoint
otherwise returns the current validated avatar image with private no-store
caching. Hamstik-hosted avatars are served as the optimized 96-pixel WebP UI
derivative, so the response is small and the Content-Type may be
image/webp; avatars originally hosted by an OAuth provider are proxied in
their original format. The endpoint does not redirect API clients to the
provider. A canonical avatar URL may
carry only validated content-version parameters needed by the existing avatar
storage behavior. It never exposes an internal UUID or storage key.
The five statistics are computed only after the viewer’s membership,
suspension, PAT grant, deletion, and archive predicates are applied: distinct
visible Projects involving the target as assignee/reporter/comment author;
visible Work Items currently assigned to the target; visible Work Items
reported by the target; visible assigned Work Items in canonical done; and
visible non-deleted comments authored by the target. Each row counts once per
statistic, and the self-profile has the same access restrictions as any other
profile request.
Profile Work supports the Work Item collection filters except caller-selected
assignee (the target is fixed by the profile URL), plus repeatable
organization=<organizationSlug> and involvement=assigned|created|commented;
involvement defaults to all three and scope=open is the default. It is
deduplicated before pagination. Profile Work uses its own projection: embedded
assignee and reporter identities are {publicId, name} (the reporter is
included in the complete projection; it is omitted when a sparse fields
selection is supplied because reporter is not a Work Item summary field,
and Project/Organization context is always present. The same top-level fields
selectors and 200-item limit apply to this collection. Profile activity is limited to the target’s
visible created, live comment creation/reply, and status_changed events
whose public to state is canonical done.
Bulk request envelopes
All three bulk routes require work_item:write and Idempotency-Key:
{"operations":[{"projectKey":"HAM","title":"First"}]}{"concurrency":"require-revision","operations":[{"projectKey":"HAM","workItemKey":"HAM-42","revision":4,"changes":{"priority":"high"}}]}{"concurrency":"last-write-wins","operations":[{"projectKey":"HAM","workItemKey":"HAM-42","targetStatus":"done"}]}Top-level envelopes require 1–50 operations and reject unknown fields. Each
operation is validated independently, so a malformed item becomes an embedded
VALIDATION_ERROR while other items continue. The outer status is 200,
there is no page, and each result contains its input index plus either the
per-item success status/projection or an embedded error. Replays return the
stored result array without repeating any item.
Comment editing
PATCH .../comments/{commentId} requires comment:write, an idempotency key,
and a strict { "body": "..." } body using the same 2,000-character limit as
creation. Only the comment author may edit; administrators do not receive
public moderator edit authority. Deleted comments return 404. A successful
edit returns 200 and sets editedAt; creation and deletion do not set or
change that field. The edit response uses the new public author projection
{publicId, name}. Existing comment list, create, and delete responses retain
the legacy v1 {id, name} author summary for compatibility.
Advanced reports and dashboards
Advanced Reporting API provides Organization-scoped
definitions, personal mutations, shared governance, report/dashboard runs, and
short-lived exact selections. It uses report:read / report:write with current
Premium, App, capability, and PAT Project restrictions. Standard report resources
retain Core availability and project:read.
