{"openapi":"3.1.0","info":{"title":"SentraBridge API","version":"1.0","summary":"Manage and watch SentraBridge flows from your own scripts and systems.","description":"The supported SentraBridge API for city IT teams and vendors. Sign in with a personal API token (`Authorization: Bearer sbk_…`) made under Settings → API & AI assistants. A token acts as its person, in one workspace, with the lower of the person's role and the token's own limit.\n\n**Pass-through.** SentraBridge does not keep the records it moves. This API returns settings, counts, record IDs, timings and redacted errors. The only endpoint that returns records is the Data API, which reads them live from the source for each call. Flow tests return the made-up records saved with the test.\n\n**Stable surface.** Only the endpoints in this document are supported. Other addresses under `/v1` belong to the web app and can change without notice. Within `/v1` we add fields and endpoints, and do not remove or rename documented ones.","contact":{"name":"SentraBridge","url":"https://sentrabridge.com/developers/"}},"servers":[{"url":"https://app.sentrabridge.com","description":"This SentraBridge."}],"tags":[{"name":"Identity","description":"Who the token is, and the session a write call needs."},{"name":"Flows","description":"List, read, describe, move as YAML, run, and turn flows on or off."},{"name":"Flow tests","description":"Saved tests run in dry mode: made-up records in, nothing written or sent."},{"name":"Runs","description":"Run history, one run step by step, replaying failed records, and tracing a record ID."},{"name":"Monitoring","description":"Health of the workspace: success rates, slow steps and data-health issues."},{"name":"Connections","description":"Connected systems and their last test. Credentials are never returned."},{"name":"Approvals","description":"Requests waiting for a person, and deciding them."},{"name":"Templates","description":"Ready-made flows, and making a flow from one."},{"name":"Audit and usage","description":"The activity log as a file, and this month's usage against the plan."},{"name":"Data API","description":"Read-only flows published as JSON endpoints. Each call runs the flow live; records are not stored."},{"name":"Webhooks","description":"Send an event into a flow at its private address."},{"name":"Status","description":"Platform status, feeds and status updates. Public, no token."},{"name":"MCP","description":"SentraBridge as a Model Context Protocol server for AI assistants."},{"name":"Reference","description":"This document, and addresses for firewalls."}],"paths":{"/v1/me":{"get":{"operationId":"getMe","tags":["Identity"],"summary":"Who am I","description":"The token's person, its workspace and the role it is acting with. `user.role` is the lower of the person's role and the token's limit. A good first call to check a token.","security":[{"apiToken":[]}],"x-minimum-role":null,"x-auth":"token","x-rate-limit":null,"x-write-handshake":false,"responses":{"200":{"description":"The person, the workspace and the role in use.","content":{"application/json":{"schema":{"type":"object","properties":{"auth":{"type":"string"},"user":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"email":{"type":"string"},"platform_admin":{"type":"boolean"},"must_change_password":{"type":"boolean"},"role":{"type":"string"},"mfa_enabled":{"type":"boolean"},"mfa_setup_required":{"type":"boolean"},"mfa_clerk_required":{"type":"boolean"},"signed_in_with":{"type":"string"},"departments":{"description":"Empty (null) in the captured example."}}},"workspace":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"timezone":{"type":"string"},"status":{"type":"string"},"alert_emails":{"type":"array"},"alert_on":{"type":"string"},"status_page":{"type":"boolean"},"slug":{"type":"string"},"require_mfa":{"type":"boolean"},"departments":{"type":"array"},"ai_enabled":{"type":"boolean"},"ai_records":{"type":"boolean"},"ai_available":{"type":"boolean"}}},"workspaces":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"role":{"type":"string"}}}},"version":{"type":"string"}}},"example":{"auth":"local","user":{"id":3,"name":"Alex Rivera","email":"person1@example.gov","platform_admin":false,"must_change_password":false,"role":"admin","mfa_enabled":false,"mfa_setup_required":false,"mfa_clerk_required":false,"signed_in_with":"password","departments":null},"workspace":{"id":"01m74hqe1m08znmjtembcq6sf7","name":"City of Demo","timezone":"America/Los_Angeles","status":"active","alert_emails":[],"alert_on":"failed","status_page":true,"slug":"city-of-demo","require_mfa":false,"departments":[],"ai_enabled":true,"ai_records":false,"ai_available":true},"workspaces":[{"id":"01m74hqe1m08znmjtembcq6sf7","name":"City of Demo","status":"active","role":"admin"},{"id":"01m75mava07vbsmk78aa8vnyhm","name":"City of Demo (Test)","status":"active","role":"admin"}],"version":"0.2.0"}}}},"401":{"description":"The API token is missing, wrong, expired or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/auth/csrf":{"get":{"operationId":"startWriteSession","tags":["Identity"],"summary":"Start a session for write calls","description":"Sets two cookies: a session cookie and `XSRF-TOKEN`. Calls that change something (POST, PATCH) must send both cookies back, plus the URL-decoded `XSRF-TOKEN` value in an `X-XSRF-TOKEN` header, next to the bearer token. Read calls need only the token. The session lasts two hours; on a 419 answer, call this again.","security":[],"x-minimum-role":null,"x-auth":"none","x-rate-limit":null,"x-write-handshake":false,"responses":{"204":{"description":"No body. The cookies are in the Set-Cookie headers."}}}},"/v1/flows":{"get":{"operationId":"listFlows","tags":["Flows"],"summary":"List flows","description":"Every flow in the workspace, most recently changed first: name, trigger, schedule, on or off, and the last run. Not paged: all flows are returned.","security":[{"apiToken":[]}],"x-minimum-role":"viewer","x-auth":"token","x-rate-limit":null,"x-write-handshake":false,"responses":{"200":{"description":"All flows.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"enabled":{"type":"boolean"},"trigger":{"type":"string"},"schedule":{"type":"string"},"steps":{"type":"integer"},"connectors":{"type":"array","items":{"type":"string"}},"next_run_at":{"description":"Empty (null) in the captured example."},"last_run_at":{"type":"string"},"last_status":{"type":"string"},"version":{"type":"integer"},"template":{"type":"string"},"updated_at":{"type":"string"},"last_success_at":{"type":"string"},"department":{"description":"Empty (null) in the captured example."}}}}}},"example":{"data":[{"id":"01m75v8y18hknat4d18ahm7m1a","name":"New pothole requests become Scheduler visits","description":"Every 15 minutes, takes new or changed pothole requests from Sentra 311 and creates a visit for the street repair crew in Sentra Scheduler.","enabled":false,"trigger":"trigger.schedule","schedule":"*/15 * * * *","steps":5,"connectors":["01m74hqe33cy19f80c393jnpz7","01m74hqe33cy19f80c393jnpz8"],"next_run_at":null,"last_run_at":"2026-11-11T21:46:12+00:00","last_status":"ok","version":1,"template":"311-potholes-to-scheduler","updated_at":"2026-11-11T21:46:12+00:00","last_success_at":"2026-11-11T21:46:11+00:00","department":null},{"id":"01m761r01x3qf1macy3wfkp3k1","name":"Post severe weather alerts on the city website","description":"Every 15 minutes, takes severe or extreme National Weather Service alerts for your state and posts them as website alerts in Sentra CMS.","enabled":false,"trigger":"trigger.schedule","schedule":"*/15 * * * *","steps":5,"connectors":["01m74mfya73ey4wz1snyrtapfc","01m74hqe35wmejccfkdanf375r"],"next_run_at":null,"last_run_at":null,"last_status":null,"version":2,"template":"nws-alerts-to-cms","updated_at":"2026-11-11T19:36:01+00:00","last_success_at":null,"department":null}]}}}},"401":{"description":"The API token is missing, wrong, expired or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token's role is below viewer, or the caller's address is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/flows/{id}":{"get":{"operationId":"getFlow","tags":["Flows"],"summary":"Get a flow","description":"One flow with its steps and wires (`graph`), anything that would stop it running (`problems`) and its notifications. Step settings are included; saved secrets are named in `secrets_saved`, never returned. For a webhook flow, `webhook_url` is its private address: treat it like a password.","security":[{"apiToken":[]}],"x-minimum-role":"viewer","x-auth":"token","x-rate-limit":null,"x-write-handshake":false,"parameters":[{"name":"id","in":"path","required":true,"description":"Flow ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"The flow.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"enabled":{"type":"boolean"},"trigger":{"type":"string"},"schedule":{"type":"string"},"steps":{"type":"integer"},"connectors":{"type":"array","items":{"type":"string"}},"next_run_at":{"description":"Empty (null) in the captured example."},"last_run_at":{"type":"string"},"last_status":{"type":"string"},"version":{"type":"integer"},"template":{"type":"string"},"updated_at":{"type":"string"},"last_success_at":{"type":"string"},"department":{"description":"Empty (null) in the captured example."},"graph":{"type":"object","properties":{"nodes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"position":{"type":"object","properties":{"x":{"type":"integer"},"y":{"type":"integer"}}},"data":{"type":"object","properties":{"label":{"type":"string"},"config":{"type":"object"},"note":{"type":"string"}}}}}},"edges":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"source":{"type":"string"},"target":{"type":"string"},"sourceHandle":{"description":"Empty (null) in the captured example."}}}}}},"webhook_url":{"description":"Empty (null) in the captured example."},"problems":{"type":"array"},"secrets_saved":{"type":"array"},"public_status":{"type":"boolean"},"notify":{"type":"object","properties":{"on":{"type":"array"},"emails":{"type":"array"}}}}}}},"example":{"data":{"id":"01m75v8y18hknat4d18ahm7m1a","name":"New pothole requests become Scheduler visits","description":"Every 15 minutes, takes new or changed pothole requests from Sentra 311 and creates a visit for the street repair crew in Sentra Scheduler.","enabled":false,"trigger":"trigger.schedule","schedule":"*/15 * * * *","steps":5,"connectors":["01m74hqe33cy19f80c393jnpz7","01m74hqe33cy19f80c393jnpz8"],"next_run_at":null,"last_run_at":"2026-11-11T21:46:12+00:00","last_status":"ok","version":1,"template":"311-potholes-to-scheduler","updated_at":"2026-11-11T21:46:12+00:00","last_success_at":"2026-11-11T21:46:11+00:00","department":null,"graph":{"nodes":[{"id":"start","type":"trigger.schedule","position":{"x":40,"y":160},"data":{"label":"Every 15 minutes","config":{"cron":"*/15 * * * *"},"note":""}},{"id":"read","type":"source.read","position":{"x":300,"y":160},"data":{"label":"Changed 311 requests","config":{"connection_id":"01m74hqe33cy19f80c393jnpz7","resource":"requests","only_changed":true,"max_records":500},"note":""}}],"edges":[{"id":"start-read","source":"start","target":"read","sourceHandle":null},{"id":"read-pothole","source":"read","target":"pothole","sourceHandle":null}]},"webhook_url":null,"problems":[],"secrets_saved":[],"public_status":false,"notify":{"on":[],"emails":[]}}}}}},"401":{"description":"The API token is missing, wrong, expired or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token's role is below viewer, or the caller's address is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Nothing with that ID in this workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updateFlow","tags":["Flows"],"summary":"Turn a flow on or off, or rename it","description":"Send only what should change. Turning a flow on is refused with 422 while it has problems, or when the workspace requires passing tests and they do not pass. The change is written to the activity log. The web app also sends `graph` here; its format is not part of the supported API, so change steps with YAML import instead.","security":[{"apiToken":[]}],"x-minimum-role":"builder","x-auth":"token","x-rate-limit":null,"x-write-handshake":false,"parameters":[{"name":"id","in":"path","required":true,"description":"Flow ID.","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean","description":"true turns the flow on, false turns it off."},"name":{"type":"string","description":"New name, up to 120 characters."},"description":{"type":"string","description":"New description, up to 1,000 characters."}}},"example":{"enabled":true}}}},"responses":{"200":{"description":"The flow after the change.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"enabled":{"type":"boolean"},"trigger":{"type":"string"},"schedule":{"description":"Empty (null) in the captured example."},"steps":{"type":"integer"},"connectors":{"type":"array"},"next_run_at":{"description":"Empty (null) in the captured example."},"last_run_at":{"description":"Empty (null) in the captured example."},"last_status":{"description":"Empty (null) in the captured example."},"version":{"type":"integer"},"template":{"type":"string"},"updated_at":{"type":"string"},"last_success_at":{"description":"Empty (null) in the captured example."},"department":{"description":"Empty (null) in the captured example."},"graph":{"type":"object","properties":{"nodes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"position":{"type":"object","properties":{"x":{"type":"integer"},"y":{"type":"integer"}}},"data":{"type":"object","properties":{"label":{"type":"string"},"config":{"type":"array"},"note":{"type":"string"}}}}}},"edges":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"source":{"type":"string"},"target":{"type":"string"},"sourceHandle":{"description":"Empty (null) in the captured example."}}}}}},"webhook_url":{"description":"Empty (null) in the captured example."},"problems":{"type":"array"},"secrets_saved":{"type":"array"},"public_status":{"type":"boolean"},"notify":{"type":"object","properties":{"on":{"type":"array"},"emails":{"type":"array"}}}}}}},"example":{"data":{"id":"01m769behch8x1ym13ssqct3xv","name":"Data quality alert (sub-flow)","description":"A reusable sub-flow: other flows hand it the records that failed their checks, and it emails the data team a summary with a CSV attached.","enabled":true,"trigger":"trigger.manual","schedule":null,"steps":3,"connectors":[],"next_run_at":null,"last_run_at":null,"last_status":null,"version":1,"template":"data-quality-alert","updated_at":"2026-11-11T21:48:47+00:00","last_success_at":null,"department":null,"graph":{"nodes":[{"id":"t","type":"trigger.manual","position":{"x":40,"y":160},"data":{"label":"Records from another flow","config":[],"note":""}},{"id":"lim","type":"transform.limit","position":{"x":330,"y":160},"data":{"label":"First 500","config":{"count":500},"note":""}}],"edges":[{"id":"t-lim-out","source":"t","target":"lim","sourceHandle":null},{"id":"lim-mail-out","source":"lim","target":"mail","sourceHandle":null}]},"webhook_url":null,"problems":[],"secrets_saved":[],"public_status":false,"notify":{"on":[],"emails":[]}}}}}},"401":{"description":"The API token is missing, wrong, expired or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token's role is below builder, or the caller's address is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Nothing with that ID in this workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The flow cannot be turned on yet. `problems` lists what to fix, or `tests_required` is true.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/flows/{id}/describe":{"get":{"operationId":"describeFlow","tags":["Flows"],"summary":"Describe a flow in plain English","description":"What starts the flow, what each step does and which systems it reads and writes, written from its settings. No AI is used and no records are read.","security":[{"apiToken":[]}],"x-minimum-role":"viewer","x-auth":"token","x-rate-limit":null,"x-write-handshake":false,"parameters":[{"name":"id","in":"path","required":true,"description":"Flow ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"A summary, the systems involved and one sentence per step.","content":{"application/json":{"schema":{"type":"object","properties":{"summary":{"type":"string"},"trigger":{"type":"string"},"systems":{"type":"array","items":{"type":"string"}},"steps":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"type":{"type":"string"},"step":{"type":"string"},"text":{"type":"string"},"after":{"type":"array","items":{"type":"string"}}}}},"text":{"type":"string"}}},"example":{"summary":"Every 15 minutes, takes new or changed pothole requests from Sentra 311 and creates a visit for the street repair crew in Sentra Scheduler. It works with Sentra 311 (sandbox) and Sentra Scheduler (sandbox). It has 5 steps, 1 of them sending or writing somewhere. It is off.","trigger":"Starts every 15 minutes (America/Los_Angeles).","systems":["Sentra 311 (sandbox)","Sentra Scheduler (sandbox)"],"steps":[{"id":"start","label":"Every 15 minutes","type":"trigger.schedule","step":"On a schedule","text":"Starts every 15 minutes (America/Los_Angeles).","after":["Changed 311 requests"]},{"id":"read","label":"Changed 311 requests","type":"source.read","step":"Get records","text":"Reads service requests from Sentra 311 (sandbox), only the ones that changed since the last good run, up to 500 at a time.","after":["Open potholes"]}],"text":"New pothole requests become Scheduler visits\n\nEvery 15 minutes, takes new or changed pothole requests from Sentra 311 and creates a visit for the street repair crew in Sentra Scheduler. It works with Sentra 311 (sandbox) and Sentra Scheduler (sandbox). It has 5 steps, 1 of them sending or writing somewhere. It is off.\n\n1. Every 15 minutes (On a schedule): Starts every 15 minutes (America/Los_Angeles).\n2. Changed 311 requests (Get records): Reads service requests from Sentra 311 (sandbox), only the ones that changed since the last good run, up to 500 at a time.\n3. Open potholes (Filter): Keeps …"}}}},"401":{"description":"The API token is missing, wrong, expired or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token's role is below viewer, or the caller's address is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Nothing with that ID in this workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/flows/{id}/export":{"get":{"operationId":"exportFlow","tags":["Flows"],"summary":"Export a flow as YAML","description":"The whole flow as a SentraBridge flow file, for version control or moving between workspaces. Connections appear by name, never their keys. Saved secrets are left out. Tests are included.","security":[{"apiToken":[]}],"x-minimum-role":"viewer","x-auth":"token","x-rate-limit":null,"x-write-handshake":false,"parameters":[{"name":"id","in":"path","required":true,"description":"Flow ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"The flow file.","content":{"application/yaml":{"schema":{"type":"string"},"example":"# SentraBridge flow. Connections are named, never their keys: import matches them in the target client.\nsentrabridge: 1\nname: 'Copy 311 requests into the city database, with map points'\ndescription: 'Every 15 minutes, upserts new and changed Sentra 311 requests into a PostGIS table, with a map point built from latitude and longitude.'\nexported: '2026-11-11T21:48:46+00:00'\nconnections:\n  sentra_311_sandbox:\n    connector: sentra311\n    name: 'Sentra 311 (sandbox)'\n  city_database_sandbox:\n    connector: postgres\n    name: 'City database (sandbox)'\nsteps:\n  -\n    id: start\n    type: trigger.schedule\n    label: 'Every 15 minutes'\n    at:\n      - 40\n      - 160\n    settings:\n      cron: '*/15 * * * *'\n  -\n    id: read\n…"}}},"401":{"description":"The API token is missing, wrong, expired or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token's role is below viewer, or the caller's address is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Nothing with that ID in this workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/flows/import":{"post":{"operationId":"importFlow","tags":["Flows"],"summary":"Import a flow from YAML","description":"Makes a new flow from a flow file. It is always created turned off. Connections are matched by connector and name in this workspace; `notes` says what was matched or made. Importing the same file twice makes two flows.","security":[{"apiToken":[]}],"x-minimum-role":"builder","x-auth":"token","x-rate-limit":null,"x-write-handshake":false,"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"yaml":{"type":"string","description":"The flow file, starting with \"sentrabridge: 1\". Up to 1 MB."}},"required":["yaml"]},"example":{"yaml":"sentrabridge: 1\nname: 'Nightly permit export'\nsteps:\n  - {id: start, type: trigger.manual, label: 'Run by hand', at: [40, 160]}\n"}}}},"responses":{"201":{"description":"The new flow and import notes.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"enabled":{"type":"boolean"},"trigger":{"type":"string"},"schedule":{"type":"string"},"steps":{"type":"integer"},"connectors":{"type":"array","items":{"type":"string"}},"next_run_at":{"description":"Empty (null) in the captured example."},"last_run_at":{"description":"Empty (null) in the captured example."},"last_status":{"description":"Empty (null) in the captured example."},"version":{"type":"integer"},"template":{"description":"Empty (null) in the captured example."},"updated_at":{"type":"string"},"last_success_at":{"description":"Empty (null) in the captured example."},"department":{"description":"Empty (null) in the captured example."},"graph":{"type":"object","properties":{"nodes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"position":{"type":"object","properties":{"x":{"type":"integer"},"y":{"type":"integer"}}},"data":{"type":"object","properties":{"label":{"type":"string"},"config":{"type":"object"},"note":{"type":"string"}}}}}},"edges":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"source":{"type":"string"},"target":{"type":"string"},"sourceHandle":{"description":"Empty (null) in the captured example."}}}}}},"webhook_url":{"description":"Empty (null) in the captured example."},"problems":{"type":"array"},"secrets_saved":{"type":"array"},"public_status":{"type":"boolean"},"notify":{"type":"object","properties":{"on":{"type":"array"},"emails":{"type":"array"}}}}},"notes":{"type":"array","items":{"type":"string"}}}},"example":{"data":{"id":"01m769bjp928n7vqvkaezxh05w","name":"API reference sample","description":"Every 15 minutes, upserts new and changed Sentra 311 requests into a PostGIS table, with a map point built from latitude and longitude.","enabled":false,"trigger":"trigger.schedule","schedule":"*/15 * * * *","steps":4,"connectors":["01m74hqe33cy19f80c393jnpz7","01m74knwpc7xd4424q3ybrqf3b"],"next_run_at":null,"last_run_at":null,"last_status":null,"version":1,"template":null,"updated_at":"2026-11-11T21:48:51+00:00","last_success_at":null,"department":null,"graph":{"nodes":[{"id":"start","type":"trigger.schedule","position":{"x":40,"y":160},"data":{"label":"Every 15 minutes","config":{"cron":"*/15 * * * *"},"note":""}},{"id":"read","type":"source.read","position":{"x":300,"y":160},"data":{"label":"Changed 311 requests","config":{"resource":"requests","only_changed":true,"connection_id":"01m74hqe33cy19f80c393jnpz7"},"note":""}}],"edges":[{"id":"start-read-out","source":"start","target":"read","sourceHandle":null},{"id":"read-map-out","source":"read","target":"map","sourceHandle":null}]},"webhook_url":null,"problems":[],"secrets_saved":[],"public_status":false,"notify":{"on":[],"emails":[]}},"notes":["Added 1 test(s)."]}}}},"401":{"description":"The API token is missing, wrong, expired or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token's role is below builder, or the caller's address is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The file is not a valid flow file. `message` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/flows/{id}/run":{"post":{"operationId":"runFlow","tags":["Flows"],"summary":"Run a flow now","description":"Starts the flow by hand, whether it is on or off, and answers at once with the new run in `queued` state. Poll \"Get a run\" until `status` is `ok`, `partial` or `failed`.","security":[{"apiToken":[]}],"x-minimum-role":"builder","x-auth":"token","x-rate-limit":{"requests":30,"per":"minute","counted_by":"person"},"x-write-handshake":false,"parameters":[{"name":"id","in":"path","required":true,"description":"Flow ID.","schema":{"type":"string"}}],"responses":{"202":{"description":"The run that was started.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"flow_id":{"type":"string"},"flow_name":{"type":"string"},"flow_version":{"type":"integer"},"trigger":{"type":"string"},"status":{"description":"Empty (null) in the captured example."},"replay_of":{"description":"Empty (null) in the captured example."},"replay_count":{"type":"integer"},"created_at":{"type":"string"},"started_at":{"description":"Empty (null) in the captured example."},"finished_at":{"description":"Empty (null) in the captured example."},"duration_ms":{"description":"Empty (null) in the captured example."},"records_in":{"description":"Empty (null) in the captured example."},"records_out":{"description":"Empty (null) in the captured example."},"records_failed":{"description":"Empty (null) in the captured example."},"error":{"description":"Empty (null) in the captured example."}}}}},"example":{"data":{"id":"01m769ben0mnkzwcdmyjy6aqsv","flow_id":"01m769behch8x1ym13ssqct3xv","flow_name":"Data quality alert (sub-flow)","flow_version":1,"trigger":"manual","status":null,"replay_of":null,"replay_count":0,"created_at":"2026-11-11T21:48:47+00:00","started_at":null,"finished_at":null,"duration_ms":null,"records_in":null,"records_out":null,"records_failed":null,"error":null}}}}},"401":{"description":"The API token is missing, wrong, expired or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token's role is below builder, or the caller's address is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Nothing with that ID in this workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The flow has problems and cannot run. `problems` lists them.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"More than 30 calls in a minute. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/flows/{id}/runs":{"get":{"operationId":"listFlowRuns","tags":["Flows"],"summary":"List a flow's runs","description":"The flow's 50 most recent runs, newest first.","security":[{"apiToken":[]}],"x-minimum-role":"viewer","x-auth":"token","x-rate-limit":null,"x-write-handshake":false,"parameters":[{"name":"id","in":"path","required":true,"description":"Flow ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Up to 50 runs.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"flow_id":{"type":"string"},"flow_name":{"type":"string"},"flow_version":{"type":"integer"},"trigger":{"type":"string"},"status":{"type":"string"},"replay_of":{"description":"Empty (null) in the captured example."},"replay_count":{"type":"integer"},"created_at":{"type":"string"},"started_at":{"type":"string"},"finished_at":{"type":"string"},"duration_ms":{"type":"integer"},"records_in":{"type":"integer"},"records_out":{"type":"integer"},"records_failed":{"type":"integer"},"error":{"description":"Empty (null) in the captured example."}}}}}},"example":{"data":[{"id":"01m75tbvcqvmw773q9fgb66h96","flow_id":"01m74ksfj3gg6fvx6yvsv2g3w9","flow_name":"Copy 311 requests into the city database, with map points","flow_version":1,"trigger":"manual","status":"ok","replay_of":null,"replay_count":0,"created_at":"2026-11-11T17:26:51+00:00","started_at":"2026-11-11T17:26:52+00:00","finished_at":"2026-11-11T17:26:52+00:00","duration_ms":99,"records_in":41,"records_out":41,"records_failed":0,"error":null},{"id":"01m74kssqx334fnx1tvs6mmt5j","flow_id":"01m74ksfj3gg6fvx6yvsv2g3w9","flow_name":"Copy 311 requests into the city database, with map points","flow_version":1,"trigger":"manual","status":"ok","replay_of":null,"replay_count":0,"created_at":"2026-11-11T06:12:54+00:00","started_at":"2026-11-11T06:12:54+00:00","finished_at":"2026-11-11T06:12:54+00:00","duration_ms":95,"records_in":40,"records_out":40,"records_failed":0,"error":null}]}}}},"401":{"description":"The API token is missing, wrong, expired or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token's role is below viewer, or the caller's address is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Nothing with that ID in this workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/flows/{id}/tests":{"get":{"operationId":"listFlowTests","tags":["Flow tests"],"summary":"List a flow's tests","description":"The saved tests of a flow, with their made-up input records, expectations and last result. Test records are made up by the person who wrote the test; they are not records from a city system.","security":[{"apiToken":[]}],"x-minimum-role":"viewer","x-auth":"token","x-rate-limit":null,"x-write-handshake":false,"parameters":[{"name":"id","in":"path","required":true,"description":"Flow ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"The tests, plus what the designer needs to edit them.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"reads":{"type":"string"},"input":{"type":"object","properties":{"start_at":{"type":"string"},"format":{"type":"string"},"text":{"type":"string"}}},"records":{"type":"integer"},"mocks":{"type":"object"},"expectations":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"node_id":{"type":"string"},"op":{"type":"string"},"value":{"type":"integer"},"output":{"type":"string"}}}},"has_snapshot":{"type":"boolean"},"snapshot":{"type":"object","properties":{"write":{"type":"array","items":{"type":"object","properties":{"request_id":{"type":"string"},"number":{"type":"string"},"category":{"description":"Empty (null) in the captured example."},"department":{"description":"Empty (null) in the captured example."},"status":{"type":"string"},"address":{"type":"string"},"description":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"},"lat":{"description":"Empty (null) in the captured example."},"lng":{"description":"Empty (null) in the captured example."}}}}}},"last_status":{"description":"Empty (null) in the captured example."},"last_passed":{"description":"Empty (null) in the captured example."},"last_failed":{"description":"Empty (null) in the captured example."},"last_run_at":{"description":"Empty (null) in the captured example."},"updated_at":{"type":"string"}}}},"tests_on_save":{"type":"boolean"},"require_passing_tests":{"type":"boolean"},"default_start":{"type":"string"},"condition_fields":{"type":"array"},"mockable":{"type":"array","items":{"type":"object","properties":{"node_id":{"type":"string"},"label":{"type":"string"},"type":{"type":"string"},"kind":{"type":"string"}}}},"outputs":{"type":"object","properties":{"start":{"type":"object","properties":{"out":{"type":"string"}}},"read":{"type":"object","properties":{"out":{"type":"string"}}},"map":{"type":"object","properties":{"out":{"type":"string"}}},"write":{"type":"object","properties":{"out":{"type":"string"}}}}}}},"example":{"data":[{"id":"01m769bjpb8xkzb9gjmq4gekb5","name":"From preview","reads":"mocked","input":{"start_at":"read","format":"json","text":"[\n    {\n        \"description\": \"Cgivmq sals vowt (LM-2957)\",\n        \"address\": \"890 Mhlz Dpxk, Hnfu Hsea\",\n        \"source\": \"cyzahskc\",\n        \"external_ref\": \"CS-8320\",\n        \"id\": \"39904s\",\n        \"created_at\": \"2026-11-25T07:13:40+00:00\",\n        \"updated_at\": \"2026-11-25T07:13:40+00:00\",\n        \"number\": \"GBK-82269\",\n        \"status\": \"wkyc\"\n    },\n    {\n        \"id\": \"0622\",\n        \"number\": \"ZHG-26141\",\n        \"status\": \"itznqt\",\n        \"priority\": \"dwnsie\",\n        \"category\": \"Jpmo fxhx\",\n        \"category_id\": \"1\",\n        \"department\": \"Dowwu\",\n        \"assignee\": \"Lmlc Z\",…"},"records":5,"mocks":[],"expectations":[{"type":"count","node_id":"map","op":"eq","value":5,"output":"out"},{"type":"count","node_id":"write","op":"eq","value":5,"output":"out"}],"has_snapshot":true,"snapshot":{"write":[{"request_id":"39904s","number":"GBK-82269","category":null,"department":null,"status":"wkyc","address":"890 Mhlz Dpxk, Hnfu Hsea","description":"Cgivmq sals vowt (LM-2957)","created_at":"2026-11-25T07:13:40+00:00","updated_at":"2026-11-25T07:13:40+00:00","lat":null,"lng":null},{"request_id":"0622","number":"ZHG-26141","category":"Jpmo fxhx","department":"Dowwu","status":"itznqt","address":"132 Dkxxqjsoqy Kpi, Dgon Cxgt","description":"Amah yonh pndfquoi jhbm sgk gxqarn.","created_at":"2026-11-24T10:36:39+00:00","updated_at":"2026-11-25T05:36:39+00:00","lat":18.5259,"lng":-540.6357}]},"last_status":null,"last_passed":null,"last_failed":null,"last_run_at":null,"updated_at":"2026-11-11T21:48:51+00:00"}],"tests_on_save":false,"require_passing_tests":false,"default_start":"read","condition_fields":[],"mockable":[{"node_id":"read","label":"Changed 311 requests","type":"source.read","kind":"records"}],"outputs":{"start":{"out":"out"},"read":{"out":"out"},"map":{"out":"out"},"write":{"out":"out"}}}}}},"401":{"description":"The API token is missing, wrong, expired or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token's role is below viewer, or the caller's address is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Nothing with that ID in this workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/flows/{id}/tests/run":{"post":{"operationId":"runFlowTests","tags":["Flow tests"],"summary":"Run all of a flow's tests","description":"Runs every saved test in dry mode: nothing is read from live sources unless the test says so, and nothing is written, sent or emailed. Answers with pass or fail per test and per expectation. Good for a deployment pipeline.","security":[{"apiToken":[]}],"x-minimum-role":"builder","x-auth":"token","x-rate-limit":{"requests":60,"per":"minute","counted_by":"person"},"x-write-handshake":false,"parameters":[{"name":"id","in":"path","required":true,"description":"Flow ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Totals and one result per test. `status` is `passing`, `failing` or `none`.","content":{"application/json":{"schema":{"type":"object","properties":{"total":{"type":"integer"},"passed":{"type":"integer"},"failed":{"type":"integer"},"status":{"type":"string"},"tests":{"type":"array","items":{"type":"object","properties":{"test_id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"passed":{"type":"integer"},"failed":{"type":"integer"},"duration_ms":{"type":"integer"},"results":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"label":{"type":"string"},"pass":{"type":"boolean"},"message":{"type":"string"},"diff":{"type":"object","properties":{"expected":{"type":"integer"},"actual":{"type":"integer"}}}}}},"run_id":{"type":"integer"},"flow_status":{"type":"string"},"steps":{"type":"array","items":{"type":"object","properties":{"node_id":{"type":"string"},"label":{"type":"string"},"type":{"type":"string"},"status":{"type":"string"},"records_in":{"type":"integer"},"records_out":{"type":"integer"},"outputs":{"type":"object","properties":{"out":{"type":"integer"}}},"error":{"description":"Empty (null) in the captured example."},"note":{"description":"Empty (null) in the captured example."},"would_send_count":{"type":"integer"},"would_send":{"type":"array"}}}},"output":{"type":"object","properties":{"write":{"type":"array","items":{"type":"object","properties":{"request_id":{"type":"string"},"number":{"type":"string"},"category":{"description":"Empty (null) in the captured example."},"department":{"description":"Empty (null) in the captured example."},"status":{"type":"string"},"address":{"type":"string"},"description":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"},"lat":{"description":"Empty (null) in the captured example."},"lng":{"description":"Empty (null) in the captured example."}}}}}}}}}}},"example":{"total":1,"passed":1,"failed":0,"status":"passing","tests":[{"test_id":"01m769bjpb8xkzb9gjmq4gekb5","name":"From preview","status":"passed","passed":5,"failed":0,"duration_ms":1,"results":[{"type":"count","label":"Name fields like the columns out = 5","pass":true,"message":"Got 5.","diff":{"expected":5,"actual":5}},{"type":"count","label":"Upsert into service_requests out = 5","pass":true,"message":"Got 5.","diff":{"expected":5,"actual":5}}],"run_id":15,"flow_status":"ok","steps":[{"node_id":"start","label":"Every 15 minutes","type":"trigger.schedule","status":"ok","records_in":0,"records_out":1,"outputs":{"out":1},"error":null,"note":null,"would_send_count":0,"would_send":[]},{"node_id":"read","label":"Changed 311 requests","type":"source.read","status":"ok","records_in":1,"records_out":5,"outputs":{"out":5},"error":null,"note":"5 test record(s) handed in here.","would_send_count":0,"would_send":[]}],"output":{"write":[{"request_id":"39904s","number":"GBK-82269","category":null,"department":null,"status":"wkyc","address":"890 Mhlz Dpxk, Hnfu Hsea","description":"Cgivmq sals vowt (LM-2957)","created_at":"2026-11-25T07:13:40+00:00","updated_at":"2026-11-25T07:13:40+00:00","lat":null,"lng":null},{"request_id":"0622","number":"ZHG-26141","category":"Jpmo fxhx","department":"Dowwu","status":"itznqt","address":"132 Dkxxqjsoqy Kpi, Dgon Cxgt","description":"Amah yonh pndfquoi jhbm sgk gxqarn.","created_at":"2026-11-24T10:36:39+00:00","updated_at":"2026-11-25T05:36:39+00:00","lat":18.5259,"lng":-540.6357}]}}]}}}},"401":{"description":"The API token is missing, wrong, expired or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token's role is below builder, or the caller's address is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Nothing with that ID in this workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"More than 60 calls in a minute. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/flows/{id}/tests/{test}/run":{"post":{"operationId":"runFlowTest","tags":["Flow tests"],"summary":"Run one test","description":"Runs a single saved test in dry mode.","security":[{"apiToken":[]}],"x-minimum-role":"builder","x-auth":"token","x-rate-limit":{"requests":60,"per":"minute","counted_by":"person"},"x-write-handshake":false,"parameters":[{"name":"id","in":"path","required":true,"description":"Flow ID.","schema":{"type":"string"}},{"name":"test","in":"path","required":true,"description":"Test ID from \"List a flow's tests\".","schema":{"type":"string"}}],"responses":{"200":{"description":"The result of that test.","content":{"application/json":{"schema":{"type":"object","properties":{"test_id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"passed":{"type":"integer"},"failed":{"type":"integer"},"duration_ms":{"type":"integer"},"results":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"label":{"type":"string"},"pass":{"type":"boolean"},"message":{"type":"string"},"diff":{"type":"object","properties":{"expected":{"type":"integer"},"actual":{"type":"integer"}}}}}},"run_id":{"type":"integer"},"flow_status":{"type":"string"},"steps":{"type":"array","items":{"type":"object","properties":{"node_id":{"type":"string"},"label":{"type":"string"},"type":{"type":"string"},"status":{"type":"string"},"records_in":{"type":"integer"},"records_out":{"type":"integer"},"outputs":{"type":"object","properties":{"out":{"type":"integer"}}},"error":{"description":"Empty (null) in the captured example."},"note":{"description":"Empty (null) in the captured example."},"would_send_count":{"type":"integer"},"would_send":{"type":"array"}}}},"output":{"type":"object","properties":{"write":{"type":"array","items":{"type":"object","properties":{"request_id":{"type":"string"},"number":{"type":"string"},"category":{"description":"Empty (null) in the captured example."},"department":{"description":"Empty (null) in the captured example."},"status":{"type":"string"},"address":{"type":"string"},"description":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"},"lat":{"description":"Empty (null) in the captured example."},"lng":{"description":"Empty (null) in the captured example."}}}}}}}},"example":{"test_id":"01m769bjpb8xkzb9gjmq4gekb5","name":"From preview","status":"passed","passed":5,"failed":0,"duration_ms":1,"results":[{"type":"count","label":"Name fields like the columns out = 5","pass":true,"message":"Got 5.","diff":{"expected":5,"actual":5}},{"type":"count","label":"Upsert into service_requests out = 5","pass":true,"message":"Got 5.","diff":{"expected":5,"actual":5}}],"run_id":16,"flow_status":"ok","steps":[{"node_id":"start","label":"Every 15 minutes","type":"trigger.schedule","status":"ok","records_in":0,"records_out":1,"outputs":{"out":1},"error":null,"note":null,"would_send_count":0,"would_send":[]},{"node_id":"read","label":"Changed 311 requests","type":"source.read","status":"ok","records_in":1,"records_out":5,"outputs":{"out":5},"error":null,"note":"5 test record(s) handed in here.","would_send_count":0,"would_send":[]}],"output":{"write":[{"request_id":"39904s","number":"GBK-82269","category":null,"department":null,"status":"wkyc","address":"890 Mhlz Dpxk, Hnfu Hsea","description":"Cgivmq sals vowt (LM-2957)","created_at":"2026-11-25T07:13:40+00:00","updated_at":"2026-11-25T07:13:40+00:00","lat":null,"lng":null},{"request_id":"0622","number":"ZHG-26141","category":"Jpmo fxhx","department":"Dowwu","status":"itznqt","address":"132 Dkxxqjsoqy Kpi, Dgon Cxgt","description":"Amah yonh pndfquoi jhbm sgk gxqarn.","created_at":"2026-11-24T10:36:39+00:00","updated_at":"2026-11-25T05:36:39+00:00","lat":18.5259,"lng":-540.6357}]}}}}},"401":{"description":"The API token is missing, wrong, expired or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token's role is below builder, or the caller's address is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Nothing with that ID in this workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"More than 60 calls in a minute. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/runs":{"get":{"operationId":"listRuns","tags":["Runs"],"summary":"List runs","description":"The workspace's 200 most recent runs, newest first. Filter to reach older ones. Each run has counts in, out and failed, timing and a redacted error.","security":[{"apiToken":[]}],"x-minimum-role":"viewer","x-auth":"token","x-rate-limit":null,"x-write-handshake":false,"parameters":[{"name":"status","in":"query","required":false,"description":"Only runs in this state.","schema":{"type":"string","enum":["queued","running","ok","partial","failed","waiting"]}},{"name":"flow_id","in":"query","required":false,"description":"Only runs of this flow.","schema":{"type":"string"}}],"responses":{"200":{"description":"Up to 200 runs.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"flow_id":{"type":"string"},"flow_name":{"type":"string"},"flow_version":{"type":"integer"},"trigger":{"type":"string"},"status":{"type":"string"},"replay_of":{"description":"Empty (null) in the captured example."},"replay_count":{"type":"integer"},"created_at":{"type":"string"},"started_at":{"type":"string"},"finished_at":{"type":"string"},"duration_ms":{"type":"integer"},"records_in":{"type":"integer"},"records_out":{"type":"integer"},"records_failed":{"type":"integer"},"error":{"description":"Empty (null) in the captured example."}}}}}},"example":{"data":[{"id":"01m7696pwp3b42x4zxxkqh6ntp","flow_id":"01m75v8y18hknat4d18ahm7m1a","flow_name":"New pothole requests become Scheduler visits","flow_version":1,"trigger":"manual","status":"ok","replay_of":null,"replay_count":0,"created_at":"2026-11-11T21:46:11+00:00","started_at":"2026-11-11T21:46:11+00:00","finished_at":"2026-11-11T21:46:12+00:00","duration_ms":37,"records_in":0,"records_out":0,"records_failed":0,"error":null},{"id":"01m768zpfvdv44tnbt25drnr54","flow_id":"01m75v8y18hknat4d18ahm7m1a","flow_name":"New pothole requests become Scheduler visits","flow_version":1,"trigger":"manual","status":"ok","replay_of":null,"replay_count":0,"created_at":"2026-11-11T21:42:21+00:00","started_at":"2026-11-11T21:42:22+00:00","finished_at":"2026-11-11T21:42:22+00:00","duration_ms":117,"records_in":41,"records_out":4,"records_failed":0,"error":null}]}}}},"401":{"description":"The API token is missing, wrong, expired or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token's role is below viewer, or the caller's address is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/runs/{id}":{"get":{"operationId":"getRun","tags":["Runs"],"summary":"Get a run","description":"One run step by step: counts, the record IDs that moved (`source` and, once written, `target`), and failures with their record ID and error. Record contents are not kept, so they are not here.","security":[{"apiToken":[]}],"x-minimum-role":"viewer","x-auth":"token","x-rate-limit":null,"x-write-handshake":false,"parameters":[{"name":"id","in":"path","required":true,"description":"Run ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"The run with its steps.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"flow_id":{"type":"string"},"flow_name":{"type":"string"},"flow_version":{"type":"integer"},"trigger":{"type":"string"},"status":{"type":"string"},"replay_of":{"description":"Empty (null) in the captured example."},"replay_count":{"type":"integer"},"created_at":{"type":"string"},"started_at":{"type":"string"},"finished_at":{"type":"string"},"duration_ms":{"type":"integer"},"records_in":{"type":"integer"},"records_out":{"type":"integer"},"records_failed":{"type":"integer"},"error":{"description":"Empty (null) in the captured example."},"steps":{"type":"array","items":{"type":"object","properties":{"node_id":{"type":"string"},"type":{"type":"string"},"label":{"type":"string"},"status":{"type":"string"},"records_in":{"type":"integer"},"records_out":{"type":"integer"},"records_failed":{"type":"integer"},"http_calls":{"type":"integer"},"duration_ms":{"type":"integer"},"record_ids":{"type":"array"},"failures":{"type":"array"},"error":{"description":"Empty (null) in the captured example."},"totals":{"description":"Empty (null) in the captured example."}}}},"replayable_ids":{"type":"array"}}}}},"example":{"data":{"id":"01m768zpfvdv44tnbt25drnr54","flow_id":"01m75v8y18hknat4d18ahm7m1a","flow_name":"New pothole requests become Scheduler visits","flow_version":1,"trigger":"manual","status":"ok","replay_of":null,"replay_count":0,"created_at":"2026-11-11T21:42:21+00:00","started_at":"2026-11-11T21:42:22+00:00","finished_at":"2026-11-11T21:42:22+00:00","duration_ms":117,"records_in":41,"records_out":4,"records_failed":0,"error":null,"steps":[{"node_id":"start","type":"trigger.schedule","label":"Every 15 minutes","status":"ok","records_in":0,"records_out":1,"records_failed":0,"http_calls":0,"duration_ms":0,"record_ids":[],"failures":[],"error":null,"totals":null},{"node_id":"read","type":"source.read","label":"Changed 311 requests","status":"ok","records_in":1,"records_out":41,"records_failed":0,"http_calls":1,"duration_ms":32,"record_ids":[{"source":"10411g"},{"source":"5040"}],"failures":[],"error":null,"totals":null}],"replayable_ids":[]}}}}},"401":{"description":"The API token is missing, wrong, expired or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token's role is below viewer, or the caller's address is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Nothing with that ID in this workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/runs/{id}/replay":{"post":{"operationId":"replayRun","tags":["Runs"],"summary":"Replay failed records","description":"Reads the failed records again from their source, by ID, and sends them through the flow as a new run. With no body, every failed record of the run is replayed.","security":[{"apiToken":[]}],"x-minimum-role":"builder","x-auth":"token","x-rate-limit":null,"x-write-handshake":false,"parameters":[{"name":"id","in":"path","required":true,"description":"Run ID.","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"ids":{"type":"array","items":{"type":"string"},"description":"Record IDs to replay (up to 500). Default: every failed record of the run."}}},"example":{"ids":["REQ-1001","REQ-1002"]}}}},"responses":{"202":{"description":"The new run.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"flow_id":{"type":"string"},"flow_name":{"type":"string"},"flow_version":{"type":"integer"},"trigger":{"type":"string"},"status":{"description":"Empty (null) in the captured example."},"replay_of":{"type":"string"},"replay_count":{"type":"integer"},"created_at":{"type":"string"},"started_at":{"description":"Empty (null) in the captured example."},"finished_at":{"description":"Empty (null) in the captured example."},"duration_ms":{"description":"Empty (null) in the captured example."},"records_in":{"description":"Empty (null) in the captured example."},"records_out":{"description":"Empty (null) in the captured example."},"records_failed":{"description":"Empty (null) in the captured example."},"error":{"description":"Empty (null) in the captured example."}}}}},"example":{"data":{"id":"01m769bjmpvfwac9ksw2arqp8w","flow_id":"01m769behch8x1ym13ssqct3xv","flow_name":"Data quality alert (sub-flow)","flow_version":1,"trigger":"replay","status":null,"replay_of":"01m769ben0mnkzwcdmyjy6aqsv","replay_count":1,"created_at":"2026-11-11T21:48:51+00:00","started_at":null,"finished_at":null,"duration_ms":null,"records_in":null,"records_out":null,"records_failed":null,"error":null}}}}},"401":{"description":"The API token is missing, wrong, expired or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token's role is below builder, or the caller's address is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Nothing with that ID in this workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The run has no failed records with IDs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/trace":{"get":{"operationId":"traceRecord","tags":["Runs"],"summary":"Trace a record","description":"Follows one record ID (a permit number, a request ID) through every run that read, sent or failed it, and shows what it became in the next system. Up to 200 hops, newest first.","security":[{"apiToken":[]}],"x-minimum-role":"viewer","x-auth":"token","x-rate-limit":{"requests":60,"per":"minute","counted_by":"person"},"x-write-handshake":false,"parameters":[{"name":"id","in":"query","required":true,"description":"The record's ID in any connected system.","schema":{"type":"string"}}],"responses":{"200":{"description":"The hops, and how many flows and runs were involved.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"hops":{"type":"array","items":{"type":"object","properties":{"what":{"type":"string"},"source":{"type":"string"},"target":{"description":"Empty (null) in the captured example."},"error":{"description":"Empty (null) in the captured example."},"step":{"type":"string"},"type":{"type":"string"},"run_id":{"type":"string"},"run_status":{"type":"string"},"trigger":{"type":"string"},"flow_id":{"type":"string"},"flow_name":{"type":"string"},"at":{"type":"string"}}}},"flows":{"type":"integer"},"runs":{"type":"integer"}}},"example":{"id":"10411g","hops":[{"what":"read","source":"10411g","target":null,"error":null,"step":"Changed 311 requests","type":"source.read","run_id":"01m768zpfvdv44tnbt25drnr54","run_status":"ok","trigger":"manual","flow_id":"01m75v8y18hknat4d18ahm7m1a","flow_name":"New pothole requests become Scheduler visits","at":"2026-11-11T21:42:21+00:00"},{"what":"read","source":"10411g","target":null,"error":null,"step":"Changed 311 requests","type":"source.read","run_id":"01m75twwnd39jj708g6g25r6sf","run_status":"ok","trigger":"manual","flow_id":"01m75tw3hvy8n0vc7naawnazrf","flow_name":"New pothole requests become Scheduler visits","at":"2026-11-11T17:36:09+00:00"}],"flows":7,"runs":11}}}},"401":{"description":"The API token is missing, wrong, expired or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token's role is below viewer, or the caller's address is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"More than 60 calls in a minute. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/monitoring":{"get":{"operationId":"getMonitoring","tags":["Monitoring"],"summary":"Workspace health","description":"Runs per day, totals and success rate, results per flow, flows failing right now, the slowest steps and connections that need attention.","security":[{"apiToken":[]}],"x-minimum-role":"viewer","x-auth":"token","x-rate-limit":null,"x-write-handshake":false,"parameters":[{"name":"days","in":"query","required":false,"description":"How many days to look back. Default 14.","schema":{"type":"integer","enum":[7,14,30,90]}}],"responses":{"200":{"description":"Health over the chosen days.","content":{"application/json":{"schema":{"type":"object","properties":{"days":{"type":"integer"},"timezone":{"type":"string"},"daily":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string"},"ok":{"type":"integer"},"partial":{"type":"integer"},"failed":{"type":"integer"},"records":{"type":"integer"}}}},"totals":{"type":"object","properties":{"runs":{"type":"integer"},"ok":{"type":"integer"},"partial":{"type":"integer"},"failed":{"type":"integer"},"success_rate":{"type":"integer"},"records":{"type":"integer"},"records_failed":{"type":"integer"},"avg_ms":{"type":"integer"},"p95_ms":{"type":"integer"},"running":{"type":"integer"}}},"flows":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"enabled":{"type":"boolean"},"runs":{"type":"integer"},"ok":{"type":"integer"},"partial":{"type":"integer"},"failed":{"type":"integer"},"success_rate":{"type":"integer"},"avg_ms":{"type":"integer"},"records":{"type":"integer"},"last_status":{"type":"string"},"last_run_at":{"type":"string"},"last_failure_at":{"description":"Empty (null) in the captured example."},"last_error":{"description":"Empty (null) in the captured example."}}}},"failing_now":{"type":"array"},"slow_steps":{"type":"array","items":{"type":"object","properties":{"flow_id":{"type":"string"},"flow_name":{"type":"string"},"step":{"type":"string"},"type":{"type":"string"},"runs":{"type":"integer"},"avg_ms":{"type":"integer"},"max_ms":{"type":"integer"},"calls":{"type":"integer"}}}},"connections":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"issue":{"type":"string"},"error":{"description":"Empty (null) in the captured example."},"tested_at":{"description":"Empty (null) in the captured example."}}}}}},"example":{"days":7,"timezone":"America/Los_Angeles","daily":[{"date":"2026-11-05","ok":0,"partial":0,"failed":0,"records":0},{"date":"2026-11-06","ok":0,"partial":0,"failed":0,"records":0}],"totals":{"runs":51,"ok":51,"partial":0,"failed":0,"success_rate":100,"records":667,"records_failed":0,"avg_ms":147,"p95_ms":454,"running":0},"flows":[{"id":"01m74hqe3jjn4tpavrv5j4hp84","name":"New pothole requests become Scheduler visits","enabled":false,"runs":11,"ok":11,"partial":0,"failed":0,"success_rate":100,"avg_ms":55,"records":20,"last_status":"ok","last_run_at":"2026-11-11T17:33:34+00:00","last_failure_at":null,"last_error":null},{"id":"01m75ma90qyvtfemtpz47d802s","name":"Open data: building permits API","enabled":true,"runs":8,"ok":8,"partial":0,"failed":0,"success_rate":100,"avg_ms":32,"records":224,"last_status":"ok","last_run_at":"2026-11-11T17:50:59+00:00","last_failure_at":null,"last_error":null}],"failing_now":[],"slow_steps":[{"flow_id":"01m75ktv3qm550vqn6qjwpcmvg","flow_name":"EnerGov issued permits to ArcGIS","step":"Add to the map layer","type":"destination.write","runs":1,"avg_ms":833,"max_ms":833,"calls":9},{"flow_id":"01m75ktt4de0ghzf72k83sb3c9","flow_name":"Meter reads from AMI to utility billing with zero-read check","step":"Load the read","type":"destination.write","runs":1,"avg_ms":466,"max_ms":466,"calls":20}],"connections":[{"id":"01m74hqe33cy19f80c393jnpz7","name":"Sentra 311 (sandbox)","issue":"Never tested","error":null,"tested_at":null},{"id":"01m74hqe33cy19f80c393jnpz8","name":"Sentra Scheduler (sandbox)","issue":"Never tested","error":null,"tested_at":null}]}}}},"401":{"description":"The API token is missing, wrong, expired or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token's role is below viewer, or the caller's address is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/health/issues":{"get":{"operationId":"listHealthIssues","tags":["Monitoring"],"summary":"Data-health issues","description":"Open data-health issues (no records, volume change, run late, stale data, failure rate) and the last 10 that cleared.","security":[{"apiToken":[]}],"x-minimum-role":"viewer","x-auth":"token","x-rate-limit":null,"x-write-handshake":false,"responses":{"200":{"description":"Active and recently cleared issues.","content":{"application/json":{"schema":{"type":"object","properties":{"active":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"flow_id":{"type":"string"},"flow":{"type":"string"},"rule":{"type":"string"},"title":{"type":"string"},"message":{"type":"string"},"status":{"type":"string"},"started_at":{"type":"string"},"cleared_at":{"description":"Empty (null) in the captured example."},"sent":{"type":"array"}}}},"recent":{"type":"array"}}},"example":{"active":[{"id":2,"flow_id":"01m74hqe3jjn4tpavrv5j4hp84","flow":"New pothole requests become Scheduler visits","rule":"no_records","title":"No records read","message":"The last run finished but read no records. Check the source system and the flow's filters.","status":"active","started_at":"2026-11-11T17:18:10+00:00","cleared_at":null,"sent":[]},{"id":1,"flow_id":"01m74hqe3jjn4tpavrv5j4hp84","flow":"New pothole requests become Scheduler visits","rule":"volume","title":"Volume changed","message":"The last run read 0 records, 100% below the average of 8.2 over the last 5 runs (limit 20%).","status":"active","started_at":"2026-11-11T15:34:00+00:00","cleared_at":null,"sent":[]}],"recent":[]}}}},"401":{"description":"The API token is missing, wrong, expired or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token's role is below viewer, or the caller's address is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/connections":{"get":{"operationId":"listConnections","tags":["Connections"],"summary":"List connections","description":"Connected systems: connector, address, how they sign in, the result of the last test and which flows use them. `has_credentials` and `credential_fields` say what is saved; the credentials themselves are never returned.","security":[{"apiToken":[]}],"x-minimum-role":"viewer","x-auth":"token","x-rate-limit":null,"x-write-handshake":false,"responses":{"200":{"description":"All connections.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"connector":{"type":"string"},"connector_name":{"type":"string"},"kind":{"type":"string"},"base_url":{"type":"string"},"auth_type":{"type":"string"},"auth_config":{"type":"object"},"default_headers":{"type":"object","properties":{"x-accela-agency":{"type":"string"},"x-accela-environment":{"type":"string"}}},"has_credentials":{"type":"boolean"},"credential_fields":{"type":"array"},"status":{"type":"string"},"last_tested_at":{"description":"Empty (null) in the captured example."},"last_error":{"description":"Empty (null) in the captured example."},"sandbox":{"type":"boolean"},"is_database":{"type":"boolean"},"department":{"description":"Empty (null) in the captured example."},"is_mcp":{"type":"boolean"},"is_files":{"type":"boolean"},"relay_id":{"description":"Empty (null) in the captured example."},"used_by":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}}}}}}}}},"example":{"data":[{"id":"01m74k6h1paacjet4mez9974j8","name":"Accela Civic Platform","connector":"accela","connector_name":"Accela Civic Platform","kind":"vendor","base_url":"https://system.example.gov/v4","auth_type":"oauth2_client","auth_config":[],"default_headers":{"x-accela-agency":"","x-accela-environment":"PROD"},"has_credentials":false,"credential_fields":[],"status":"untested","last_tested_at":null,"last_error":null,"sandbox":false,"is_database":false,"department":null,"is_mcp":false,"is_files":false,"relay_id":null,"used_by":[{"id":"01m74k6h1qjx1g0d9wc838410v","name":"311 code-enforcement requests become Accela records"},{"id":"01m74q1bcnfc4vf0dr9rd1mn6q","name":"Closed Accela records close their 311 requests"}]},{"id":"01m74qmz62e9qbpv00yse14w5d","name":"Backup bucket (sandbox)","connector":"s3","connector_name":"Amazon S3 / S3-compatible","kind":"file","base_url":"s3://city-backups","auth_type":"s3","auth_config":{"endpoint":"https://system.example.gov","region":"us-east-1","bucket":"city-backups","access_key":"EXAMPLE"},"default_headers":[],"has_credentials":true,"credential_fields":["secret_key"],"status":"ok","last_tested_at":"2026-11-11T17:50:59+00:00","last_error":null,"sandbox":true,"is_database":false,"department":null,"is_mcp":false,"is_files":true,"relay_id":null,"used_by":[{"id":"01m74qnpe5hs1vtafrrec21j62","name":"Nightly backup: Sentra 311 requests to S3"},{"id":"01m74qnwnyf7n1ns4xnwnjsq58","name":"Nightly backup: a database table to CSV in S3"}]}]}}}},"401":{"description":"The API token is missing, wrong, expired or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token's role is below viewer, or the caller's address is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/connections/{id}/test":{"post":{"operationId":"testConnection","tags":["Connections"],"summary":"Test a connection","description":"Signs in to the system and reads something small. The answer is always 200: look at `ok`. The result is saved on the connection.","security":[{"apiToken":[]}],"x-minimum-role":"admin","x-auth":"token","x-rate-limit":{"requests":30,"per":"minute","counted_by":"person"},"x-write-handshake":false,"parameters":[{"name":"id","in":"path","required":true,"description":"Connection ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Whether it worked, a plain message, how long it took and the connection.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"message":{"type":"string"},"ms":{"type":"integer"},"data":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"connector":{"type":"string"},"connector_name":{"type":"string"},"kind":{"type":"string"},"base_url":{"type":"string"},"auth_type":{"type":"string"},"auth_config":{"type":"object","properties":{"endpoint":{"type":"string"},"region":{"type":"string"},"bucket":{"type":"string"},"access_key":{"type":"string"}}},"default_headers":{"type":"object"},"has_credentials":{"type":"boolean"},"credential_fields":{"type":"array","items":{"type":"string"}},"status":{"type":"string"},"last_tested_at":{"type":"string"},"last_error":{"description":"Empty (null) in the captured example."},"sandbox":{"type":"boolean"},"is_database":{"type":"boolean"},"department":{"description":"Empty (null) in the captured example."},"is_mcp":{"type":"boolean"},"is_files":{"type":"boolean"},"relay_id":{"description":"Empty (null) in the captured example."}}}}},"example":{"ok":true,"message":"Signed in to bucket city-backups. 0 files at the top of the bucket.","ms":9,"data":{"id":"01m74qmz62e9qbpv00yse14w5d","name":"Backup bucket (sandbox)","connector":"s3","connector_name":"Amazon S3 / S3-compatible","kind":"file","base_url":"s3://city-backups","auth_type":"s3","auth_config":{"endpoint":"https://system.example.gov","region":"us-east-1","bucket":"city-backups","access_key":"EXAMPLE"},"default_headers":[],"has_credentials":true,"credential_fields":["secret_key"],"status":"ok","last_tested_at":"2026-11-11T21:48:51+00:00","last_error":null,"sandbox":true,"is_database":false,"department":null,"is_mcp":false,"is_files":true,"relay_id":null}}}}},"401":{"description":"The API token is missing, wrong, expired or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token's role is below admin, or the caller's address is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Nothing with that ID in this workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"More than 30 calls in a minute. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/approvals":{"get":{"operationId":"listApprovals","tags":["Approvals"],"summary":"List approvals","description":"The 200 most recent approval requests, newest first, and how many are waiting. Requests hold record IDs, not record contents.","security":[{"apiToken":[]}],"x-minimum-role":"viewer","x-auth":"token","x-rate-limit":null,"x-write-handshake":false,"parameters":[{"name":"status","in":"query","required":false,"description":"Send `pending` for only the requests still waiting.","schema":{"type":"string","enum":["pending"]}}],"responses":{"200":{"description":"Approval requests and the pending count.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"status":{"type":"string"},"flow_id":{"type":"string"},"flow_name":{"type":"string"},"step":{"type":"string"},"run_id":{"type":"string"},"continued_run_id":{"type":"string"},"count":{"type":"integer"},"record_ids":{"type":"array","items":{"type":"string"}},"approved_ids":{"type":"array","items":{"type":"string"}},"approvers":{"type":"array","items":{"type":"string"}},"decided_by":{"type":"string"},"decided_at":{"type":"string"},"reason":{"description":"Empty (null) in the captured example."},"expires_at":{"type":"string"},"created_at":{"type":"string"},"can_decide":{"type":"boolean"}}}},"pending":{"type":"integer"}}},"example":{"data":[{"id":"01m74qgpw7kc7jjs998pfjavjs","title":"Publish 2 agendas to the website","status":"approved","flow_id":"01m74qgp2wzbenx034zhvc6ye0","flow_name":"Publish agendas on the website after the clerk approves","step":"Clerk approves","run_id":"01m74qgp6xcjma40d75fd8gqe6","continued_run_id":"01m74qgvmt0eaj5r8x0t8rv4zz","count":2,"record_ids":["45","43"],"approved_ids":["45","43"],"approvers":["clerk@demo.example.gov"],"decided_by":"SentraBridge admin","decided_at":"2026-11-11T07:17:55+00:00","reason":null,"expires_at":"2026-11-13T07:17:50+00:00","created_at":"2026-11-11T07:17:50+00:00","can_decide":false}],"pending":0}}}},"401":{"description":"The API token is missing, wrong, expired or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token's role is below viewer, or the caller's address is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/approvals/{id}":{"get":{"operationId":"getApproval","tags":["Approvals"],"summary":"Get an approval","description":"One approval request. `can_decide` says whether this token's person may decide it.","security":[{"apiToken":[]}],"x-minimum-role":"viewer","x-auth":"token","x-rate-limit":null,"x-write-handshake":false,"parameters":[{"name":"id","in":"path","required":true,"description":"Approval ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"The approval request.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"status":{"type":"string"},"flow_id":{"type":"string"},"flow_name":{"type":"string"},"step":{"type":"string"},"run_id":{"type":"string"},"continued_run_id":{"type":"string"},"count":{"type":"integer"},"record_ids":{"type":"array","items":{"type":"string"}},"approved_ids":{"type":"array","items":{"type":"string"}},"approvers":{"type":"array","items":{"type":"string"}},"decided_by":{"type":"string"},"decided_at":{"type":"string"},"reason":{"description":"Empty (null) in the captured example."},"expires_at":{"type":"string"},"created_at":{"type":"string"},"can_decide":{"type":"boolean"}}}}},"example":{"data":{"id":"01m74qgpw7kc7jjs998pfjavjs","title":"Publish 2 agendas to the website","status":"approved","flow_id":"01m74qgp2wzbenx034zhvc6ye0","flow_name":"Publish agendas on the website after the clerk approves","step":"Clerk approves","run_id":"01m74qgp6xcjma40d75fd8gqe6","continued_run_id":"01m74qgvmt0eaj5r8x0t8rv4zz","count":2,"record_ids":["45","43"],"approved_ids":["45","43"],"approvers":["clerk@demo.example.gov"],"decided_by":"SentraBridge admin","decided_at":"2026-11-11T07:17:55+00:00","reason":null,"expires_at":"2026-11-13T07:17:50+00:00","created_at":"2026-11-11T07:17:50+00:00","can_decide":false}}}}},"401":{"description":"The API token is missing, wrong, expired or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token's role is below viewer, or the caller's address is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Nothing with that ID in this workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/approvals/{id}/decide":{"post":{"operationId":"decideApproval","tags":["Approvals"],"summary":"Approve or reject","description":"Decides a waiting request. Any role can call it, but only the people named as approvers, or an admin, are allowed to decide. Approving continues the flow for the approved records as a new run (`continued_run_id`).","security":[{"apiToken":[]}],"x-minimum-role":"viewer","x-auth":"token","x-rate-limit":null,"x-write-handshake":false,"parameters":[{"name":"id","in":"path","required":true,"description":"Approval ID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"decision":{"type":"string","description":"What to do.","enum":["approve","reject"]},"ids":{"type":"array","items":{"type":"string"},"description":"Approve only these record IDs. Default: all of them."},"reason":{"type":"string","description":"Optional note, up to 500 characters."}},"required":["decision"]},"example":{"decision":"approve","reason":"Checked against the agenda packet."}}}},"responses":{"200":{"description":"The request after the decision.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"status":{"type":"string"},"flow_id":{"type":"string"},"flow_name":{"type":"string"},"step":{"type":"string"},"run_id":{"type":"string"},"continued_run_id":{"type":"string"},"count":{"type":"integer"},"record_ids":{"type":"array","items":{"type":"string"}},"approved_ids":{"type":"array","items":{"type":"string"}},"approvers":{"type":"array","items":{"type":"string"}},"decided_by":{"type":"string"},"decided_at":{"type":"string"},"reason":{"description":"Empty (null) in the captured example."},"expires_at":{"type":"string"},"created_at":{"type":"string"},"can_decide":{"type":"boolean"}}}}},"example":{"data":{"id":"01m74qgpw7kc7jjs998pfjavjs","title":"Publish 2 agendas to the website","status":"approved","flow_id":"01m74qgp2wzbenx034zhvc6ye0","flow_name":"Publish agendas on the website after the clerk approves","step":"Clerk approves","run_id":"01m74qgp6xcjma40d75fd8gqe6","continued_run_id":"01m74qgvmt0eaj5r8x0t8rv4zz","count":2,"record_ids":["45","43"],"approved_ids":["45","43"],"approvers":["clerk@demo.example.gov"],"decided_by":"SentraBridge admin","decided_at":"2026-11-11T07:17:55+00:00","reason":null,"expires_at":"2026-11-13T07:17:50+00:00","created_at":"2026-11-11T07:17:50+00:00","can_decide":false}}}}},"401":{"description":"The API token is missing, wrong, expired or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"This person is not an approver or an admin.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Nothing with that ID in this workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request was already approved, rejected or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/templates":{"get":{"operationId":"listTemplates","tags":["Templates"],"summary":"List templates","description":"Ready-made flows: built-in ones, and ones SentraBridge staff shared (their `key` starts with `shared:`).","security":[{"apiToken":[]}],"x-minimum-role":"viewer","x-auth":"token","x-rate-limit":null,"x-write-handshake":false,"responses":{"200":{"description":"All templates.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"category":{"type":"string"},"connectors":{"type":"array","items":{"type":"string"}},"trigger":{"type":"string"},"steps":{"type":"integer"}}}}}},"example":{"data":[{"key":"311-potholes-to-scheduler","name":"New pothole requests become Scheduler visits","description":"Every 15 minutes, takes new or changed pothole requests from Sentra 311 and creates a visit for the street repair crew in Sentra Scheduler.","category":"Work orders","connectors":["sentra311","sentrascheduler"],"trigger":"trigger.schedule","steps":5},{"key":"311-to-accela-code-cases","name":"311 code-enforcement requests become Accela records","description":"When Sentra 311 sends request.created, code-enforcement requests become new Accela records. Everything else is ignored.","category":"Permitting","connectors":["sentra311","accela"],"trigger":"trigger.webhook","steps":4}]}}}},"401":{"description":"The API token is missing, wrong, expired or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token's role is below viewer, or the caller's address is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/templates/{key}/use":{"post":{"operationId":"useTemplate","tags":["Templates"],"summary":"Make a flow from a template","description":"Makes a new flow, turned off, from a built-in template. It uses this workspace's connection for each connector the template needs, and makes an empty or sandbox connection where there is none. Shared templates (`shared:` keys) are not available here.","security":[{"apiToken":[]}],"x-minimum-role":"builder","x-auth":"token","x-rate-limit":null,"x-write-handshake":false,"parameters":[{"name":"key","in":"path","required":true,"description":"Template key from \"List templates\", for example `311-potholes-to-scheduler`.","schema":{"type":"string"}}],"responses":{"201":{"description":"The new flow.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"enabled":{"description":"Empty (null) in the captured example."},"trigger":{"type":"string"},"schedule":{"description":"Empty (null) in the captured example."},"steps":{"type":"integer"},"connectors":{"type":"array"},"next_run_at":{"description":"Empty (null) in the captured example."},"last_run_at":{"description":"Empty (null) in the captured example."},"last_status":{"description":"Empty (null) in the captured example."},"version":{"type":"integer"},"template":{"type":"string"},"updated_at":{"type":"string"},"last_success_at":{"description":"Empty (null) in the captured example."},"department":{"description":"Empty (null) in the captured example."},"graph":{"type":"object","properties":{"nodes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"position":{"type":"object","properties":{"x":{"type":"integer"},"y":{"type":"integer"}}},"data":{"type":"object","properties":{"label":{"type":"string"},"config":{"type":"array"},"note":{"type":"string"}}}}}},"edges":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"source":{"type":"string"},"target":{"type":"string"},"sourceHandle":{"description":"Empty (null) in the captured example."}}}}}},"webhook_url":{"description":"Empty (null) in the captured example."},"problems":{"type":"array"},"secrets_saved":{"type":"array"},"public_status":{"type":"boolean"},"notify":{"type":"object","properties":{"on":{"type":"array"},"emails":{"type":"array"}}}}}}},"example":{"data":{"id":"01m769behch8x1ym13ssqct3xv","name":"Data quality alert (sub-flow)","description":"A reusable sub-flow: other flows hand it the records that failed their checks, and it emails the data team a summary with a CSV attached.","enabled":null,"trigger":"trigger.manual","schedule":null,"steps":3,"connectors":[],"next_run_at":null,"last_run_at":null,"last_status":null,"version":1,"template":"data-quality-alert","updated_at":"2026-11-11T21:48:47+00:00","last_success_at":null,"department":null,"graph":{"nodes":[{"id":"t","type":"trigger.manual","position":{"x":40,"y":160},"data":{"label":"Records from another flow","config":[],"note":""}},{"id":"lim","type":"transform.limit","position":{"x":330,"y":160},"data":{"label":"First 500","config":{"count":500},"note":""}}],"edges":[{"id":"t-lim-out","source":"t","target":"lim","sourceHandle":null},{"id":"lim-mail-out","source":"lim","target":"mail","sourceHandle":null}]},"webhook_url":null,"problems":[],"secrets_saved":[],"public_status":false,"notify":{"on":[],"emails":[]}}}}}},"401":{"description":"The API token is missing, wrong, expired or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token's role is below builder, or the caller's address is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No built-in template has that key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/activity/export":{"get":{"operationId":"exportActivity","tags":["Audit and usage"],"summary":"Export the activity log","description":"Streams the workspace activity log (who did what, when, with tamper-check hashes) or the AI and MCP call log, as CSV or NDJSON. The whole range is sent in one answer. Each export is itself written to the log.","security":[{"apiToken":[]}],"x-minimum-role":"admin","x-auth":"token","x-rate-limit":{"requests":30,"per":"minute","counted_by":"person"},"x-write-handshake":false,"parameters":[{"name":"format","in":"query","required":false,"description":"File format. Default csv.","schema":{"type":"string","enum":["csv","ndjson"]}},{"name":"scope","in":"query","required":false,"description":"`activity` (default) or `calls` for the AI and MCP call log.","schema":{"type":"string","enum":["activity","calls"]}},{"name":"from","in":"query","required":false,"description":"First day, YYYY-MM-DD, in the workspace time zone.","schema":{"type":"string"}},{"name":"to","in":"query","required":false,"description":"Last day, YYYY-MM-DD, in the workspace time zone.","schema":{"type":"string"}},{"name":"action","in":"query","required":false,"description":"Only this action, or a prefix such as `flow.` (scope activity).","schema":{"type":"string"}},{"name":"kind","in":"query","required":false,"description":"Only AI or only MCP calls (scope calls).","schema":{"type":"string","enum":["ai","mcp"]}}],"responses":{"200":{"description":"The log as a file.","content":{"text/csv":{"schema":{"type":"string"},"example":"id,at,workspace_id,workspace,action,summary,who,email,subject_type,subject_id,ip,meta,prev_hash,hash\n1,2026-11-11T06:20:05+00:00,01m74hqe1m08znmjtembcq6sf7,\"City of Demo\",user.signed_in,person1@example.gov,\"SentraBridge admin\",person1@example.gov,User,1,203.0.113.10,,000000000000…,ee41d37ccf9c…\n2,2026-11-11T06:24:09+00:00,01m74hqe1m08znmjtembcq6sf7,\"City of Demo\",user.signed_in,person1@example.gov,\"Pedro Braz\",person1@example.gov,User,2,203.0.113.10,,ee41d37ccf9c…,8fa3f5496b4b…\n…"}}},"401":{"description":"The API token is missing, wrong, expired or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token's role is below admin, or the caller's address is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"More than 30 calls in a minute. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/usage":{"get":{"operationId":"getUsage","tags":["Audit and usage"],"summary":"Usage this month","description":"This month's meters (runs, records read and written, active flows, connections, Data API, AI and MCP calls) against the plan, and the last 12 months. Plan amounts are null when the plan is not shown to the client.","security":[{"apiToken":[]}],"x-minimum-role":"admin","x-auth":"token","x-rate-limit":null,"x-write-handshake":false,"responses":{"200":{"description":"Meters, history and plan.","content":{"application/json":{"schema":{"type":"object","properties":{"month":{"type":"string"},"meters":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"unit":{"type":"string"},"used":{"type":"integer"},"included":{"type":"integer"},"percent":{"type":"integer"},"hard":{"type":"boolean"}}}},"labels":{"type":"object","properties":{"runs":{"type":"string"},"records_read":{"type":"string"},"records_written":{"type":"string"},"active_flows":{"type":"string"},"connections":{"type":"string"},"relays":{"type":"string"},"data_api_calls":{"type":"string"},"ai_calls":{"type":"string"},"mcp_calls":{"type":"string"}}},"history":{"type":"array","items":{"type":"object","properties":{"month":{"type":"string"},"runs":{"type":"integer"},"records_read":{"type":"integer"},"records_written":{"type":"integer"},"active_flows":{"type":"integer"},"connections":{"type":"integer"},"relays":{"type":"integer"},"data_api_calls":{"type":"integer"},"ai_calls":{"type":"integer"},"mcp_calls":{"type":"integer"}}}},"plan":{"type":"object","properties":{"name":{"type":"string"},"cycle":{"type":"string"},"starts_on":{"type":"string"},"visible":{"type":"boolean"}}},"estimate":{"type":"object","properties":{"lines":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"label":{"type":"string"},"quantity":{"type":"integer"},"unit_cents":{"type":"integer"},"amount_cents":{"type":"integer"}}}},"total_cents":{"type":"integer"}}},"banner":{"type":"array"},"updated_at":{"type":"string"}}},"example":{"month":"2026-11","meters":[{"key":"runs","label":"Runs","unit":"runs","used":63,"included":2000,"percent":3,"hard":false},{"key":"records_read","label":"Records read","unit":"records","used":1293,"included":null,"percent":null,"hard":false}],"labels":{"runs":"Runs","records_read":"Records read","records_written":"Records written","active_flows":"Active flows","connections":"Connections","relays":"Relays","data_api_calls":"Data API calls","ai_calls":"AI calls","mcp_calls":"MCP calls"},"history":[{"month":"2025-12","runs":0,"records_read":0,"records_written":0,"active_flows":0,"connections":0,"relays":0,"data_api_calls":0,"ai_calls":0,"mcp_calls":0},{"month":"2026-01","runs":0,"records_read":0,"records_written":0,"active_flows":0,"connections":0,"relays":0,"data_api_calls":0,"ai_calls":0,"mcp_calls":0}],"plan":{"name":"Demo city (draft)","cycle":"monthly","starts_on":"2026-11-01","visible":true},"estimate":{"lines":[{"kind":"base","label":"Demo city (draft) plan, monthly","quantity":1,"unit_cents":150000,"amount_cents":150000}],"total_cents":150000},"banner":[],"updated_at":"2026-11-11T17:59:48+00:00"}}}},"401":{"description":"The API token is missing, wrong, expired or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token's role is below admin, or the caller's address is not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/data/{workspace}/{flow}":{"get":{"operationId":"getData","tags":["Data API"],"summary":"Read a published data API","description":"Runs a read-only flow that starts with \"When someone asks for data\" and answers with its records. The records are read live for this call and are not stored (a flow can turn on a short encrypted cache). Flows marked public need no key; others need a data key (`sbd_…`) made on the flow, sent as a bearer token or in `X-API-Key`. The flow's own query parameters are listed in its OpenAPI document.","security":[[],{"dataKey":[]},{"dataKeyHeader":[]}],"x-minimum-role":null,"x-auth":"data_key","x-rate-limit":{"requests":600,"per":"minute","counted_by":"calling address"},"x-write-handshake":false,"parameters":[{"name":"workspace","in":"path","required":true,"description":"The workspace's short name, for example `city-of-demo`.","schema":{"type":"string"}},{"name":"flow","in":"path","required":true,"description":"The address name set on the flow, for example `permits`.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Records per page. The flow sets the default (100) and the most allowed (up to 5,000).","schema":{"type":"integer"}},{"name":"offset","in":"query","required":false,"description":"Records to skip. Use `meta.next_offset` or follow `links.next`.","schema":{"type":"integer"}}],"responses":{"200":{"description":"A page of records, with `meta` and `links.next`. Headers: X-Total-Count, X-Run-Id, X-Cache.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"permit_number":{"type":"string"},"permit_type":{"type":"string"},"status":{"type":"string"},"address":{"type":"string"},"applicant":{"type":"string"},"issued_on":{"type":"string"},"valuation":{"type":"integer"},"updated_at":{"type":"string"},"geom":{"type":"object","properties":{"type":{"type":"string"},"coordinates":{"type":"array","items":{"type":"number"}}}}}}},"meta":{"type":"object","properties":{"total":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"},"next_offset":{"type":"integer"}}},"links":{"type":"object","properties":{"next":{"type":"string"}}}}},"example":{"data":[{"id":1,"permit_number":"BLD-2026-0001","permit_type":"New single-family","status":"issued","address":"137 Oak Ave, Demo City","applicant":"B. Chen","issued_on":"2026-11-08","valuation":6375,"updated_at":"2026-11-11 05:07:15.494113+00","geom":{"type":"Point","coordinates":[-119.7991,36.7298]}},{"id":2,"permit_number":"BLD-2026-0002","permit_type":"Commercial tenant improvement","status":"in review","address":"174 Elm St, Demo City","applicant":"C. Okafor","issued_on":"2026-11-05","valuation":7750,"updated_at":"2026-11-11 04:07:15.494113+00","geom":{"type":"Point","coordinates":[-119.7951,36.7338]}}],"meta":{"total":30,"limit":2,"offset":0,"next_offset":2},"links":{"next":"https://app.sentrabridge.example/public/data/city-of-demo/permits?limit=2&offset=2"}}}}},"401":{"description":"A key is needed, or the key is not valid for this data API.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The data API is turned off, or its address is used by more than one flow.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Over the flow's limit per caller (60 a minute unless the flow says otherwise). See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"The source could not be read. The message carries a run ID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/data/{workspace}/{flow}/openapi.json":{"get":{"operationId":"getDataOpenapi","tags":["Data API"],"summary":"OpenAPI for one data API","description":"An OpenAPI 3 document for one published data API: its parameters and the fields its records have. Same key rules as the data itself.","security":[[],{"dataKey":[]},{"dataKeyHeader":[]}],"x-minimum-role":null,"x-auth":"data_key","x-rate-limit":{"requests":600,"per":"minute","counted_by":"calling address"},"x-write-handshake":false,"parameters":[{"name":"workspace","in":"path","required":true,"description":"The workspace's short name.","schema":{"type":"string"}},{"name":"flow","in":"path","required":true,"description":"The address name set on the flow.","schema":{"type":"string"}}],"responses":{"200":{"description":"An OpenAPI 3 document.","content":{"application/json":{"schema":{"type":"object","properties":{"openapi":{"type":"string"},"info":{"type":"object","properties":{"title":{"type":"string"},"version":{"type":"string"},"description":{"type":"string"}}},"servers":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"}}}},"paths":{"type":"string"}}},"example":{"openapi":"3.0.3","info":{"title":"Open data: building permits API","version":"1","description":"Publishes the permits table as a read-only JSON API for the open data portal.\n\nPublished by City of Demo with SentraBridge. Each request reads the data live from its sources; SentraBridge doesn't keep a copy."},"servers":[{"url":"https://app.sentrabridge.example"}],"paths":"…"}}}},"429":{"description":"More than 600 calls in a minute. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/hooks/{token}":{"post":{"operationId":"receiveWebhook","tags":["Webhooks"],"summary":"Send an event into a flow","description":"POST JSON to a flow's private address (shown on its \"When a system calls in\" step). The flow runs inside the request and the message is dropped afterwards: it is never queued or stored. If the flow checks signatures, sign the body as its step says. The flow may also limit which addresses can call.","security":[],"x-minimum-role":null,"x-auth":"webhook","x-rate-limit":{"requests":120,"per":"minute","counted_by":"calling address"},"x-write-handshake":false,"parameters":[{"name":"token","in":"path","required":true,"description":"The private part of the flow's address.","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"Any JSON object. The flow says where the records are inside it."},"example":{"id":"REQ-1001","status":"open"}}}},"responses":{"200":{"description":"The run that handled the message, with counts.","content":{"application/json":{"schema":{"type":"object","properties":{"run_id":{"type":"string"},"status":{"type":"string"},"records":{"type":"integer"},"sent":{"type":"integer"}}},"example":{"run_id":"01m769bjym26wdm4ej2rw2k7wn","status":"ok","records":1,"sent":0}}}},"401":{"description":"The signature check failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The calling address is not on the flow's list.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The flow is turned off.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The body was not JSON, or was empty.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"More than 120 calls in a minute. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"The flow ran and failed. The body still carries the run ID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/platform-status":{"get":{"operationId":"getPlatformStatus","tags":["Status"],"summary":"Platform status","description":"Whether SentraBridge itself is working: five components checked live, 90 days of uptime, open incidents and planned maintenance. Open to any origin. Cached for 30 seconds. It never includes anything about a client.","security":[],"x-minimum-role":null,"x-auth":"none","x-rate-limit":{"requests":240,"per":"minute","counted_by":"calling address"},"x-write-handshake":false,"responses":{"200":{"description":"Overall status, components, uptime and notices.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"label":{"type":"string"},"updated_at":{"type":"string"},"components":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"status":{"type":"string"}}}},"uptime":{"type":"object","properties":{"app":{"type":"object","properties":{"days":{"type":"array","items":{"description":"Empty (null) in the captured example."}},"percent":{"type":"integer"}}},"api":{"type":"object","properties":{"days":{"type":"array","items":{"description":"Empty (null) in the captured example."}},"percent":{"type":"integer"}}},"schedules":{"type":"object","properties":{"days":{"type":"array","items":{"description":"Empty (null) in the captured example."}},"percent":{"type":"integer"}}},"live":{"type":"object","properties":{"days":{"type":"array","items":{"description":"Empty (null) in the captured example."}},"percent":{"type":"integer"}}},"queue":{"type":"object","properties":{"days":{"type":"array","items":{"description":"Empty (null) in the captured example."}},"percent":{"type":"integer"}}}}},"incidents":{"type":"array"},"upcoming":{"type":"array"}}},"example":{"status":"operational","label":"All systems operational","updated_at":"2026-11-11T21:48:51+00:00","components":[{"key":"app","label":"Web app and sign-in","status":"operational"},{"key":"api","label":"API, webhooks and MCP","status":"operational"}],"uptime":{"app":{"days":[null,null],"percent":100},"api":{"days":[null,null],"percent":100},"schedules":{"days":[null,null],"percent":100},"live":{"days":[null,null],"percent":100},"queue":{"days":[null,null],"percent":100}},"incidents":[],"upcoming":[]}}}},"429":{"description":"More than 240 calls in a minute. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/status/{slug}":{"get":{"operationId":"getWorkspaceStatus","tags":["Status"],"summary":"A workspace's public status page","description":"For workspaces that turned their status page on: the state and 30-day history of the flows they chose to show. Names, states and counts of runs only.","security":[],"x-minimum-role":null,"x-auth":"none","x-rate-limit":{"requests":120,"per":"minute","counted_by":"calling address"},"x-write-handshake":false,"parameters":[{"name":"slug","in":"path","required":true,"description":"The workspace's short name.","schema":{"type":"string"}}],"responses":{"200":{"description":"Overall state and one entry per published flow.","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"timezone":{"type":"string"},"overall":{"type":"string"},"flows":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"state":{"type":"string"},"last_success_at":{"type":"string"},"uptime":{"type":"integer"},"days":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string"},"ok":{"type":"integer"},"partial":{"type":"integer"},"failed":{"type":"integer"}}}}}}},"updated_at":{"type":"string"}}},"example":{"name":"City of Demo","timezone":"America/Los_Angeles","overall":"paused","flows":[{"name":"Copy 311 requests into the city database, with map points","description":"Every 15 minutes, upserts new and changed Sentra 311 requests into a PostGIS table, with a map point built from latitude and longitude.","state":"paused","last_success_at":"2026-11-11T17:26:51+00:00","uptime":100,"days":[{"date":"2026-10-13","ok":0,"partial":0,"failed":0},{"date":"2026-10-14","ok":0,"partial":0,"failed":0}]},{"name":"File CivicCA agendas and minutes in Laserfiche","description":"File CivicCA agendas and minutes in Laserfiche","state":"paused","last_success_at":"2026-11-11T06:48:59+00:00","uptime":100,"days":[{"date":"2026-10-13","ok":0,"partial":0,"failed":0},{"date":"2026-10-14","ok":0,"partial":0,"failed":0}]}],"updated_at":"2026-11-11T21:48:51+00:00"}}}},"404":{"description":"No workspace with that name has a status page.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"More than 120 calls in a minute. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/status/feed.atom":{"get":{"operationId":"getStatusAtom","tags":["Status"],"summary":"Status feed (Atom)","description":"Incidents and planned maintenance as an Atom feed. Cached for 5 minutes.","security":[],"x-minimum-role":null,"x-auth":"none","x-rate-limit":{"requests":120,"per":"minute","counted_by":"calling address"},"x-write-handshake":false,"responses":{"200":{"description":"An Atom feed.","content":{"application/atom+xml":{"schema":{"type":"string"},"example":"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<feed xmlns=\"http://www.w3.org/2005/Atom\"><title>SentraBridge status</title><subtitle>Incidents and planned maintenance on the SentraBridge platform</subtitle><id>tag:app.sentrabridge.example,2026:status</id><updated>2026-11-11T21:48:51+00:00</updated><link rel=\"alternate\" type=\"text/html\" href=\"https://app.sentrabridge.example/status/\"/><link rel=\"self\" type=\"application/atom+xml\" href=\"https://app.sentrabridge.example/public/status/feed.atom\"/><author><name>SentraBridge</name></author></feed>\n…"}}},"429":{"description":"More than 120 calls in a minute. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/status/feed.rss":{"get":{"operationId":"getStatusRss","tags":["Status"],"summary":"Status feed (RSS)","description":"The same notices as an RSS 2.0 feed.","security":[],"x-minimum-role":null,"x-auth":"none","x-rate-limit":{"requests":120,"per":"minute","counted_by":"calling address"},"x-write-handshake":false,"responses":{"200":{"description":"An RSS feed.","content":{"application/rss+xml":{"schema":{"type":"string"},"example":"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\"><channel><title>SentraBridge status</title><link>https://app.sentrabridge.example/status/</link><description>Incidents and planned maintenance on the SentraBridge platform</description><language>en-us</language><lastBuildDate>Wed, 11 Nov 2026 21:48:51 +0000</lastBuildDate><atom:link href=\"https://app.sentrabridge.example/public/status/feed.rss\" rel=\"self\" type=\"application/rss+xml\"/></channel></rss>\n…"}}},"429":{"description":"More than 120 calls in a minute. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/status/maintenance.ics":{"get":{"operationId":"getMaintenanceCalendar","tags":["Status"],"summary":"Maintenance calendar (iCal)","description":"Planned maintenance windows as a calendar you can subscribe to.","security":[],"x-minimum-role":null,"x-auth":"none","x-rate-limit":{"requests":120,"per":"minute","counted_by":"calling address"},"x-write-handshake":false,"responses":{"200":{"description":"An iCalendar file.","content":{"text/calendar":{"schema":{"type":"string"},"example":"BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:-//SentraBridge//Status//EN\nCALSCALE:GREGORIAN\nMETHOD:PUBLISH\nX-WR-CALNAME:SentraBridge planned maintenance\nX-PUBLISHED-TTL:PT1H\nREFRESH-INTERVAL;VALUE=DURATION:PT1H\nEND:VCALENDAR\n…"}}},"429":{"description":"More than 120 calls in a minute. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/status/subscribe":{"get":{"operationId":"getStatusSubscribeOptions","tags":["Status"],"summary":"Ways to get status updates","description":"The channels on offer (email, text when set up, webhook) and the components you can follow.","security":[],"x-minimum-role":null,"x-auth":"none","x-rate-limit":{"requests":60,"per":"minute","counted_by":"calling address"},"x-write-handshake":false,"responses":{"200":{"description":"Channels and components.","content":{"application/json":{"schema":{"type":"object","properties":{"channels":{"type":"array","items":{"type":"string"}},"components":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"}}}}}},"example":{"channels":["email","webhook"],"components":[{"key":"app","label":"Web app and sign-in"},{"key":"api","label":"API, webhooks and MCP"}]}}}},"429":{"description":"More than 60 calls in a minute. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"subscribeToStatus","tags":["Status"],"summary":"Subscribe to status updates","description":"Email: a confirmation link is sent. Webhook: SentraBridge first POSTs a signed `subscription.verify` message with a `challenge`; your endpoint must answer 2xx with the challenge in its body. The signing secret is shown once in the answer. Limited to 20 tries an hour per network.","security":[],"x-minimum-role":null,"x-auth":"none","x-rate-limit":{"requests":10,"per":"minute","counted_by":"calling address"},"x-write-handshake":false,"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","description":"How to be told. Default email.","enum":["email","sms","webhook"]},"email":{"type":"string","description":"For channel email."},"phone":{"type":"string","description":"For channel sms."},"url":{"type":"string","description":"For channel webhook: a public https address."},"secret":{"type":"string","description":"Optional signing secret of your own, 16 to 200 characters. One is made if left out."},"components":{"type":"array","items":{"type":"string"},"description":"Only these components (keys from \"Ways to get status updates\"). Default: everything."}}},"example":{"channel":"webhook","url":"https://ops.example.gov/hooks/sentrabridge-status","components":["api","schedules"]}}}},"responses":{"202":{"description":"Email and text: a message saying a confirmation is on its way. A verified webhook answers 201 instead, with `message`, `secret` (shown once) and `unsubscribe_url`.","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"}}},"example":{"message":"Too Many Attempts."}}}},"422":{"description":"Something is missing, or the webhook address could not be verified.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many tries.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/mcp":{"post":{"operationId":"mcp","tags":["MCP"],"summary":"MCP server","description":"SentraBridge as a Model Context Protocol server (streamable HTTP, JSON answers, no event stream). Send JSON-RPC 2.0: `initialize`, `ping`, `tools/list`, `tools/call`. Signs in with an API token only. Each tool needs a least role; a tool the token's role cannot use answers with `isError`. Every tool call is written to the activity log and the MCP call log. No session or X-XSRF-TOKEN is needed here.","security":[{"apiToken":[]}],"x-minimum-role":null,"x-auth":"token","x-rate-limit":{"requests":120,"per":"minute","counted_by":"person"},"x-write-handshake":false,"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"jsonrpc":{"type":"string","description":"Always \"2.0\"."},"id":{"type":"integer","description":"Your request number. Leave it out for notifications."},"method":{"type":"string","description":"The MCP method.","enum":["initialize","ping","tools/list","tools/call"]},"params":{"type":"object","description":"For tools/call: `name` and `arguments`."}},"required":["jsonrpc","method"]},"example":{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_flows","arguments":[]}}}}},"responses":{"200":{"description":"A JSON-RPC result. Notifications answer 202 with no body.","content":{"application/json":{"schema":{"type":"object","properties":{"jsonrpc":{"type":"string"},"id":{"type":"integer"},"result":{"type":"object","properties":{"content":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string"},"text":{"type":"string"}}}},"structuredContent":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"description":"Empty (null) in the captured example."},"enabled":{"type":"boolean"},"trigger":{"type":"string"},"schedule":{"description":"Empty (null) in the captured example."},"steps":{"type":"integer"},"connectors":{"type":"array"},"next_run_at":{"description":"Empty (null) in the captured example."},"last_run_at":{"type":"string"},"last_status":{"type":"string"},"version":{"type":"integer"},"template":{"description":"Empty (null) in the captured example."},"updated_at":{"type":"string"},"last_success_at":{"description":"Empty (null) in the captured example."},"department":{"description":"Empty (null) in the captured example."}}}}}},"isError":{"type":"boolean"}}}}},"example":{"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"(the same data as structuredContent, as text)"}],"structuredContent":{"data":[{"id":"01m769bjwj9bnvmyggg6rp5kcn","name":"API reference webhook sample","description":null,"enabled":false,"trigger":"trigger.webhook","schedule":null,"steps":2,"connectors":[],"next_run_at":null,"last_run_at":"2026-11-11T21:48:51+00:00","last_status":"ok","version":1,"template":null,"updated_at":"2026-11-11T21:48:51+00:00","last_success_at":null,"department":null},{"id":"01m769bjp928n7vqvkaezxh05w","name":"API reference sample","description":"Every 15 minutes, upserts new and changed Sentra 311 requests into a PostGIS table, with a map point built from latitude and longitude.","enabled":false,"trigger":"trigger.schedule","schedule":"*/15 * * * *","steps":4,"connectors":["01m74hqe33cy19f80c393jnpz7","01m74knwpc7xd4424q3ybrqf3b"],"next_run_at":null,"last_run_at":null,"last_status":null,"version":1,"template":null,"updated_at":"2026-11-11T21:48:51+00:00","last_success_at":null,"department":null}]},"isError":false}}}}},"401":{"description":"No valid API token was sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"More than 120 calls in a minute. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/openapi.json":{"get":{"operationId":"getOpenapi","tags":["Reference"],"summary":"This document","description":"The OpenAPI 3.1 description of the supported API, with this server's address filled in.","security":[],"x-minimum-role":null,"x-auth":"none","x-rate-limit":{"requests":120,"per":"minute","counted_by":"calling address"},"x-write-handshake":false,"responses":{"200":{"description":"An OpenAPI 3.1 document.","content":{"application/json":{"schema":{"type":"object"}}}},"429":{"description":"More than 120 calls in a minute. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/egress-ips":{"get":{"operationId":"getEgressIps","tags":["Reference"],"summary":"Outbound addresses","description":"The addresses SentraBridge calls out from, for your firewall's allow list. Not secret.","security":[],"x-minimum-role":null,"x-auth":"none","x-rate-limit":{"requests":60,"per":"minute","counted_by":"calling address"},"x-write-handshake":false,"responses":{"200":{"description":"A list of addresses and a note.","content":{"application/json":{"schema":{"type":"object","properties":{"ips":{"type":"array"},"note":{"type":"string"}}},"example":{"ips":[],"note":"This SentraBridge has no fixed outbound addresses listed. If it runs on your own network (self-hosted), it calls out from that server's address. Otherwise ask SentraBridge support for the relay address to allow."}}}},"429":{"description":"More than 60 calls in a minute. See the Retry-After header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"apiToken":{"type":"http","scheme":"bearer","bearerFormat":"sbk_…","description":"A personal API token from Settings → API & AI assistants. It acts as its person in one workspace, with the lower of their role and the token's limit (viewer, builder or admin)."},"dataKey":{"type":"http","scheme":"bearer","bearerFormat":"sbd_…","description":"A data key made on a published data API flow. Only for that flow's data."},"dataKeyHeader":{"type":"apiKey","in":"header","name":"X-API-Key","description":"The same data key, sent in a header instead."}},"parameters":{"XsrfToken":{"name":"X-XSRF-TOKEN","in":"header","required":true,"schema":{"type":"string"},"description":"The URL-decoded value of the XSRF-TOKEN cookie from GET /v1/auth/csrf. Send the cookies from that call too."}},"schemas":{"Error":{"type":"object","required":["message"],"properties":{"message":{"type":"string","description":"What went wrong, in plain words."},"errors":{"type":"object","description":"On 422: messages per field.","additionalProperties":{"type":"array","items":{"type":"string"}}},"code":{"type":"string","description":"On some 403 answers: ip_blocked, sso_required, mfa_setup_required."},"problems":{"type":"array","description":"On 422 from run and turn-on: what to fix in the flow.","items":{"type":"object"}}}}}},"x-roles":{"viewer":"Look: flows, runs, health, trace, approvals.","builder":"Also change, import, test and run flows.","admin":"Also connections, the activity log and usage."},"x-token-writes":{"handshake_required":false,"checked_on":"2026-11-11"},"x-error-examples":[{"status":401,"body":{"message":"Unauthenticated."}},{"status":403,"body":{"message":"Your role (viewer) can't do this. Ask an admin."}},{"status":404,"body":{"message":"Not found."}},{"status":422,"body":{"message":"The yaml field is required.","errors":{"yaml":["The yaml field is required."]}}}],"x-mcp":{"endpoint":"/mcp","protocol_versions":["2025-11-25","2025-06-18","2025-03-26"],"tools":[{"name":"list_flows","title":"List flows","description":"Every flow in this workspace: name, trigger, schedule, on/off, last run and status.","role":"viewer","read_only":true,"inputSchema":{"type":"object","properties":[]}},{"name":"get_flow","title":"Get a flow","description":"One flow: its steps in order (type and label), schedule, problems, notifications and status. Settings only; no records.","role":"viewer","read_only":true,"inputSchema":{"type":"object","properties":{"flow_id":{"type":"string","description":"Flow ID from list_flows"}},"required":["flow_id"]}},{"name":"describe_flow","title":"Describe a flow","description":"A plain-English description of a flow, step by step, in wiring order: what starts it, what each step does with the records, which systems it reads and writes. Settings only.","role":"viewer","read_only":true,"inputSchema":{"type":"object","properties":{"flow_id":{"type":"string","description":"Flow ID from list_flows"}},"required":["flow_id"]}},{"name":"get_flow_yaml","title":"Get a flow as YAML","description":"The whole flow as a SentraBridge flow file (YAML): every step with its settings and the wires. Connections appear by name, never their keys. Use it to understand a flow in detail or as the starting point for update_draft_flow.","role":"viewer","read_only":true,"inputSchema":{"type":"object","properties":{"flow_id":{"type":"string","description":"Flow ID from list_flows"}},"required":["flow_id"]}},{"name":"flow_building_guide","title":"How to build a flow","description":"Everything needed to write a SentraBridge flow file: the format, every step type with its settings and outputs, this workspace's connections with their resources and fields, and an example. Call this before create_draft_flow.","role":"viewer","read_only":true,"inputSchema":{"type":"object","properties":[]}},{"name":"create_draft_flow","title":"Create a draft flow","description":"Create a new flow from a SentraBridge flow file (YAML; see flow_building_guide). It is created turned off, as a draft for a person to preview and turn on. Only connections that already exist in this workspace can be used. Returns the new flow, any problems to fix, and its address in SentraBridge.","role":"builder","read_only":false,"inputSchema":{"type":"object","properties":{"yaml":{"type":"string","description":"The flow file, starting with \"sentrabridge: 1\""}},"required":["yaml"]}},{"name":"update_draft_flow","title":"Change a draft flow","description":"Replace the steps of a flow that is turned off with a new flow file (YAML). The old version stays in the flow's history and can be restored. Flows that are on are refused: a person must turn them off first.","role":"builder","read_only":false,"inputSchema":{"type":"object","properties":{"flow_id":{"type":"string","description":"Flow ID"},"yaml":{"type":"string","description":"The whole flow file, as from get_flow_yaml, with the changes"}},"required":["flow_id","yaml"]}},{"name":"run_history","title":"Run history","description":"Recent runs, newest first: status, counts in/out/failed, duration and redacted errors. Filter by flow or status.","role":"viewer","read_only":true,"inputSchema":{"type":"object","properties":{"flow_id":{"type":"string","description":"Only this flow"},"status":{"type":"string","enum":["ok","partial","failed","waiting"]},"limit":{"type":"integer","minimum":1,"maximum":200,"default":25}}}},{"name":"get_run","title":"Get a run","description":"One run step by step: counts, record IDs that moved (source → target), failures with their IDs and errors.","role":"viewer","read_only":true,"inputSchema":{"type":"object","properties":{"run_id":{"type":"string","description":"Run ID"}},"required":["run_id"]}},{"name":"trace_record","title":"Trace a record","description":"Follow one record ID (a permit number, request ID…) through every run and system: where it was read, sent, what it became, or why it failed.","role":"viewer","read_only":true,"inputSchema":{"type":"object","properties":{"record_id":{"type":"string","description":"The record's ID in any connected system"}},"required":["record_id"]}},{"name":"flow_health","title":"Flow health","description":"Health over the last 7–90 days: runs per day, success rate per flow, slowest steps and connections needing attention.","role":"viewer","read_only":true,"inputSchema":{"type":"object","properties":{"days":{"type":"integer","enum":[7,14,30,90],"default":14}}}},{"name":"list_connections","title":"List connections","description":"Connected systems: name, connector, status of the last test and which flows use them. Never credentials.","role":"viewer","read_only":true,"inputSchema":{"type":"object","properties":[]}},{"name":"list_approvals","title":"List approvals","description":"Approval requests: what is waiting, who can decide, and recent decisions.","role":"viewer","read_only":true,"inputSchema":{"type":"object","properties":{"pending_only":{"type":"boolean","default":true}}}},{"name":"run_flow","title":"Run a flow now","description":"Start a flow by hand, as the token's person. Returns the new run; check it with get_run.","role":"builder","read_only":false,"inputSchema":{"type":"object","properties":{"flow_id":{"type":"string","description":"Flow ID"}},"required":["flow_id"]}},{"name":"run_flow_tests","title":"Run a flow's tests","description":"Run every saved test of a flow in dry mode (made-up records in; nothing is read from sources, written, sent or emailed) and report pass or fail per test and per expectation. Answers hold plain messages only, never record values.","role":"builder","read_only":false,"inputSchema":{"type":"object","properties":{"flow_id":{"type":"string","description":"Flow ID"}},"required":["flow_id"]}},{"name":"decide_approval","title":"Decide an approval","description":"Approve or reject a waiting request, if the token's person is an approver. Approving continues the flow for those records.","role":"viewer","read_only":false,"inputSchema":{"type":"object","properties":{"approval_id":{"type":"string","description":"Approval ID"},"decision":{"type":"string","enum":["approve","reject"]},"record_ids":{"type":"array","items":{"type":"string"},"description":"Approve only these records (default: all)"},"reason":{"type":"string","description":"Optional note"}},"required":["approval_id","decision"]}}],"examples":{"initialize":{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-06-18","capabilities":{"tools":{"listChanged":false}},"serverInfo":{"name":"sentrabridge","title":"SentraBridge","version":"0.5"},"instructions":"SentraBridge moves records between City of Demo's systems. These tools show flows, runs, health and approvals as counts, IDs and errors; record contents are never stored or returned. Role: viewer."}},"tools/list":{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"list_flows","title":"List flows","description":"Every flow in this workspace: name, trigger, schedule, on/off, last run and status.","inputSchema":{"type":"object","properties":[]},"annotations":{"readOnlyHint":true,"openWorldHint":false,"title":"List flows"}},{"name":"get_flow","title":"Get a flow","description":"One flow: its steps in order (type and label), schedule, problems, notifications and status. Settings only; no records.","inputSchema":{"type":"object","properties":{"flow_id":{"type":"string","description":"Flow ID from list_flows"}},"required":["flow_id"]},"annotations":{"readOnlyHint":true,"openWorldHint":false,"title":"Get a flow"}}]}}}},"x-webhook-signatures":[{"scheme":"none","label":"Don't check (the private address is the only secret)","header":null,"how":"No check. The private address is the only secret."},{"scheme":"hmac","label":"Another system: HMAC-SHA256 of the body in a header","header":"X-Signature (or the header you name)","how":"HMAC-SHA256 of the raw body with the shared secret, as hex or base64, with or without a \"sha256=\" prefix."},{"scheme":"token","label":"Another system: a shared token in a header","header":"X-Webhook-Token (or the header you name)","how":"The shared secret itself, compared in constant time."},{"scheme":"sentra311","label":"Sentra 311 or Sentra Scheduler (X-Civic311-Signature)","header":"X-Civic311-Signature + X-Civic311-Timestamp","how":"\"sha256=\" + hex HMAC-SHA256 of \"{timestamp}.{body}\". Refused when the timestamp is more than 5 minutes off."},{"scheme":"sentraqueue","label":"SentraQueue (X-SentraQueue-Signature)","header":"X-SentraQueue-Signature","how":"\"t={unix},v1={hex}\" where v1 is the HMAC-SHA256 of \"{t}.{body}\". Refused when t is more than 5 minutes off."},{"scheme":"civicca","label":"CivicCA (X-CivicCA-Signature)","header":"X-CivicCA-Signature","how":"\"t={unix},v1={hex}\" where v1 is the HMAC-SHA256 of \"{t}.{body}\". Refused when t is more than 5 minutes off."},{"scheme":"sentradeed","label":"SentraDeed (X-SentraDeed-Signature)","header":"X-SentraDeed-Signature","how":"\"t={unix},v1={hex}\" where v1 is the HMAC-SHA256 of \"{t}.{body}\". Refused when t is more than 5 minutes off."},{"scheme":"sentracms","label":"Sentra CMS (X-SentraCMS-Signature)","header":"X-SentraCMS-Signature","how":"\"sha256=\" + hex HMAC-SHA256 of the raw body."}],"x-webhook-signature-window-seconds":300,"x-outgoing-webhooks":{"signature":{"header":"X-SentraBridge-Signature","timestamp_header":"X-SentraBridge-Timestamp","format":"sha256=<hex>","signed_text":"{timestamp}.{raw body}","algorithm":"HMAC-SHA256","test_vector":{"example_secret":"whsec_test","timestamp":"1768496400","body":"{\"type\":\"subscription.verify\"}","signature":"sha256=bc1904fb176ca5096ac52d6ca0b4ac0e50083adb8b26c9c2bf12656a8cfcdac4"}},"status":{"headers":["X-SentraBridge-Event","X-SentraBridge-Timestamp","X-SentraBridge-Signature"],"user_agent":"SentraBridge-Status/1.0","timeout_seconds":10,"pauses_after_failures":20,"events":["subscription.verify","incident.posted","incident.updated","maintenance.posted","maintenance.updated","maintenance.reminder"],"verify_example":{"type":"subscription.verify","challenge":"EXAMPLE_CHALLENGE","sent_at":"2026-01-15T17:00:00+00:00"},"example":{"type":"incident.posted","sent_at":"2026-01-15T17:00:00+00:00","status_page":"https://sentrabridge.example/status/","incident":{"id":"01jexampleincident0000000000","title":"Scheduled runs are delayed","kind":"incident","impact":"degraded","status":"investigating","components":["schedules"],"starts_at":"2026-01-15T17:00:00+00:00","ends_at":null,"resolved_at":null},"update":{"at":"2026-01-15T17:00:00+00:00","status":"investigating","text":"Scheduled flows are starting late. We are looking into it."},"unsubscribe_url":"https://app.sentrabridge.example/public/status/unsubscribe?token=EXAMPLE"}},"activity_log":{"headers":["X-SentraBridge-Timestamp","X-SentraBridge-Sink","X-SentraBridge-Signature"],"user_agent":"SentraBridge-LogForwarder/1","timeout_seconds":15,"signed_when":"A signing secret is saved on the destination.","example":{"source":"sentrabridge","sent_at":"2026-01-15T17:00:00+00:00","events":[{"id":4821,"chain":"01jexampleworkspace00000000","workspace_id":null,"workspace":null,"at":"2026-01-15T17:00:00+00:00","action":"flow.toggled","summary":"Turned on Nightly permit export","actor":{"id":null,"name":"System","email":null},"subject":{"type":"flow","id":"01jexampleflow0000000000000"},"ip":"203.0.113.10","hash":"ab12ab12ab12ab12ab12ab12ab12ab12ab12ab12ab12ab12ab12ab12ab12ab12","prev_hash":"cd34cd34cd34cd34cd34cd34cd34cd34cd34cd34cd34cd34cd34cd34cd34cd34","meta":null}]}}},"x-also":[{"method":"GET","path":"/scim/v2/Users","note":"SCIM 2.0 provisioning (RFC 7644) at /scim/v2 with a SCIM token from Settings → Security. Point your identity provider at it; it follows the standard, so it is not repeated here."}]}