ShrubberyDocs
Sign in

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

EnvironmentURL
Productionhttps://shrubbery.eu
Local devhttp://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.

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 returns 400 invalid_payload.
  • Validation errors: when a payload fails Zod validation, the response includes a details field with the flattened error map. Programmatic clients should not depend on the shape of details — treat it as opaque human-readable debugging context. The stable contract is the error discriminator and the HTTP status code.
  • Idempotency: /api/v1/inbox enforces uniqueness on (externalSystem, externalId, assigner). Replaying the same payload returns 409 duplicate_external_ref and does not insert a second row.
  • Rate limits: /api/gather enforces per-user rate limits (Upstash-backed) and returns 429 rate_limited with a Retry-After header 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_events with actor_kind = api_token and the token ID. The full Audit Trail is visible per Shrubbery on its Detail Sheet.

Endpoints

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