API Reference Overview
Shrubbery exposes three HTTP endpoints today. Two are webhook surfaces meant for external systems (Jira, Linear, GitHub, custom) to push commitments and status changes inbound; the third is the first-party Smart Paste extractor that the in-app capture surface calls. Each endpoint documents its full request and response schema below, sourced from the same Zod definitions the route handlers use at runtime — the spec cannot drift from the code.
Base URL
| Environment | URL |
|---|---|
| Production | https://shrubbery.eu |
| Local dev | http://localhost:3000 |
All requests use HTTPS in production; HTTP is accepted only on localhost.
Authentication
Two authentication schemes are in play. Pick the one matching the route you are calling.
Bearer Token (webhook routes)
POST /api/v1/inbox and POST /api/v1/sync authenticate via an Authorization: Bearer <token> header. Mint a token in the Shrubbery app at Settings → API Tokens. The plaintext is shown once at creation; only the SHA-256 hash and a short prefix persist. Tokens are scoped per-user — the token owner is recorded as the assigner on rows created through /api/v1/inbox, and as the implied row owner for state changes through /api/v1/sync.
A token owner cannot operate on another user's rows. Inbox payloads where assignerEmail resolves to a user other than the token owner return 403 forbidden. Sync lookups are silently scoped to assigner = token owner, so a sync request against an external ID owned by a different user returns 404 not_found.
Session Cookie (Smart Paste)
POST /api/gather is a first-party endpoint called by the in-app Smart Paste flow. It authenticates via the user's active Supabase session cookie. It cannot be reached by webhook tokens — there is no Bearer path. Programmatic clients should not target /api/gather; they should call /api/v1/inbox instead.
/api/gather additionally requires a stored BYOK provider key (OpenAI or Anthropic). The endpoint calls the upstream LLM with the caller's own key; there is no platform-side AI fallback.
Conventions
- Content type: all routes accept and return
application/json. Any other body type returns400 invalid_payload. - Validation errors: when a payload fails Zod validation, the response includes a
detailsfield with the flattened error map. Programmatic clients should not depend on the shape ofdetails— treat it as opaque human-readable debugging context. The stable contract is theerrordiscriminator and the HTTP status code. - Idempotency:
/api/v1/inboxenforces uniqueness on(externalSystem, externalId, assigner). Replaying the same payload returns409 duplicate_external_refand does not insert a second row. - Rate limits:
/api/gatherenforces per-user rate limits (Upstash-backed) and returns429 rate_limitedwith aRetry-Afterheader in seconds when exceeded. The webhook routes are not rate-limited today; the backlog tracks per-token throttling as a Sprint 35+ candidate. - Audit: every state-changing call appends a row to
shrubbery_eventswithactor_kind = api_tokenand the token ID. The full Audit Trail is visible per Shrubbery on its Detail Sheet.
Endpoints
POST /api/v1/inbox— see Inbox Webhook.POST /api/v1/sync— see Sync Webhook.POST /api/gather— see Smart Paste (gather).
The full OpenAPI 3.0.3 spec is browsable at /api-reference — interactive Scalar viewer with request / response schemas, status codes, authentication blocks, and a Test Request client.
Downloading the spec
Scalar's top-right Download OpenAPI Document menu exports the same spec as JSON or YAML. Use the downloaded file to:
- Import into Postman. File → Import → Upload Files → select the JSON. Postman creates a collection mirroring the operations; environment variables (
SHRUBBERY_TOKEN, etc.) need to be added manually. - Import into Insomnia. Create → Import From → File → select the JSON. Same outcome as Postman.
- Generate a typed client. Feed the spec to
openapi-typescript,openapi-generator, or similar — every public field round-trips because the spec is auto-emitted from the runtime Zod schemas (see Sprint 34a).
The raw spec file is also tracked in the repo at content/docs/_generated/openapi.json and regenerated on every PR via npm run docs:check (CI gate from Sprint 35c).
Versioning
The current API is version 1.0.0 (info.version in the spec). When breaking changes ship, v2 will live at /api-reference/v2 and the existing /api-reference URL will remain pinned to v1 — backlog entry tracks the routing reservation. Until then, every change to the spec is additive and backwards-compatible.
Last updated: 17 May 2026