Headless API
The custom chat interface lets you connect your genie to any application you build and control. Your application talks to your genie directly over a REST API, called a Headless API, instead of routing users through Slack, Microsoft Teams, or Workato GO. The custom chat interface includes API endpoints to manage conversations and send and receive genie messages. This API is separate from the Agent Studio Developer API, which lets you create, configure, and maintain genies.
HEADLESS API ENDPOINTS REQUIRE A CUSTOM CHAT INTERFACE
Headless API endpoints are only compatible with the custom chat interface option.
PRIVATE BETA
Headless APIs are in private beta and only available to selected customers. Contact your Customer Success Manager to learn more.
Headless API prerequisites
Before you use the Headless API, confirm the following:
- You have a genie in Agent Studio.
- You've set up the genie's custom chat interface from the Agent Studio UI, including creating and attaching a genie client with either API key or OAuth 2.0 (PKCE) credentials. Refer to Genie clients in the Developer API if you provision the client programmatically instead of through the UI.
- Headless API is enabled for your workspace. Headless API is in private beta: if the custom chat interface option doesn't appear in your genie's chat interface settings, contact your Customer Success Manager to request access.
- Your builder role has the Custom chat interface privilege under Genie building. Refer to collaborator privileges for more information on configuring builder roles.
Headless API base URLs
Headless API is available in all data centers where Agentic is supported. The base URLs follow the same per-data-center scheme as the app host. For example:
| Data center | Dev API base URL | Headless API base URL |
|---|---|---|
| US | https://www.workato.com | https://genie-api.workato.com |
Refer to the data center overview for more information.
Authentication
Headless API supports the following access methods:
API key:
Authorization: Bearer <api_key>
X-IDP-User-Id: <idp_user_id>OAuth 2.0 (PKCE):
Authorization: Bearer <access_token>Authorization methods comparison
Refer to the following authorization method comparison and use case recommendation tables to determine which authorization method to use:
| Comparison point | API key (token-based) | OAuth 2.0 (PKCE) |
|---|---|---|
| How it works | The builder's backend holds a static API key and asserts user identity through the X-IDP-User-Id header. | End users authenticate directly through Workato Identity and delegate to the customer's IdP through SAML SSO. |
| End user visibility | Workato is invisible to end users. | End users see an IdP login step. |
| Required headers | Authorization: Bearer <api_key> and X-IDP-User-Id: <idp_user_id> | Authorization: Bearer <access_token> |
| Client credentials | The API key is static and is shown only once. | client_id only — PKCE replaces the client secret. |
| User identity managed by | The builder maps user IDs to Workato IdP user IDs. | IdP directly, such as Okta, Azure AD, OneLogin, and more. |
| Prerequisites | End users provisioned in Workato Identity through IAM API or the Workato UI. | One-time SAML federation between Workato Identity and the customer's existing IdP. Workato Identity acts as a broker. It doesn't replace the customer's IdP. |
| Best for | Server-side integrations, QA, automated testing, internal tools. | User-facing apps requiring SSO, centralized access governance, and per-user permissions. |
Recommended authorization method by scenario
| Scenario | Recommended |
|---|---|
| QA / automated testing | API key |
| Security red-teaming through CI/CD | API key |
| Internal tool with builder-controlled authorization | API key |
| Custom chat UI where Workato should be invisible | API key |
| Employee portal with company SSO | OAuth 2.0 |
| Multi-tenant app serving multiple organizations | OAuth 2.0 |
| Deployment requiring IdP-governed access control | OAuth 2.0 |
| Mobile or single-page app or public client | OAuth 2.0 |
OAuth 2.0 PKCE authorization flow
OAuth clients authenticate end users through Workato Identity (id.workato.com) using the authorization code flow with PKCE. No client secret is required. Use the oauth_client_id returned when you create an OAuth genie client.
You must complete the following steps to use this authorization flow:
Generate a code_verifier to create a random, URL-safe string of 43–128 characters and derive the code_challenge as base64url(sha256(code_verifier)).
Redirect the user to the authorization endpoint:
https://id.workato.com/oauth/authorize?response_type=code
&client_id=<oauth_client_id>
&redirect_uri=<your_redirect_url>
&scope=openid profile email
&state=<random_state>
&code_challenge=<code_challenge>
&code_challenge_method=S256Verify state matches when Workato Identity redirects to your redirect_uri with a code and the original state.
Exchange the code for tokens at https://id.workato.com/oauth/token with a form-encoded body. Don't send the client secret:
curl -X POST https://id.workato.com/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "client_id=<oauth_client_id>" \
-d "redirect_uri=<your_redirect_url>" \
-d "code=<authorization_code>" \
-d "code_verifier=<code_verifier>"Use the returned access_token as Authorization: Bearer <access_token> on Headless API calls. The token response also includes a refresh_token and an id_token.
Refresh the access token before it expires (expires_in is 3600 seconds) to avoid forcing the user through an interactive login again. Exchange the refresh_token at https://id.workato.com/oauth/token to obtain a new access token silently:
curl -X POST https://id.workato.com/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "client_id=<oauth_client_id>" \
-d "refresh_token=<refresh_token>"Refresh tokens are single-use and rotating: each refresh returns a new refresh_token. Persist the new value for the next refresh. Request the default openid profile email scope and don't add offline_access, which Workato Identity rejects.
The redirect URL must match the oauth_redirect_url registered on the client. Perform the code exchange in the preceding steps from a same-origin backend rather than directly in the browser. This ensures the flow is free of cross-origin (CORS) concerns and keeps token handling server-side.
Quick reference
List conversations
Retrieve a list of the authenticated user's conversations. Results are listed by the created_at timestamp in descending order.
GET /api/v1/genies/:genie_handle/chat/conversationsURL parameters
| Name | Type | Description |
|---|---|---|
| genie_handle | string required | Genie handle. |
Query parameters
| Name | Type | Description |
|---|---|---|
| limit | integer optional | Number of conversations to return. Defaults to 50. |
| cursor | string optional | Pagination cursor from a previous response. |
Response
| Name | Type | Description |
|---|---|---|
| list[] | array | List of conversations. |
| list[].conversation_id | string | Conversation ID. |
| list[].topic | string | Conversation topic. |
| list[].last_updated_at | string | Timestamp of the last update. |
| list[].created_at | string | Timestamp when the conversation was created. |
| total_count | integer | Total number of conversations. |
| cursor | string | Cursor for the next page of results. |
Create a conversation
Create a new conversation with the genie.
POST /api/v1/genies/:genie_handle/chat/conversationsURL parameters
| Name | Type | Description |
|---|---|---|
| genie_handle | string required | Genie handle. |
Response
| Name | Type | Description |
|---|---|---|
| conversation_id | string | ID of the new conversation. |
Get a conversation
Retrieve the details and current state of a conversation.
GET /api/v1/genies/:genie_handle/chat/conversations/:conversation_idURL parameters
| Name | Type | Description |
|---|---|---|
| genie_handle | string required | Genie handle. |
| conversation_id | string required | Conversation ID. |
Response
| Name | Type | Description |
|---|---|---|
| updated_at | string | Timestamp of the last update. |
| state | string | Current state of the conversation. One of idle (no active turn), ai_running (the model is generating), skill_processing (a skill is executing — including while the turn is paused on a runtime-connection authorization), or awaiting_approval (paused on a skill.confirmation_required). |
| last_event | object | The most recent event in the conversation. |
Get messages
Retrieve message history for a conversation. Results are listed by the created_at timestamp in descending order.
GET /api/v1/genies/:genie_handle/chat/conversations/:conversation_id/messagesURL parameters
| Name | Type | Description |
|---|---|---|
| genie_handle | string required | Genie handle. |
| conversation_id | string required | Conversation ID. |
Query parameters
| Name | Type | Description |
|---|---|---|
| cursor | string optional | Pagination cursor from a previous response. |
| limit | integer optional | Number of messages to return. Defaults to 100. |
Response
| Name | Type | Description |
|---|---|---|
| conversation_id | string | Conversation ID. |
| messages[] | array | List of messages. |
| messages[].message_id | string | Message ID. |
| messages[].source | string | Message origin. One of user or genie. |
| messages[].content | string | Text content of the message. |
| messages[].genie_run_id | string | ID of the request/response cycle the message belongs to. |
| messages[].created_at | string | Timestamp of the message in RFC3339 format. |
| total_count | integer | Total number of messages in the conversation. |
| cursor | string | Cursor for the next page of results. |
Send a message
Send a user message and receive a real-time stream of events as the genie processes and responds.
POST /api/v1/genies/:genie_handle/chat/conversations/:conversation_id/messagesURL parameters
| Name | Type | Description |
|---|---|---|
| genie_handle | string required | Genie handle. |
| conversation_id | string required | Conversation ID. |
Payload
| Name | Type | Description |
|---|---|---|
| message | string required | The user's message. Maximum 12 KB. |
| file_id | string optional | ID of a previously uploaded file to attach to this message. |
| stream | boolean optional | Set to true to receive an SSE stream in the response. Defaults to false. |
Response
When stream is false, returns HTTP 202 with the following body:
| Name | Type | Description |
|---|---|---|
| conversation_id | string | Conversation ID. |
| genie_run_id | string | ID of the genie run. Use this to reconnect to the stream. |
When stream is true, the response is a stream of Server-Sent Events. The stream closes when the genie finishes processing, indicated by the processing.finished event.
Reconnect to a stream
Reopen the SSE stream for a specific genie run. Use this to recover events after a disconnection. Pass the ID of the last successfully received event in the Last-Event-ID header.
GET /api/v1/genies/:genie_handle/chat/conversations/:conversation_id/genie-runs/:genie_run_idURL parameters
| Name | Type | Description |
|---|---|---|
| genie_handle | string required | Genie handle. |
| conversation_id | string required | Conversation ID. |
| genie_run_id | string required | Genie run ID, as returned by Send a message. |
Headers
| Name | Type | Description |
|---|---|---|
| Last-Event-ID | string optional | ID of the last successfully received event. The stream replays events from this point. |
Response
The response is a stream of Server-Sent Events.
Get events
Retrieve recent events. Use this as a fallback to manually fetch missed events after a disconnection. Events are returned oldest first and are available for 24 hours.
GET /api/v1/genies/:genie_handle/chat/conversations/eventsURL parameters
| Name | Type | Description |
|---|---|---|
| genie_handle | string required | Genie handle. |
Query parameters
| Name | Type | Description |
|---|---|---|
| since_created_at | string optional | Lower bound (inclusive) on event creation time, as an RFC3339 timestamp (for example, 2026-05-26T12:00:00.123456Z). Pass next_since_created_at from the previous response to fetch the next page. |
| conversation_id | string optional | Filter events by conversation ID. |
| limit | integer optional | Page size. Defaults to 100. |
Response
| Name | Type | Description |
|---|---|---|
| events[] | array | Array of events, ordered oldest first. |
| events[].conversation_id | string | Conversation ID. |
| events[].genie_handle | string | Genie handle (string identifier, for example gin-AaDX9axF-CNtgQn-B6). |
| events[].genie_run_id | string | ID of the genie run this event belongs to. |
| events[].type | string | Event type. |
| events[].event_id | string | UUIDv7 identifying this event. |
| events[].seq_num | integer | Per-genie-run monotonic sequence number. |
| events[].created_at | string | Gateway-side timestamp when the event was persisted, in RFC3339 format. |
| next_since_created_at | string | Continuation cursor. When present, pass this value as since_created_at on the next request to fetch the next page. |
Approve or reject a skill
Approve or reject a skill confirmation request. Use this when the genie emits a skill.confirmation_required event.
POST /api/v1/genies/:genie_handle/chat/conversations/:conversation_id/skill_approval/:call_idURL parameters
| Name | Type | Description |
|---|---|---|
| genie_handle | string required | Genie handle. |
| conversation_id | string required | Conversation ID. |
| call_id | string required | Call ID from the skill.confirmation_required event. |
Payload
| Name | Type | Description |
|---|---|---|
| resolution | string required | One of approved or rejected. |
| rejection_reason | string optional | Reason for rejection. Only used when resolution is rejected. |
Get a runtime connection link
Get an authentication link for a runtime connection. Use this when the genie emits a runtime_connection.auth_required event.
POST /api/v1/genies/:genie_handle/chat/runtime_connection/:runtime_connection_attempt_id/linkURL parameters
| Name | Type | Description |
|---|---|---|
| genie_handle | string required | Genie handle. |
| runtime_connection_attempt_id | string required | Server-generated ID for the pending runtime-connection authorization attempt, as delivered in the runtime_connection.auth_required SSE event. |
Response
| Name | Type | Description |
|---|---|---|
| status | string | One of auth_required or authorized. When authorized, the connection is already complete and no user action is needed. |
| auth_link.url | string | Authentication URL to present to the user. Only present when status is auth_required. |
| auth_link.expires_at | string | Expiration time of the authentication URL in RFC3339 format. Only present when status is auth_required. |
| auth_link.connector_name | string | Human-readable connector label (for example, Salesforce, Google Drive). Only present when status is auth_required. |
Reject a runtime connection
Reject a runtime connection request.
POST /api/v1/genies/:genie_handle/chat/runtime_connection/:runtime_connection_attempt_id/rejectURL parameters
| Name | Type | Description |
|---|---|---|
| genie_handle | string required | Genie handle. |
| runtime_connection_attempt_id | string required | Server-generated ID for the pending runtime-connection authorization attempt, as delivered in the runtime_connection.auth_required SSE event. |
Payload
| Name | Type | Description |
|---|---|---|
| reason | string optional | Reason for rejection. |
Upload a file
Upload a file to attach to a subsequent message. Returns a file_id to pass in the file_id parameter of Send a message. The maximum file size is 20 MB.
POST /api/v1/genies/:genie_handle/chat/conversations/:conversation_id/uploadURL parameters
| Name | Type | Description |
|---|---|---|
| genie_handle | string required | Genie handle. |
| conversation_id | string required | Conversation ID. |
Payload
| Name | Type | Description |
|---|---|---|
| file | file required | File data (multipart/form-data). Maximum size is 20 MB. |
Response
| Name | Type | Description |
|---|---|---|
| file_id | string | ID of the uploaded file. |
SSE events
You can send a message with stream: true to get a response with a stream of SSE covering the full lifecycle of the genie's response.
Events are persisted for 24 hours and are retrievable through the Get events endpoint.
Base event shape
Every event shares the following base fields:
| Field | Type | Description |
|---|---|---|
conversation_id | string | Conversation ID. |
genie_handle | string | Genie handle (string identifier, for example gin-AaDX9axF-CNtgQn-B6). |
genie_run_id | string | ID of the genie run this event belongs to. |
type | string | Event type. |
event_id | string | UUIDv7 identifying this event. |
seq_num | integer | Per-genie-run monotonic sequence number. |
created_at | string | Gateway-side timestamp when the event was persisted, in RFC3339 format. |
Event types
| Event | Additional fields | Description |
|---|---|---|
processing.started | Genie has begun processing the request. | |
processing.finished | Genie has completed processing. | |
agent.message |
| The genie's response message. |
skill.running |
| A skill has started executing. |
skill.completed |
| A skill completed successfully. |
skill.failed |
| A skill execution failed. |
skill.stopped |
| Terminal variant emitted by some runtime versions in place of skill.completed. Treat it as a successful completion. |
skill.confirmation_required |
| A skill requires user confirmation before executing. Use call_id with Approve or reject a skill. |
runtime_connection.auth_required |
| A skill requires the user to authenticate a connection. Use runtime_connection_attempt_id with Get a runtime connection link. |
system.ping | Keep-alive heartbeat, sent roughly every 30 seconds during long-running or paused turns. Ignore it. | |
system.stream_interrupted |
| The server closed the stream before the turn finished (for example, during a long pause). Recover from persisted state rather than assuming the turn failed; see Rebuild a conversation timeline. |
Limitations
Headless API endpoints have the following limitations:
- Action Board isn't supported
- App Events aren't supported
- A genie supports a single attached client. The client-to-genie relationship is 1:1: attaching a second client returns a
409conflict. Detach the existing client first to re-point it.
Last updated: