Skip to content

API resources

Public API resources

All examples use the canonical production origin and an example token:

Terminal window
export HAMSTIK_TOKEN='hstk_pat_example_replace_me'
export ORG_SLUG='blackboard-studios'
export PROJECT_KEY='HAM'

Organizations

Terminal window
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

Terminal window
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:

Terminal window
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:

Terminal window
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:

Terminal window
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:

Terminal window
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:

Terminal window
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:

Terminal window
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:

Terminal window
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.

Terminal window
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:

Terminal window
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:

Terminal window
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:

Terminal window
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.png

Use the attachment id to remove it:

Terminal window
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.

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.

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.