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.
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.
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
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.
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.
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, 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.
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, and dueDate.
Update accepts the same mutable fields except status; at least one field is
required. 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.
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.
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.
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 are plain text and limited to 2,000 characters. A reply parent must belong to the same Work Item. Comment creation does not change the Work Item revision.
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.
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 remain readable and their existing Work Items remain usable,
but new Work Items and Sprints return PROJECT_ARCHIVED.
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.
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 existing validated image bytes with private no-store
caching, including for avatars originally hosted by an OAuth provider. It
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.
