Configuration
Flows, webhook endpoints, reason templates, tags and your roster, with safe concurrent editing
The settings your team edits. This is the one part of the API where two people editing at once can do lasting damage, so it works differently from everything else here.
Every write is conditional, if you let it
Every configuration resource carries a version, returned on every read.
GET /v1/console/flows → { "id": "flw_…", "version": 3, … }
PATCH /v1/console/flows/flw_…
If-Match: 3If somebody has edited that flow since you read it, your write is refused with 409 STALE_VERSION
and the response tells you the current version:
{ "error": { "code": "STALE_VERSION",
"detail": { "current_version": 4 } } }Re-read, show the person what changed underneath them, and let them decide. Do not retry a 409 automatically. A client that does has reimplemented last-write-wins on top of the control that exists to prevent it.
It catches edits made in our console too
The version moves on any real change, wherever it came from: your back office, our console, our
support team, or your own /v1 integration. So If-Match protects you against a colleague editing
the same flow in a different window, which is the case that actually happens.
If-Match is optional. A write without it succeeds unconditionally, which is what keeps existing
integrations working. That means the protection is real but opt-in: if you are building an editor
that a human will use, send it. The version is on every read, so there is nothing to look up.
A malformed If-Match is rejected with 400 INVALID_IF_MATCH rather than ignored, so you can never
believe you are writing conditionally when you are not. If-Match: * means "the row must exist" and
imposes no version check.
Name the environment
A flow, a webhook endpoint and a tag each belong to one environment.
If the acting seat can see both, creating one must say which:
POST /v1/console/flows
{ "name": "Onboarding", "mode": "live" }Leaving it out is 400 MODE_REQUIRED. There is deliberately no default: guessing is how somebody
ends up with a live endpoint they believed was a sandbox one. A seat that holds only one environment
is not asked, because there is nothing to choose, and naming the other one is
400 ENVIRONMENT_NOT_ALLOWED.
What is here
| Resource | Read | Write |
|---|---|---|
/v1/console/flows | flows.read | flows.manage |
/v1/console/webhook-endpoints | webhooks.read | webhooks.manage |
/v1/console/reason-templates | reason_templates.read | reason_templates.manage |
/v1/console/tags | tags.read | tags.manage |
/v1/console/members | team.read | not available, see below |
Webhook signing secrets are never returned, on any endpoint. They are shown once when created or rotated, in our console. A listing that carried them would put every one of your webhook secrets into whatever logs your back office writes.
Your roster
GET /v1/console/membersSeat ids, your own external_id for each person, their role, their environments, and can_decide.
That last one is resolved for you, because rendering an assignee picker means knowing who can
actually work a case, and a raw permission delta does not answer that without reimplementing our role
presets.
No names and no email addresses: you already know who your people are, and the external_id is what
maps a seat back to them.
Creating and promoting seats is not available over the API, deliberately. It is the one configuration write where a bug in your code could grant somebody authority rather than change a setting. Your owner does it on the Team page.