Docs · API reference

greatfeedback.ai API reference

greatfeedback.ai API reference

The /api/v1/* surface is the stable programmatic API. Use it from CI scripts, language SDKs, or any Claude Code agent that needs to read or update workspace annotations.

For dashboard-style flows, the same endpoints accept a Cognito session, so the FE composables and a third-party CLI share one contract.

Base URLs

Stage Base URL
prd https://api.greatfeedback.ai
stg https://stg-api.greatfeedback.ai
dev https://dev-api.greatfeedback.ai
local http://localhost:11001

Authentication

Pass the bearer token in the standard Authorization header.

Authorization: Bearer gfat_<token>

Tokens are minted in the dashboard at workspace settings → API tokens, or via POST /api/workspaces/{ws}/api-tokens (OWNER role required to mint). Tokens are clamped to MEMBER or GUEST at issuance — OWNER and ADMIN are not assignable. The plaintext is shown ONCE at creation time; only the SHA-256 hash + last-4 chars persist on the row.

Cognito session

Authorization: Bearer <Cognito ID token>

The dashboard FE uses this. Same /api/v1/* endpoints work for both auth styles; route handlers don't care which one was used.

Required headers

Header When Description
Authorization always Bearer gfat_* or Bearer <Cognito>
X-GF-Workspace session callers Workspace id; API tokens carry this implicitly
Idempotency-Key POST writes Optional; recommended for retried writes

API tokens carry their workspace id internally — no X-GF-Workspace header is needed when using gfat_*.

Endpoints

Sites

Method Path Min role Notes
GET /api/v1/sites GUEST List workspace sites. Excludes mcp_token.
GET /api/v1/sites/{id} GUEST Site detail. Excludes mcp_token.

Annotations

Method Path Min role Notes
GET /api/v1/sites/{id}/annotations GUEST Filters: ?status=&kind=&since=<epoch_ms>&auto_fix=. Cursor pagination.
GET /api/v1/annotations/{id} GUEST Detail with replies + resolutions.
PATCH /api/v1/annotations/{id} GUEST Body: {status?, summary?, assignee_id?}.
POST /api/v1/annotations/{id}/replies GUEST Body: {body, author_name?}. Author is agent for token callers.
GET /api/v1/annotations/{id}/markdown GUEST Agent-paste-friendly markdown blob.
POST /api/v1/annotations/{id}/run-personas MEMBER Body: {persona_ids: [...]}. Cost-bearing.

GUEST tokens that try run-personas get 403 with Insufficient role.

The auto_fix=true|false filter restricts to (or excludes) submissions the original submitter authorised for unattended automated change. See § Auto-fix and resolution evidence.

Resolutions (auto-fix evidence)

Method Path Min role Notes
POST /api/v1/annotations/{id}/resolutions/upload-url MEMBER Body `{kind: "before"
POST /api/v1/annotations/{id}/resolutions MEMBER Body {before_key?, after_key?, diff_url?, explanation?}. At least one required. diff_url must be https://. explanation capped at 4 KB. Triggers async submitter notification.
GET /api/v1/annotations/{id}/resolutions GUEST List the resolutions attached to an annotation, oldest-first.

A RESOLUTION row records what changed to address the annotation. Multiple resolutions per annotation are allowed (fix → revert → re-fix). On create, the original submitter is notified asynchronously: by Resend email if their address is on file, otherwise via a REPLY row appended to the annotation thread (visible to anonymous submitters when they revisit the page). The notification_status field on the row reports the outcome.

API tokens (workspace settings)

Method Path Auth Notes
POST /api/workspaces/{ws}/api-tokens OWNER session Body: {name, role?, expires_at?}. Returns {token, ...} ONCE.
GET /api/workspaces/{ws}/api-tokens session OWNER sees all; MEMBER sees only their own.
DELETE /api/workspaces/{ws}/api-tokens/{id} session OWNER revokes any; MEMBER revokes their own.

These management endpoints are session-only; you cannot mint or revoke a token via another token.

Secrets (dashboard-only)

Method Path Auth Notes
GET /api/widget/sites/{id}/secrets OWNER session Returns {widget_token, mcp_token, loader_secret_id}.
POST /api/widget/sites/{id}/secrets/rotate-mcp-token OWNER session Mints a fresh MCP token; old one immediately stops resolving.

API tokens cannot read these endpoints. Tokens are clamped to MEMBER, while secrets require OWNER, so the auth check fails. The dashboard is the only place that ever reveals mcp_token or widget_token.

Personas

Method Path Min role Notes
GET /api/personas/mine GUEST Defaults + customs in the caller's workspace. Ordered: category asc, defaults before customs in a category, name asc.
GET /api/personas/category/{category} GUEST Same ordering, scoped to one category.
GET /api/personas/{id} GUEST Workspace-scoped read.
POST /api/personas MEMBER Create a custom persona.
PATCH /api/personas/{id} MEMBER Edit any persona (default or custom).
DELETE /api/personas/{id} MEMBER Delete any persona.
POST /api/personas/reset-defaults MEMBER Body {slug?}slug=null resets all defaults; passing a slug resets that one. Returns {reset_count}. Customs untouched.

Each new workspace is seeded with 60 default personas (is_default=true) at creation. is_built_in is no longer part of the API; the legacy GET /api/personas/built-in endpoint is removed.

MCP tools

Configure once: claude mcp add greatfeedback https://api.greatfeedback.ai/mcp/<mcp_token>.

The MCP server exposes:

  • list_annotations(status?, kind?, since?, auto_fix?, limit?) — discover open work. Pass auto_fix=true to pick only submissions the submitter authorised for unattended automated change.
  • read_annotation(id) — full body, screenshot, computed styles, replies, markdown.
  • acknowledge(id) — flip status * -> ack.
  • reply(id, message) — post an agent reply on the thread.
  • resolve(id, summary) — flip status * -> resolved with a summary.
  • patch_annotation(id, status?, summary?, assignee_id?) — generalised write.
  • get_resolution_upload_url(annotation_id, kind, content_type) — presign a before / after screenshot upload. Returns {url, method, headers, key, expires_in_seconds}. PUT bytes to url with headers, then pass key back to attach_resolution.
  • attach_resolution(annotation_id, before_key?, after_key?, diff_url?, explanation?) — append a RESOLUTION row recording what changed. At least one of the four fields must be present; diff_url must be https://; explanation is markdown, 4 KB cap. Auto-notifies the submitter (email when an address is on file, otherwise a thread reply).
  • list_resolutions(annotation_id) — list the resolutions already attached, oldest-first. Use this to avoid duplicating work.
  • run_personas(id, persona_ids?) — kick off the AI persona pipeline.

Auto-fix and resolution evidence

Each annotation carries an auto_fix boolean. When true, the submitter authorised the consumer system (an agent, a CI bot, the engineering team) to apply the change without further discussion. Anonymous submitters always land false regardless of what the client sent — the server coerces.

Stakeholder tokens have a per-row auto_fix_default (never | optional_off | optional_on) that gates whether the toggle renders for them in the widget at all and what initial state it has. Manage via:

Method Path Min role Notes
POST /api/widget/sites/{id}/stakeholders MEMBER Body {name?, email?, auto_fix_default?}.
PATCH /api/widget/sites/{id}/stakeholders/{token} MEMBER Body {name?, email?, auto_fix_default?}.
GET /api/widget/sites/{id}/stakeholders GUEST List existing.
DELETE /api/widget/sites/{id}/stakeholders/{token} MEMBER Revoke.

End-to-end agent flow:

TOKEN=gfat_<your token>
ANN=$(curl -sH "Authorization: Bearer $TOKEN" \
  "https://api.greatfeedback.ai/api/v1/sites/<site>/annotations?auto_fix=true" \
  | jq -r '.items[0].id')

# Upload screenshots (optional)
PRE=$(curl -sX POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"kind":"after","content_type":"image/png"}' \
  "https://api.greatfeedback.ai/api/v1/annotations/$ANN/resolutions/upload-url")
URL=$(echo "$PRE" | jq -r .url); KEY=$(echo "$PRE" | jq -r .key)
curl -X PUT --data-binary @after.png -H "Content-Type: image/png" "$URL"

# Attach the resolution
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d "{\"after_key\":\"$KEY\",\"diff_url\":\"https://github.com/x/y/pull/42\",\"explanation\":\"Increased CTA contrast.\"}" \
  "https://api.greatfeedback.ai/api/v1/annotations/$ANN/resolutions"

MCP tokens authenticate the caller as the site's MCP integration; every write tool stamps the audit log with actor_id="mcp:<site_id>" so an operator can grep MCP calls separately from human or API-token actions.

Status taxonomy

"new" | "ack" | "triaged" | "in_progress" | "resolved" | "won_t_fix" | "duplicate"
  • new — created via the widget; nobody's looked at it yet.
  • ack — "we saw it"; no work yet.
  • triaged — looked at and filed (e.g., into Linear/Jira). Distinct from ack.
  • in_progress — work underway.
  • resolved — done.
  • won_t_fix — closed without action.
  • duplicate — closed because another annotation covers it; pair with a summary like "duplicate of ".

Unknown status values 400 at request validation.

Pagination

List endpoints accept ?cursor=...&limit=.... Default limit 50, max 200.

Cursors are server-issued and HMAC-tamper-protected. A tampered cursor 400s; a missing cursor returns the first page. The cursor encodes the last row's sort key plus a signature; decoding it without the secret is rejected.

GET /api/v1/sites/{id}/annotations?limit=50
→ { "items": [...], "next_cursor": "<opaque>" }

GET /api/v1/sites/{id}/annotations?limit=50&cursor=<opaque>
→ { "items": [...], "next_cursor": "<opaque or null>" }

The last page returns "next_cursor": null.

Rate limits

Bucket Limit Window
Per token 600 60s
Per IP 30 60s

Both buckets must allow the request. Excess returns 429 with a reason string. Recovery happens automatically once the 60-second window rolls over.

The WAF adds an additional per-IP rule at the edge — those rejections never reach the application layer and don't appear in audit logs.

HTTP error codes

Code Meaning
200 / 201 Success
400 Bad request (validation, unknown status, tampered cursor)
401 Missing / bad-format / unknown / revoked / expired token; deleted workspace (token caller)
403 Token valid but role insufficient; deleted workspace (session caller)
404 Resource doesn't exist OR is in a workspace the caller doesn't own
410 Invitation already accepted / revoked
422 Pydantic validation error (request body didn't match the schema)
429 Rate limited (see limits above)
500 Server error — please report

401 responses include a WWW-Authenticate: Bearer error="invalid_token" challenge with the failure reason in error_description. Clients should treat any 401 as "stop retrying with this credential" and either rotate the token or sign in again.

A workspace soft-deleted in the 30-day grace window returns 401 to API-token callers (so a CI loop stops retrying) and 403 to session callers (so the FE can render the "restore" banner). The OWNER restoring the workspace flips both back to 200.

Quick start (curl)

TOKEN=gfat_<your token>

# List sites
curl -H "Authorization: Bearer $TOKEN" \
  https://api.greatfeedback.ai/api/v1/sites

# List annotations on a site
curl -H "Authorization: Bearer $TOKEN" \
  "https://api.greatfeedback.ai/api/v1/sites/$SITE_ID/annotations?status=new"

# Mark one resolved
curl -X PATCH \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"status":"resolved","summary":"shipped in v2"}' \
  https://api.greatfeedback.ai/api/v1/annotations/$ANN_ID

# Reply on the thread
curl -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"body":"fixed in #1234"}' \
  https://api.greatfeedback.ai/api/v1/annotations/$ANN_ID/replies

Token rotation

There's no rotation grace period — revoking a token invalidates it immediately. To rotate:

  1. Mint a new token in the dashboard.
  2. Roll the new token into your secret manager.
  3. Confirm the new token works against /api/v1/sites.
  4. Revoke the old token.

If you suspect a token leaked, revoke first and ask questions later.

Claude Code skill

A higher-level skill that wraps these endpoints in slash commands ships from a separate repo: great-feedback-skill. The skill speaks MCP for read paths and curls /api/v1/* for writes; an install.sh writes both configs.