States are the building blocks of an RCS conversation. Each state represents one message in a flow — with its template, API operations, transitions, and input config. This guide covers the key concepts and constraints you need to know when building flows via the API.
State IDs
The stateId is set in the URL path when creating a state — it is not a field in the request body.
# stateId is "welcome" — set in the URL, not the body
POST /agents/ag_abc123/states/welcomeImportant constraints:
stateIdis immutable after creation — it cannot be renamed or changed- No spaces allowed in a
stateId - Choose descriptive, stable names before creating states in production
Deleting states
A state cannot be deleted if it is referenced as the flowEntryStateId of any other state. Delete or update all dependent states first, then delete the entry state.
Attempting to delete a referenced entry state returns 403.
authenticationId is always required on operations
Every API operation on a state requires an authenticationId — even when the connector uses no authentication.
{
"operations": [
{
"connectorId": "amazon-location-place-search",
"authenticationId": "none",
"operation": "searchPlaceByText",
"contextKey": "locations",
"data": { ... }
}
]
}Use "authenticationId": "none" for connectors that require no credentials. Omitting the field entirely will cause a validation error.
Conditional transitions
Transitions let a state route silently to a different state based on data — before the template renders and before any message is sent. Add a transitions array to define ordered conditions. The first condition that evaluates to "true" determines the next state; if none match, the template renders normally.
// POST /agents/ag_abc123/states/show-order-status
{
"operations": [
{
"connectorId": "order-service",
"authenticationId": "auth_abc123",
"operation": "getOrder",
"contextKey": "order",
"data": { "orderId": "{{ input.orderId }}" }
}
],
"transitions": [
{
"condition": "{% if data.order == null %}true{% endif %}",
"nextState": "order-not-found"
},
{
"condition": "{% if data.order.status == 'cancelled' %}true{% endif %}",
"nextState": "order-cancelled"
}
],
"template": {
"contentMessage": {
"text": "Order #{{ input.orderId }} is {{ data.order.status }}."
}
}
}Each transition object has two required fields:
| Field | Type | Description |
|---|---|---|
condition | string | Liquid expression. Fires when the rendered output equals the string "true". |
nextState | string | The stateId to route to when the condition matches. |
How evaluation works:
- Transitions are evaluated in array order — the first match wins
- Transitions run after operations complete, so
data.*values from connector responses are available in conditions - If a transition fires, the template is never rendered or sent — the agent moves directly to
nextState - If no condition matches, the template renders and the message is sent normally
Common condition patterns:
{{- /* Check an API response value */ -}}
{% if data.inventory.inStock == false %}true{% endif %}
{{- /* Check a param */ -}}
{% if params.tier == "premium" %}true{% endif %}
{{- /* Catch-all — always routes */ -}}
trueFull state example
A complete state with an API operation, template, input config, and a transition:
// POST /agents/ag_abc123/states/ask-order-id
{
"template": {
"contentMessage": {
"text": "Please enter your order number."
}
},
"input": {
"nextState": "show-order-status",
"assignments": [
{
"from": "message.text",
"to": "orderId"
}
]
}
}// POST /agents/ag_abc123/states/show-order-status
{
"operations": [
{
"connectorId": "order-service",
"authenticationId": "auth_abc123",
"operation": "getOrder",
"contextKey": "order",
"data": { "orderId": "{{ input.orderId }}" }
}
],
"transitions": [
{
"condition": "{% if data.order == null %}true{% endif %}",
"nextState": "order-not-found"
}
],
"template": {
"contentMessage": {
"text": "Order #{{ input.orderId }} is {{ data.order.status }}."
}
}
}See also
- POST /agents/:agentId/states/:stateId — Create a state
- PUT /agents/:agentId/states/:stateId — Update a state