herro

herro API

A small, stable REST API for reading your boards and creating or updating initiatives — plus inbound and outbound webhooks.

Overview

All requests go to the API base URL. Every response is JSON, and CORS is enabled so you can call it from a browser app.

https://twsow4mkgdvwkmsdfo3q6igdwm0xzdhg.lambda-url.us-east-1.on.aws

The current version is v1. Endpoints are additive — the response shapes documented here are a stable whitelist and won't change without a version bump.

Authentication

Create a personal access token in the app under Account → API tokens. The token is shown once at creation (only its hash is stored), and can be revoked from the same screen. Send it on every request:

Authorization: Bearer hro_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
curl -H "Authorization: Bearer hro_…" \
  https://twsow4mkgdvwkmsdfo3q6igdwm0xzdhg.lambda-url.us-east-1.on.aws/v1/me

A missing, malformed, or revoked token returns 401. Tokens act on the data owned by the account that created them.

Token scopes

Each token has an access level, chosen when you create it — so a script that only needs to read can never mutate anything:

ScopeAllows
readGET endpoints only — any mutation returns 403.
writeReads plus create and update. No deletes, no invites. (Default.)
fullEverything, including DELETE endpoints and POST /v1/invites.

Me

GET/v1/me

Identifies the token's account. Useful as a connectivity check.

{ "name": "Ada Lovelace", "email": "ada@example.com", "tokenName": "CI token" }

Workspaces

GET/v1/workspaces

{ "workspaces": [ { "id": "ws-main", "name": "Main", "archived": false } ] }

POST/v1/workspaces

Body: { "name": "..." }. Creates a workspace in your organization; returns 201.

PATCH/v1/workspaces/{id}

Body: name and/or archived (boolean).

Boards

GET/v1/boards

{ "boards": [ { "id": "board-home", "name": "Homebase", "workspaceId": "ws-main", "archived": false } ] }

POST/v1/boards

Body: { "name": "...", "workspaceId": "ws-main", "icon"?: "home" } → 201.

PATCH/v1/boards/{id}

Body: name and/or archived.

DELETE/v1/boards/{id}

Deletes the board and everything on it (focus areas, their groups, their initiatives). The response reports what was cascaded — this is not reversible.

Focus areas

GET/v1/focus-areas

Query paramDescription
boardOnly focus areas on this board id.
{ "focusAreas": [ { "id": "fa-1", "title": "Now", "boardId": "board-home", "status": null, "archived": false } ] }

POST/v1/focus-areas

Body: { "title": "...", "boardId": "board-home", "color"?: "oklch(…)" } → 201.

PATCH/v1/focus-areas/{id}

Body: title and/or archived.

DELETE/v1/focus-areas/{id}

Deletes the column and every initiative in it (cascade reported in the response). Not reversible.

Initiatives

GET/v1/initiatives

Query paramDescription
boardOnly initiatives whose focus area is on this board.
focusOnly initiatives in this focus area id.

GET/v1/initiatives/{id}

Returns one initiative, or 404 if the id doesn't exist. The initiative shape:

{
  "id": "HB-3F2K",
  "title": "Ship the Q3 landing page",
  "focusAreaId": "fa-1",
  "priority": "P2",
  "due": "2026-09-01",
  "status": "doing",
  "completed": false,
  "completedAt": null,
  "archived": false,
  "labels": ["web"],
  "createdAt": "2026-08-20T14:03:00.000Z"
}

POST/v1/initiatives

Body fieldDescription
title requiredThe initiative title (capped at 300 characters).
focusAreaId requiredWhere the card lands — list ids with GET /v1/focus-areas.
priorityP0–P4; anything else is stored as no priority.
dueDue date string, e.g. 2026-09-01 (capped at 64 characters).
statusOne of none · backlog · todo · doing · review · done · blocked.
descriptionLong-form text (capped at 20,000 characters).
labelsArray of strings (max 20, each ≤40 chars).
assigneesArray of member ids from GET /v1/members (max 20).

Returns 201 with the created initiative. Ids follow the app's style (a board-derived prefix plus a short code, like HB-3F2K).

curl -X POST \
  -H "Authorization: Bearer hro_…" -H "Content-Type: application/json" \
  -d '{"title":"Fix login copy","focusAreaId":"fa-1","priority":"P2"}' \
  https://twsow4mkgdvwkmsdfo3q6igdwm0xzdhg.lambda-url.us-east-1.on.aws/v1/initiatives

PATCH/v1/initiatives/{id}

Body fieldDescription
titleNew title.
priorityP0–P4, or null to clear.
dueNew due date, or null to clear.
completedtrue/false — completing stamps completedAt.
status / description / labels / assignees / archivedSame semantics as create; archived is a boolean.

Only the fields you send are changed. Returns the updated initiative.

DELETE/v1/initiatives/{id}

Permanently deletes the initiative.

Comments

GET/v1/initiatives/{id}/comments

{ "comments": [ { "id": "c1756…", "author": "Ada", "text": "On it", "ts": 1756150000000 } ] }

POST/v1/initiatives/{id}/comments

Body: { "text": "…", "author"?: "Bot name" } (text ≤4,000 chars) → 201. Comments posted by the API are attributed to the token (e.g. API (CI token)) unless author is given.

Attachments

GET/v1/initiatives/{id}/attachments

Lists attachments with fresh, short-lived downloadUrls (valid 1 hour).

POST/v1/initiatives/{id}/attachments

Two-step upload. Body: { "name": "report.pdf", "type": "application/pdf", "size": 12345 } (max 25MB). The response registers the attachment on the card and returns a presigned uploadUrl — PUT the file bytes there within 10 minutes, with the same Content-Type:

curl -X PUT -H "Content-Type: application/pdf" --data-binary @report.pdf "<uploadUrl>"

Members & invites

GET/v1/members

Your workspace roster — ids here are what assignees accepts.

POST/v1/invites

Body: { "email"?: "person@co.com", "role"?: "member|admin|viewer", "boardIds"?: ["board-…"] } — an empty boardIds array means workspace-only (no boards). Returns the invite link; when email is set, only that address can accept it. (The API creates the invite — it does not send the email; share the link yourself.)

Shared workspaces

Your token also sees organizations you've been invited to: boards, focus areas, and initiatives shared with you appear in the same lists, and you can write to them with the same endpoints. Two rules carry over from the app: viewers are read-only (writes return 403), and you can only create invites for your own organization.

Webhooks & deploy cards

Inbound — GitHub pushes

Connect a repository from Settings → Integrations → GitHub feed: herro gives you a webhook URL (POST /v1/github-webhook?k=…) and a secret for the repo's webhook settings. Deliveries are verified against the raw body with X-Hub-Signature-256 (HMAC SHA-256) — unsigned or mis-signed deliveries are rejected.

Pushes appear in your team's activity feed, and any commit message that mentions an initiative id — like HB-3F2K: fix header — links that commit onto the card as a comment.

Auto deploy-cards

Turn on Auto-create deploy cards on the GitHub feed and pick a focus area. From then on, work tracks itself:

POST/v1/deploys/complete

Universal completion ping for pipelines GitHub can't see (Amplify, Jenkins, bare scripts). Bearer-token authenticated; needs a read & write token. Body: { "repo": "org/name", "branch": "main", "status"?: "success"|"failure", "note"?: "…" } — closes (or flags) the open deploy card for that repo+branch.

# last line of any deploy script:
curl -X POST -H "Authorization: Bearer hro_…" -H "Content-Type: application/json" \
  -d '{"repo":"acme/site","branch":"main","note":"deploy #342"}' \
  https://twsow4mkgdvwkmsdfo3q6igdwm0xzdhg.lambda-url.us-east-1.on.aws/v1/deploys/complete

POST/v1/deploys/ingest

The no-webhook path: report a finished deploy and herro pulls the story itself — the commit range since the last successful deploy is read from the GitHub API using your Settings → Integrations GitHub token (needs read access to the repo's contents), classified by changed files through your per-repo rules, and the card is created already completed. Failed deploys create nothing — their commits roll into the next successful deploy's card. Body: { "repo", "branch", "status"?, "commitId"? | ("appId" + "jobId"), "note"? } — with appId+jobId, herro resolves the deployed commit from the Amplify job itself.

For AWS Amplify specifically: an EventBridge rule on aws.amplify deployment-status events targeting an API destination that calls this endpoint gives you fully automatic completion with no pipeline changes.

POST/v1/deploys/slack-digest

Writes the day's deploy rundown — the same one-line-per-focus-area summary as the board's ✨ Summarize (All) — and posts it to your Slack incoming webhook from Settings → Integrations. Meant to be fired nightly by a scheduler. Body: { "board"?, "dryRun"? } — board scopes the digest to one board's focus areas, dryRun: true returns the message without posting. Days with no deploys post nothing.

Outbound — Slack, Teams, Zapier

Point herro at a Slack incoming webhook, a Teams webhook, or a Zapier catch hook from Settings → Integrations, and workspace activity is delivered there as it happens.

Errors

Errors are JSON with an error message: