ShrubberyDocs
Sign in

POST /api/v1/sync

Drive a Shrubbery state transition from an external system. The lookup is keyed by (externalSystem, externalId, assigner) where assigner = token owner; the matching row is updated to the requested status. completed_at and activated_at are populated server-side when relevant.

Quick start

curl -X POST https://shrubbery.eu/api/v1/sync \
  -H "Authorization: Bearer $SHRUBBERY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "externalSystem": "jira",
    "externalId": "PROJ-123",
    "newStatus": "Completed"
  }'

A successful response is 200 OK with the Shrubbery's id and the applied newStatus.

Reference

Notes

  • Scope is implicit. The lookup is silently filtered to rows where the assigner equals the token owner. There is no way for a token to mutate another user's rows; a mismatched external ID returns 404 not_found rather than leaking existence.
  • Allowed transitions. Sync accepts Active, Completed, and Refused. It does not accept Pending_Handshake (no path to retract consent via external systems), Draft (drafts have no external mapping), or Renegotiated (which is an event type, not a status — see the Renegotiation Flow).
  • Audit attribution. The resulting shrubbery_events row carries actor_kind = api_token with the calling token's ID. CFS arithmetic treats the transition identically to a user-driven one; the Dossier surfaces the actor distinction.
  • Idempotency. Replaying the same transition is safe: the second call updates the row to the already-current status and returns 200. completed_at / activated_at are only set on the first transition into the relevant state, not refreshed.

Last updated: 17 May 2026