404 exactly like one that never existed, and a trigger that names an agent the credential cannot read is not listed to it.
Reads need agents:read. Writes need agents:write and the owner role, because a trigger sends the business’s call data to another system. A dry run needs agents:write only. Success answers use the compatibility envelope {"data": ..., "errors": null}; refusals use the standard error envelope (Errors and limits). Without a customer database, the two list routes answer empty lists, the events route still answers, and every other route answers 503 service_unavailable.
Routes
A
limit, offset, sort, event or status outside the values above is refused with 400 validation_failed, not clamped.
Editing together
Two owners can open the same trigger and both save. Without a guard the later save wins and the earlier one is lost without a word. To avoid that, send theupdated_at you read back with the edit:
expected_updated_at is the trigger’s updated_at, sent back unchanged (a time with a zone). The trigger is saved only if it still has that updated_at, and the check and the save are one step, so of two saves that carry the same value exactly one is applied. A pause or a resume (enabled or status) is an edit and takes the same field.
If another owner saved first, the answer is 409 conflict with reason: "stale_trigger" and updated_at, the value now on file. Nothing is written and nothing is recorded in the audit log. Show the owner what changed, then either discard the edit or send it again with expected_updated_at set to that updated_at to save it over the newer version. A value that is not a time with a zone is 400 validation_failed with field: "expected_updated_at". A trigger of another workspace, or one deleted meanwhile, is 404 not_found and names no updated_at.
Without the field a save behaves as it always did: the last save wins.
A trigger
A trigger reads
id, object: "automation_trigger", org_id, name, enabled, status, event, event_name, conditions, action, agent_ids, run_count, last_run_at, created_at and updated_at.
Events
|
tool.success, tool.failure | An attached conversation tool finished or was refused on a live phone call. A Test panel run, a simulation, a typed preview and API chat never fire them. Fixture results carry environment: FIXTURE; they are not live system success | agent_id, call_id, tool, status (number or null), environment | | call.post_webhook | A completed call was recorded and its post-call event is ready, not proof of webhook delivery | agent_id, call_id, direction | | call.started | A phone runtime session reached its opening | agent_id, call_id, direction | | eval.completed | An existing evaluation run announced completion or failure | agent_id, test_id, status, outcome | | sms.received, sms.post_webhook | An authenticated, non-replayed inbound SMS webhook was stored | agent_id, thread_id, message_id | | call.voicemail | The outbound runtime classified an answering machine | agent_id, call_id | | call.inbound_received | An authenticated inbound phone request passed admission | agent_id, call_id | | integration.webhook_received | An authenticated incoming integration delivery was stored for the first time | agent_id, endpoint_id, event_id, event_type, record_type, record_id |
call.completed never fires for a test, and on a call Sempera’s own call service answered its summary is empty (Webhooks). agent.published and call.outcomes are the webhook events of the same name, reduced to the fields listed here. Integration events contain identifiers, never arbitrary incoming payloads or secrets. There are no invented CRM subscriptions. Tool actions triggered by automations do not recursively emit conversation tool events.
Integration and CRM changes
Choose Integration webhook received in the trigger builder. The contact and booking templates add an event-type condition; they do not subscribe to another system. Configure your sender to deliver a JSON object to the agent’s existing incoming webhook using the Sempera signature and delivery-ID headers. For example:endpoint_id and event_type to limit a trigger to one connection and one change. Tool inputs can reference {{record_id}} to look up or update the record through a workspace-approved API tool. Do not send credentials or whole customer records in these identifier fields. The existing incoming-event storage still retains the original accepted body under its existing retention policy.
An owner must configure the external sender or a signing adapter. Native provider webhook signatures and automatic CRM subscriptions are not implemented by this path. Receipt-to-trigger dispatch remains best effort, not a durable subscription queue: a process interruption after storage can leave an event unannounced, and redelivery is deduplicated. No live delivery proof is implied by a template or dry run.
Actions
A bare URL is not a destination: register the address as a webhook endpoint first. An
endpoint_id or tool_id that is not this workspace’s own, or a tool that is not an API request tool, is a 400. Call and SMS actions additionally require calls:write and outbound permission when created or edited. Their target agent must also be in the credential’s scope. Choosing a custom_code action additionally requires tools:write and snippet permission. A call action’s timezone, window and webhook are checked when saved, by the rules POST /v1/calls uses. A call or SMS action on call.failed or call.outcomes is a 400: only inbound call events may start contact, so it could never run.
The four specialized actions are stored under the durable tool business-action class, so existing publication crash, retention and owner-review protections still apply. Their public action preserves its specialized kind. No schema migration is needed. Each execution has a stable one-shot dispatch key, with stale reservation recovery disabled. These actions cannot be manually retried; inspect an uncertain result before authoring new work. Tests and dry runs never send. A fixture-mode SMS or Slack action records that nothing was delivered rather than reporting a real delivery. Custom code records execution status, not arbitrary code output, in the delivery log.
A webhook action posts this body with X-Sempera-Signature, X-Sempera-Event-Id (the body’s id, the same on every attempt), X-Sempera-Event and, when the workspace has turned on JWT request signing, the signing header (Secrets and request signing):
2xx, and follows the delivery rules in Webhooks. A tool is called once, because a tool that changes something is not safe to repeat unasked, through the same road and write rules as a tool step inside a call. Where the deployment answers tools from fixtures, the run ends failed with an error saying nothing was sent.
Runs
A run readsid, object: "automation_execution", trigger_id, trigger_name, event, event_id, event_summary, event_data, agent_ids, status, matched, failed_condition, action_kind, action, stalled, retryable, started_at, completed_at and duration_ms.
status is skipped (a condition did not hold; failed_condition names the first, with the value it saw), running, succeeded or failed. action is the result: for a webhook the endpoint and every attempt with its response_code or error, for a tool the tool and the outcome, for an email recipients and sent. A run still running well after its attempts could have finished reads stalled: true.
POST .../retry sends a failed or stalled webhook or tool run once more, now: the same event id and time, to the trigger’s current destination, numbered after the attempts it already has. It answers the run. A skipped or succeeded run, an email run (its owners would get it twice), a run whose call content was deleted, a run still delivering, and a run whose trigger’s action has changed since, are refused with 409 conflict and a sentence saying why.
Dry run
event sets some of the event’s fields; the event’s own sample fills the rest (and a trigger that names agents uses its first). It answers object: "automation_test", dry_run: true, delivered: false, the resolved event_data, in_scope, each condition with the value it saw (actual) and whether it passed, failed_condition, would_run and, when it would run, the action that would go out with its destination. A field the event does not have, or one of the wrong type, is a 400.