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 centerDev API base URLHeadless API base URL
UShttps://www.workato.comhttps://genie-api.workato.com

Refer to the data center overview for more information.

Authentication

Headless API supports the following access methods:

API key:

http
Authorization: Bearer <api_key>
X-IDP-User-Id: <idp_user_id>

OAuth 2.0 (PKCE):

http
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 pointAPI key (token-based)OAuth 2.0 (PKCE)
How it worksThe 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 visibilityWorkato is invisible to end users.End users see an IdP login step.
Required headersAuthorization: Bearer <api_key> and X-IDP-User-Id: <idp_user_id>Authorization: Bearer <access_token>
Client credentialsThe API key is static and is shown only once.client_id only — PKCE replaces the client secret.
User identity managed byThe builder maps user IDs to Workato IdP user IDs.IdP directly, such as Okta, Azure AD, OneLogin, and more.
PrerequisitesEnd 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 forServer-side integrations, QA, automated testing, internal tools.User-facing apps requiring SSO, centralized access governance, and per-user permissions.
ScenarioRecommended
QA / automated testingAPI key
Security red-teaming through CI/CDAPI key
Internal tool with builder-controlled authorizationAPI key
Custom chat UI where Workato should be invisibleAPI key
Employee portal with company SSOOAuth 2.0
Multi-tenant app serving multiple organizationsOAuth 2.0
Deployment requiring IdP-governed access controlOAuth 2.0
Mobile or single-page app or public clientOAuth 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:

1

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)).

2

Redirect the user to the authorization endpoint:

plaintext
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=S256
3

Verify state matches when Workato Identity redirects to your redirect_uri with a code and the original state.

4

Exchange the code for tokens at https://id.workato.com/oauth/token with a form-encoded body. Don't send the client secret:

bash
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>"
5

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.

6

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:

bash
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

TypeResourceDescription
GET/api/v1/genies/:genie_handle/chat/conversationsList a user's conversations.
POST/api/v1/genies/:genie_handle/chat/conversationsCreate a new conversation.
GET/api/v1/genies/:genie_handle/chat/conversations/
:conversation_id
Get conversation details and state.
GET/api/v1/genies/:genie_handle/chat/conversations/
:conversation_id/messages
Get message history for a conversation.
POST/api/v1/genies/:genie_handle/chat/conversations/
:conversation_id/messages
Send a message and receive a streaming response.
GET/api/v1/genies/:genie_handle/chat/conversations/
:conversation_id/genie-runs/:genie_run_id
Reconnect to a message stream.
GET/api/v1/genies/:genie_handle/chat/
conversations/events
Get recent events.
POST/api/v1/genies/:genie_handle/chat/conversations/
:conversation_id/skill_approval/:call_id
Approve or reject a skill confirmation.
POST/api/v1/genies/:genie_handle/chat/
runtime_connection/:runtime_connection_attempt_id/link
Get an authentication link for a runtime connection.
POST/api/v1/genies/:genie_handle/chat/
runtime_connection/:runtime_connection_attempt_id/reject
Reject a runtime connection request.
POST/api/v1/genies/:genie_handle/chat/conversations/
:conversation_id/upload
Upload a file for attachment to a message.

List conversations

Retrieve a list of the authenticated user's conversations. Results are listed by the created_at timestamp in descending order.

bash
GET /api/v1/genies/:genie_handle/chat/conversations

URL parameters

NameTypeDescription
genie_handlestring
required
Genie handle.

Query parameters

NameTypeDescription
limitinteger
optional
Number of conversations to return. Defaults to 50.
cursorstring
optional
Pagination cursor from a previous response.

Response

NameTypeDescription
list[]arrayList of conversations.
list[].conversation_idstringConversation ID.
list[].topicstringConversation topic.
list[].last_updated_atstringTimestamp of the last update.
list[].created_atstringTimestamp when the conversation was created.
total_countintegerTotal number of conversations.
cursorstringCursor for the next page of results.

Create a conversation

Create a new conversation with the genie.

bash
POST /api/v1/genies/:genie_handle/chat/conversations

URL parameters

NameTypeDescription
genie_handlestring
required
Genie handle.

Response

NameTypeDescription
conversation_idstringID of the new conversation.

Get a conversation

Retrieve the details and current state of a conversation.

bash
GET /api/v1/genies/:genie_handle/chat/conversations/:conversation_id

URL parameters

NameTypeDescription
genie_handlestring
required
Genie handle.
conversation_idstring
required
Conversation ID.

Response

NameTypeDescription
updated_atstringTimestamp of the last update.
statestringCurrent 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_eventobjectThe 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.

bash
GET /api/v1/genies/:genie_handle/chat/conversations/:conversation_id/messages

URL parameters

NameTypeDescription
genie_handlestring
required
Genie handle.
conversation_idstring
required
Conversation ID.

Query parameters

NameTypeDescription
cursorstring
optional
Pagination cursor from a previous response.
limitinteger
optional
Number of messages to return. Defaults to 100.

Response

NameTypeDescription
conversation_idstringConversation ID.
messages[]arrayList of messages.
messages[].message_idstringMessage ID.
messages[].sourcestringMessage origin. One of user or genie.
messages[].contentstringText content of the message.
messages[].genie_run_idstringID of the request/response cycle the message belongs to.
messages[].created_atstringTimestamp of the message in RFC3339 format.
total_countintegerTotal number of messages in the conversation.
cursorstringCursor 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.

bash
POST /api/v1/genies/:genie_handle/chat/conversations/:conversation_id/messages

URL parameters

NameTypeDescription
genie_handlestring
required
Genie handle.
conversation_idstring
required
Conversation ID.

Payload

NameTypeDescription
messagestring
required
The user's message. Maximum 12 KB.
file_idstring
optional
ID of a previously uploaded file to attach to this message.
streamboolean
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:

NameTypeDescription
conversation_idstringConversation ID.
genie_run_idstringID 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.

bash
GET /api/v1/genies/:genie_handle/chat/conversations/:conversation_id/genie-runs/:genie_run_id

URL parameters

NameTypeDescription
genie_handlestring
required
Genie handle.
conversation_idstring
required
Conversation ID.
genie_run_idstring
required
Genie run ID, as returned by Send a message.

Headers

NameTypeDescription
Last-Event-IDstring
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.

bash
GET /api/v1/genies/:genie_handle/chat/conversations/events

URL parameters

NameTypeDescription
genie_handlestring
required
Genie handle.

Query parameters

NameTypeDescription
since_created_atstring
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_idstring
optional
Filter events by conversation ID.
limitinteger
optional
Page size. Defaults to 100.

Response

NameTypeDescription
events[]arrayArray of events, ordered oldest first.
events[].conversation_idstringConversation ID.
events[].genie_handlestringGenie handle (string identifier, for example gin-AaDX9axF-CNtgQn-B6).
events[].genie_run_idstringID of the genie run this event belongs to.
events[].typestringEvent type.
events[].event_idstringUUIDv7 identifying this event.
events[].seq_numintegerPer-genie-run monotonic sequence number.
events[].created_atstringGateway-side timestamp when the event was persisted, in RFC3339 format.
next_since_created_atstringContinuation 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.

bash
POST /api/v1/genies/:genie_handle/chat/conversations/:conversation_id/skill_approval/:call_id

URL parameters

NameTypeDescription
genie_handlestring
required
Genie handle.
conversation_idstring
required
Conversation ID.
call_idstring
required
Call ID from the skill.confirmation_required event.

Payload

NameTypeDescription
resolutionstring
required
One of approved or rejected.
rejection_reasonstring
optional
Reason for rejection. Only used when resolution is rejected.

Get an authentication link for a runtime connection. Use this when the genie emits a runtime_connection.auth_required event.

bash
POST /api/v1/genies/:genie_handle/chat/runtime_connection/:runtime_connection_attempt_id/link
NameTypeDescription
genie_handlestring
required
Genie handle.
runtime_connection_attempt_idstring
required
Server-generated ID for the pending runtime-connection authorization attempt, as delivered in the runtime_connection.auth_required SSE event.
NameTypeDescription
statusstringOne of auth_required or authorized. When authorized, the connection is already complete and no user action is needed.
auth_link.urlstringAuthentication URL to present to the user. Only present when status is auth_required.
auth_link.expires_atstringExpiration time of the authentication URL in RFC3339 format. Only present when status is auth_required.
auth_link.connector_namestringHuman-readable connector label (for example, Salesforce, Google Drive). Only present when status is auth_required.

Reject a runtime connection

Reject a runtime connection request.

bash
POST /api/v1/genies/:genie_handle/chat/runtime_connection/:runtime_connection_attempt_id/reject

URL parameters

NameTypeDescription
genie_handlestring
required
Genie handle.
runtime_connection_attempt_idstring
required
Server-generated ID for the pending runtime-connection authorization attempt, as delivered in the runtime_connection.auth_required SSE event.

Payload

NameTypeDescription
reasonstring
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.

bash
POST /api/v1/genies/:genie_handle/chat/conversations/:conversation_id/upload

URL parameters

NameTypeDescription
genie_handlestring
required
Genie handle.
conversation_idstring
required
Conversation ID.

Payload

NameTypeDescription
filefile
required
File data (multipart/form-data). Maximum size is 20 MB.

Response

NameTypeDescription
file_idstringID 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:

FieldTypeDescription
conversation_idstringConversation ID.
genie_handlestringGenie handle (string identifier, for example gin-AaDX9axF-CNtgQn-B6).
genie_run_idstringID of the genie run this event belongs to.
typestringEvent type.
event_idstringUUIDv7 identifying this event.
seq_numintegerPer-genie-run monotonic sequence number.
created_atstringGateway-side timestamp when the event was persisted, in RFC3339 format.

Event types

EventAdditional fieldsDescription
processing
.started
Genie has begun processing the request.
processing
.finished
Genie has completed processing.
agent
.message
  • message
The genie's response message.
skill
.running
  • skill_name
  • skill_id
A skill has started executing.
skill
.completed
  • skill_name
  • skill_id
A skill completed successfully.
skill
.failed
  • skill_name
  • skill_id
  • error
A skill execution failed.
skill
.stopped
  • skill_name
  • skill_id
Terminal variant emitted by some runtime versions in place of skill.completed. Treat it as a successful completion.
skill
.confirmation
_required
  • call_id
  • skill_name
  • skill_id
  • skill_parameters
  • skill_
    parameter_schema
A skill requires user confirmation before executing. Use call_id with Approve or reject a skill.
runtime_connection
.auth_required
  • runtime_connection
    _attempt_id
  • auth_link
A skill requires the user to authenticate a connection. Use runtime_connection
_attempt_id with Get a runtime connection link.
system.pingKeep-alive heartbeat, sent roughly every 30 seconds during long-running or paused turns. Ignore it.
system
.stream_interrupted
  • genie_run_id
  • last_seq_num
  • reason
  • retry_after_ms
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 409 conflict. Detach the existing client first to re-point it.

Last updated: