Skip to content

Overview

To request access to the Fin Agent API, go to Settings > Integrations > Fin Agent API in your workspace and tell us about your use case. Once access is granted, Fin Agent API appears under Fin AI Agent > Deploy.

API version

The Fin Agent API is available on API version 2.14 and above. Make sure your integration uses version 2.14 or later.

Overview

Fin can be accessed programmatically via an API. You call Fin from your own agent, and Fin notifies your application of its status and responses through a set of events, delivered via webhooks.

Once Fin is initialized, it works through a series of statuses — thinking when it starts running a procedure and replying as it sends each part of an answer. Fin hands control back to your agent when it reaches awaiting_user_reply (it needs the user's next message to continue) or complete (it has finished). Allow Fin to continue uninterrupted until it returns control with one of these statuses.

Two ways to use Fin Agent API

You can use Fin in one of two ways, depending on how much control you want to keep over the conversation.

Use Fin as a tool — Your agent stays in control of the turn. It disambiguates the question and decides whether to route to Fin. If it does, it can call Fin capabilities — ask, escalate, or run a procedure — and use the result. Best when you have your own orchestration logic and want Fin to deliver contained answers.

Use Fin Agent API as a tool

Start a conversation with Fin — Your agent hands the whole turn to Fin. Fin handles the request using its own features: content, guidance, escalations, and procedures. It produces an answer and passes control back. Best when you want Fin to own the resolution end-to-end.

Start a conversation with Fin

Use Fin Agent API as a tool

If you are driving Fin from your own agent, the orchestration endpoints let your agent decide what Fin should do and when:

ENDPOINT WHEN TO USE
POST /fin/capabilitiesDiscover, per user, which procedures and actions are available — call this first to decide what to do.
POST /fin/askGet a single, self-contained answer to one question. Fin will not ask follow-ups, run procedures, or decide to hand over to a human itself. Escalation rules can still match.
POST /fin/procedures/{procedure_id}/runDeterministically run a specific procedure.
POST /fin/replySend a follow-up message to an existing conversation — typically when Fin needs more information to complete an action.
POST /fin/escalateHand the conversation off to a human teammate.

Discover capabilities

POST /fin/capabilities returns a per-user list of what Fin can do, so your agent can decide which endpoint to call. The list is audience-matched to the user you pass.

Request Payload

KEY TYPE Required DESCRIPTION
userobjecttrueThe user to list capabilities for. If no user exists for the id, one is created from the supplied details.
user.idstringtrueThe external reference for the user (maps to user_id on the Intercom User object).
user.namestringfalseThe user's name. Applied when the user is created.
user.emailstringfalseThe user's email. Applied when the user is created, and updates the existing user's email if it differs. See User Handling.
user.attributesobjectfalseUp to 10 custom attributes. Merged into the user whether it is created or already exists. See User Handling.

Example request

curl -X POST https://api.intercom.io/fin/capabilities \
  -H "Authorization: Bearer <ACCESS_TOKEN>" \
  -H "Intercom-Version: 2.16" \
  -H "Content-Type: application/json" \
  -d '{ "user": { "id": "123456" } }'

Example response

{
  "version": "2.16",
  "capabilities": [
    {
      "name": "run_procedure_reset_password_12345",
      "description": "<untrusted-content>\nWalk the user through resetting their password.\n</untrusted-content>",
      "inputSchema": {
        "type": "object",
        "properties": { "conversation_id": { "type": "string" }, "user": { "type": "object" }, "message": { "type": "object" }, "conversation_metadata": { "type": "object" } },
        "required": ["conversation_id", "user"]
      },
      "endpoint": "/fin/procedures/12345/run",
      "method": "POST"
    },
    {
      "name": "reply",
      "description": "Send a user reply into an existing Fin conversation and get Fin's next turn. ...",
      "inputSchema": { "type": "object", "properties": { "conversation_id": { "type": "string" }, "message": { "type": "object" }, "user": { "type": "object" } }, "required": ["conversation_id", "message", "user"] },
      "endpoint": "/fin/reply",
      "method": "POST"
    }
  ]
}

Each capability is a tool definition: name, description, inputSchema (a JSON Schema for the request body to send), plus the endpoint and method to call. Procedures are named run_procedure_<title_slug>_<id>, with the slug cut to 80 characters, or run_procedure_<id> when the title gives no usable slug (for example a blank or non-Latin title), and a procedure's description is wrapped in <untrusted-content> tags. The static actions are named reply, ask and escalate. The example above is abbreviated: inputSchema is shortened, and ask and escalate are omitted.

Ask Fin

POST /fin/ask gets a single, self-contained answer to one question. It is non-conversational: Fin will not ask follow-up questions, run procedures, or decide to hand over to a human itself. Escalation rules can still match, and the conversation can still be routed to a team or finish without resolution. You can also escalate one yourself with /fin/escalate — the ask conversation is already closed after its one-shot answer, and escalation leaves it closed while routing the handoff separately.

Request Payload

KEY TYPE Required DESCRIPTION
conversation_idstringtrueYour external conversation ID. Fin creates a conversation for this ID. If a conversation already exists for it, use /fin/reply instead.
messageobjecttrueThe user's question.
message.authorstringtrueWho sent the message. One of user, agent, or fin.
message.bodystringtrueThe text of the message.
message.timestampstringfalseWhen the message was sent. Send timestamp or timestamp_ms; at least one is required, and without one the request fails with a 422. If you send both, timestamp_ms is used. It is used to deduplicate messages sent within a 5-minute window.
userUsertrueThe user asking the question. Requires id.
conversation_metadataobjectfalseOptional history (up to 10 prior messages) and attributes to give Fin context. Each history item needs body, an author of user or agent, and a timestamp or timestamp_ms; if any item is invalid, Fin ignores the whole history.
attachmentsarrayfalseUp to 10 attachments to include with the message.
environmentstringfalseThe identifier of the environment to start the conversation in. Only Fin for Platforms workspaces use it; other workspaces ignore it. Use the same value as messenger_environment in your Messenger installation. Defaults to the workspace's default environment. On a Fin for Platforms workspace, an identifier that matches no environment returns a 422.
response_formatstringfalseThe format of message.body in Fin's fin_replied events: html (the default) or markdown. If Fin cannot convert a reply to Markdown it sends html instead, so render each reply according to message.format. Requesting markdown on a workspace where it is not enabled returns a 403. The format applies per request, so repeat it on each /fin/reply; a request that omits it gets html.

Example request

curl -X POST https://api.intercom.io/fin/ask \
  -H "Authorization: Bearer <ACCESS_TOKEN>" \
  -H "Intercom-Version: 2.16" \
  -H "Content-Type: application/json" \
  -d '{
    "conversation_id": "ext-123",
    "message": { "author": "user", "body": "How do I reset my password?", "timestamp": "2025-01-24T10:01:20.000Z" },
    "user": { "id": "123456", "name": "John Doe", "email": "john.doe@example.com" }
  }'

Response

KEY TYPE DESCRIPTION
conversation_idStringThe external ID of the conversation.
user_idStringThe ID of the user.
statusEnum<String>Fin's status when the request was accepted, always thinking. Later statuses arrive as events.
created_at_msStringThe timestamp the response was created at, with millisecond precision.
errorsobjectOptional. Per-field errors for non-fatal issues, such as user attributes that could not be applied.

Example response

{
  "conversation_id": "ext-123",
  "user_id": "user-456",
  "status": "thinking",
  "created_at_ms": "2025-01-24T10:00:00.123Z"
}

Fin's answer arrives asynchronously via the fin_replied event. The conversation ends with a complete status — there is no awaiting_user_reply cycle.

Reply to Fin

Endpoint: /fin/reply

Use /fin/reply to send a follow-up message in an existing conversation. When Fin needs more information to complete an action, it sets the conversation to awaiting_user_reply — send the user's response so Fin can continue.

This endpoint requires user.id, the same as the other orchestration endpoints.

Request & Response

Request Payload
KEY TYPE Required DESCRIPTION
messageMessagetrueThe user's message. Requires author, body, and a timestamp or timestamp_ms (if you send both, timestamp_ms is used). Without a timestamp the request fails with a 422.
conversation_idStringtrueThe ID of the conversation.
userObject<User>trueA user object that identifies the user replying to Fin.
attachmentsArray<Attachment>falseAn array of attachments to include with the message. Note: Maximum of 10 attachments.
response_formatStringfalseThe format of message.body in Fin's fin_replied events: html (the default) or markdown. If Fin cannot convert a reply to Markdown it sends html instead, so render each reply according to message.format. Requesting markdown on a workspace where it is not enabled returns a 403. The format applies per request, so repeat it on each /fin/reply; a request that omits it gets html.

Request Payload Example

{
  "message": {
    "author": "user",
    "body": "Here's the information you requested.",
    "timestamp": "2025-01-24T09:01:00.000Z"
  },
  "conversation_id": "ext-123",
  "user": {
    "id": "123456",
    "name": "John Doe",
    "email": "john.doe@example.com",
    "attributes": {
      "plan_type": "Pro",
      "subscription_status": "active"
    }
  },
  "attachments": [
    {
      "type": "url",
      "url": "https://example.com/invoice.pdf"
    }
  ]
}
Response
KEY TYPE DESCRIPTION
conversation_idStringThe ID of the conversation.
user_idStringThe ID of the user.
statusEnum<String>Fin's status.
created_at_msStringThe timestamp the response was created at, with millisecond precision. Example: 2025-01-24T10:00:00.123Z
errorsObjectOptional. Contains error details if any user attribute updates failed.
errors.user.attributesHash<String,String>Optional. Map of user attribute names to error messages.

Response Example

{
  "conversation_id": "ext-123",
  "user_id": "user-456",
  "status": "thinking",
  "created_at_ms": "2025-01-24T10:00:00.123Z",
  "errors": {
    "user": {
      "attributes": {
        "invalid_attr": "User attribute 'invalid_attr' does not exist"
      }
    }
  }
}

Run a procedure

POST /fin/procedures/{procedure_id}/run deterministically runs a specific procedure on a new conversation. Calling it guarantees that procedure runs, or fails with a 422, for example when the procedure doesn't exist, isn't live, isn't available on the Fin Agent API channel or doesn't match the user's audience, when no Fin profile is configured to handle the conversation, or when conversation_id is already in use.

Request Payload

KEY TYPE Required DESCRIPTION
procedure_id (path)stringtrueThe ID of the procedure to run.
conversation_idstringtrueYour external conversation ID. Fin creates a conversation for this ID. If a conversation already exists for it, use /fin/reply instead.
userUsertrueThe user the procedure runs for. Requires id.
messageobjectfalseAn optional trigger message. If provided, body is required. author (user, agent or fin) is accepted but has no effect: the message is always posted as an operator message.
conversation_metadataobjectfalseOptional conversation attributes (no history).
environmentstringfalseThe identifier of the environment to start the conversation in. Only Fin for Platforms workspaces use it; other workspaces ignore it. Use the same value as messenger_environment in your Messenger installation. Defaults to the workspace's default environment. On a Fin for Platforms workspace, an identifier that matches no environment returns a 422.

Example request

curl -X POST https://api.intercom.io/fin/procedures/12345/run \
  -H "Authorization: Bearer <ACCESS_TOKEN>" \
  -H "Intercom-Version: 2.16" \
  -H "Content-Type: application/json" \
  -d '{
    "conversation_id": "ext-123",
    "user": { "id": "123456", "name": "John Doe", "email": "john.doe@example.com" }
  }'

Response

KEY TYPE DESCRIPTION
conversation_idStringThe external ID of the conversation.
user_idStringThe ID of the user.
procedure_idStringThe ID of the procedure that was run.
statusEnum<String>Fin's status when the request was accepted, always thinking. Later statuses arrive as events.
created_at_msStringThe timestamp the response was created at, with millisecond precision.
errorsobjectOptional. Per-field errors for non-fatal issues, such as user attributes that could not be applied.

Example response

{
  "conversation_id": "ext-123",
  "user_id": "user-456",
  "procedure_id": "12345",
  "status": "thinking",
  "created_at_ms": "2025-01-24T10:00:00.123Z"
}

If the procedure pauses for input, the conversation status becomes awaiting_user_reply — send the user's response with Reply to Fin.

Escalate to a human

POST /fin/escalate hands a conversation off to a human teammate. If you use the Intercom Helpdesk, the handoff lands in your team inbox. Provide either conversation_id or user:

  • conversation_id — escalate an existing agent conversation, including one started with /fin/ask. On the Intercom Helpdesk, Fin by default summarises the conversation and opens a new Helpdesk conversation that carries the summary as an internal note. You can change this default with an escalation Operator Workflow (see below). Escalation closes the original agent conversation if it is open, including on Fin for Platforms.
  • user — escalate on behalf of a user with no prior agent conversation. Fin creates an agent conversation for the escalation and closes it once the handoff completes. On the Intercom Helpdesk, the teammate works in a separate new Helpdesk conversation. Not supported on Fin for Platforms — see below.

In both cases, pass the optional context to give the receiving teammate background your orchestrating agent has and Fin does not. On the Intercom Helpdesk, it appears above the summary in the internal note of the new conversation the teammate picks up. It is never shown to the end user.

On Fin for Platforms, context is not surfaced: escalation hands off to your own agent desk rather than creating an Intercom Helpdesk conversation, and the escalation events your platform receives do not carry it.

Escalating an existing conversation also sets its AI Agent resolution state to escalated, readable as ai_agent.resolution_state on the Conversations API. This is a resolution state, not a billable resolution.

Customise escalation behaviour

By default, an escalation that no workflow handles opens a new Helpdesk conversation on the Intercom Helpdesk. To change what happens on escalate, configure an Operator Workflow triggered by Fin Agent API escalations. When a workflow matches, it takes over the handoff instead of the default, so no new conversation is opened unless the workflow opens one. The original conversation is closed again once the workflow's first steps have run, so a reassignment or reply from those steps leaves it closed.

On Fin for Platforms, conversation_id is required

There is no Intercom Helpdesk on Fin for Platforms, so there is no Helpdesk conversation to create — user is not supported and is rejected. Escalate an existing Fin conversation with conversation_id instead. No Helpdesk conversation is created for a teammate: you're notified with the escalated status and hand the conversation to a human on your own platform.

Request Payload

KEY TYPE Required DESCRIPTION
conversation_idstringfalseThe conversation to escalate. Provide this or user. Required on Fin for Platforms, where user is not supported.
userUserfalseThe user to escalate on behalf of, creating a new conversation. Requires id. Provide this or conversation_id. Not supported on Fin for Platforms.
contextstringfalseBackground for the receiving teammate, valid with either conversation_id or user. On the Intercom Helpdesk, it appears above the summary in the internal note of the new conversation the teammate picks up, and is never shown to the end user. Up to 10,000 characters. Not surfaced on Fin for Platforms. Avoid including credentials or unnecessary personal data — it is visible to any teammate with access to the conversation.
user is optional for escalate

Unlike the other orchestration endpoints, user is optional here. Escalate accepts either conversation_id (to escalate an existing agent conversation) or user (to escalate on behalf of a user with no prior conversation) — exactly one of the two is required. On Fin for Platforms only conversation_id is accepted.

Response

KEY TYPE DESCRIPTION
conversation_idStringYour external conversation ID. When you escalate an existing conversation by conversation_id, this echoes the ID you sent. When you escalate on behalf of a user, Fin creates an agent conversation for the escalation and generates this ID for it (in the form escalate-<hex>).
statusEnum<String>The resulting status of the conversation. Value: escalated
intercom_conversation_idStringOptional. The internal Intercom ID of the agent conversation created for the escalation. Returned only when escalating on behalf of a user. Fin closes this conversation once the handoff completes; the teammate works in a separate Helpdesk conversation.

You are notified over webhooks with an escalated status followed by complete. complete signals that Fin is done. By then the original agent conversation has been closed. An escalation Operator Workflow may act on it afterwards, but it is closed again once the workflow's first steps have run, so only steps that run later (for example after a wait) can reopen it. On the Intercom Helpdesk the handoff continues in the new conversation routed to your team inbox, which stays open there, and on Fin for Platforms you hand the conversation to a human on your own platform.

Example request

curl -X POST https://api.intercom.io/fin/escalate \
  -H "Authorization: Bearer <ACCESS_TOKEN>" \
  -H "Intercom-Version: 2.16" \
  -H "Content-Type: application/json" \
  -d '{ "conversation_id": "ext-123", "context": "Customer is requesting a refund." }'

Example response

{
  "conversation_id": "ext-123",
  "status": "escalated"
}

When escalating on behalf of a user, Fin creates an agent conversation for the escalation and closes it once the handoff completes. The response returns the conversation_id Fin generated for it, along with its intercom_conversation_id:

{
  "conversation_id": "escalate-1a2b3c4d5e6f7a8b",
  "intercom_conversation_id": "987654321",
  "status": "escalated"
}

Pricing

Pricing

Pricing for the Fin Agent API is being finalised and is subject to change.

Conversations you drive through the Fin Agent API are billed the same way as any other Fin conversation — on outcomes. You're charged at most once per conversation, and only when Fin reaches a billable outcome.

A conversation that Fin answers through the API is counted as a resolution, which is a billable outcome. Conversations that don't reach a billable outcome aren't charged — for example, handing a conversation to a human with /fin/escalate without a resolution, or discovering what Fin can do with /fin/capabilities, which doesn't create a conversation.

For how outcomes are calculated and billed, see Understanding Fin outcomes.

Start a conversation with Fin

Endpoint: /fin/start

Initialise Fin by passing it the user's message along with some conversation history and user details. These additional pieces of context will be used by Fin to provide a better and more contextual answer to the user.

Request & Response

Request Payload

KEY TYPE Required DESCRIPTION
messageMessagetrueThe message that the user is sending to Fin. Requires author, body, and a timestamp or timestamp_ms (if you send both, timestamp_ms is used). Without a timestamp the request fails with a 422.
conversation_idStringtrueThe ID of the conversation that is calling Fin via this API.
userObject<User>trueA user object that identifies the user making the query.
attachmentsArray<Attachment>falseAn array of attachments to include with the message. Note: Maximum of 10 attachments.
conversation_metadataObjectfalseMetadata about the conversation, including history and attributes.
conversation_metadata.historyArray<Message>falseAn array of previous messages of the conversation before Fin is initialised. This data provides context to Fin on the history of the conversation and helps generate a better answer. Note: Limit to the last 10 messages. Each item needs body, an author of user or agent, and a timestamp or timestamp_ms; if any item is invalid, Fin ignores the whole history.
conversation_metadata.attributesHash<String,Object>falseA Hash of attributes associated with the conversation. These attributes can be used by Fin to provide more contextual responses. Note: Limit to 20 attributes.
environmentStringfalseThe identifier of the environment to start the conversation in. Only Fin for Platforms workspaces use it; other workspaces ignore it. Use the same value as messenger_environment in your Messenger installation. Defaults to the workspace's default environment. On a Fin for Platforms workspace, an identifier that matches no environment returns a 422.
response_formatStringfalseThe format of message.body in Fin's fin_replied events: html (the default) or markdown. If Fin cannot convert a reply to Markdown it sends html instead, so render each reply according to message.format. Requesting markdown on a workspace where it is not enabled returns a 403. The format applies per request, so repeat it on each /fin/reply; a request that omits it gets html.

Request Payload Example

{
  "conversation_id": "ext-123",
  "message": {
    "author": "user",
    "body": "How can I see my account details?",
    "timestamp": "2025-01-24T10:01:20.000Z"
  },
  "user": {
    "id": "123456",
    "name": "John Doe",
    "email": "john.doe@example.com",
    "attributes": {
      "plan_type": "Pro",
      "subscription_status": "active"
    }
  },
  "attachments": [
    {
      "type": "url",
      "url": "https://example.com/document.pdf"
    },
    {
      "type": "file",
      "name": "screenshot.png",
      "content_type": "image/png",
      "data": "<base64 encoded file data>"
    }
  ],
  "conversation_metadata": {
    "history": [
      {
        "author": "user",
        "body": "I need help",
        "timestamp": "2025-01-24T10:00:01Z"
      },
      {
        "author": "agent",
        "body": "What do you need help with?",
        "timestamp": "2025-01-24T10:01:00Z"
      }
    ],
    "attributes": {
      "priority_level": "high",
      "department": "sales"
    }
  }
}

Response

KEY TYPE DESCRIPTION
conversation_idStringThe ID of the conversation.
user_idStringThe ID of the user.
statusEnum<String>Fin's status.
created_at_msStringThe timestamp the response was created at, with millisecond precision. Example: 2025-01-24T10:00:00.123Z
errorsObjectOptional. Contains error details if any user or conversation attribute updates failed.
errors.user.attributesHash<String,String>Optional. Map of user attribute names to error messages.
errors.conversation.attributesHash<String,String>Optional. Map of conversation attribute names to error messages.

Response Example

{
  "conversation_id": "ext-123",
  "user_id": "user-456",
  "status": "thinking",
  "created_at_ms": "2025-01-24T10:00:00.123Z",
  "errors": {
    "user": {
      "attributes": {
        "invalid_attr": "User attribute 'invalid_attr' does not exist"
      }
    },
    "conversation": {
      "attributes": {
        "bad_attr": "Conversation attribute 'bad_attr' does not exist",
        "priority_level": "'1234' is not a valid value for attribute 'priority_level' of type 'string'"
      }
    }
  }
}

Pricing

Conversations you drive through /fin/start are billed on outcomes. You're charged at most once per conversation, and only when Fin reaches a billable outcome such as a resolution.

For how outcomes are calculated and billed, see Understanding Fin outcomes.

Submit a CSAT rating

POST /fin/csat records a customer's satisfaction rating for a conversation, with an optional free-text remark.

Fin asks for a rating when a conversation reaches a point where CSAT collection is enabled — this reuses the CSAT settings on your Fin workflow, so you control when a rating is requested from Intercom, not through this API. When Fin asks, it fires a csat_requested event over webhooks carrying the rating options to show the user. Present those options, then submit the user's choice with this endpoint.

POST /fin/csat and the csat_requested event are available on API version 2.14 and above.

Request Payload

KEY TYPE Required DESCRIPTION
conversation_idStringtrueYour external conversation ID — the same ID you started the conversation with and the one echoed on the csat_requested event.
ratingEnum<String>trueThe rating the user selected — one of the key values from the csat_requested event's options. Values: terrible bad ok good amazing
remarkStringfalseAn optional free-text comment the user left alongside the rating.

Response

KEY TYPE DESCRIPTION
conversation_idStringThe external ID of the rated conversation.
ratingEnum<String>The rating now recorded on the conversation. Values: terrible bad ok good amazing
statusEnum<String>The result of the submission. Value: rated

Submitting the same rating again (with no new remark) is a no-op that succeeds even after the rating window has closed, so an at-least-once client can safely retry. Submitting a different rating updates the stored rating, but only while the rating window is still open. A first remark can be added to an already-rated survey — even after the window has closed — but once a remark has been recorded, the rating is permanently locked and can no longer be changed.

Example request

curl -X POST https://api.intercom.io/fin/csat \
  -H "Authorization: Bearer <ACCESS_TOKEN>" \
  -H "Intercom-Version: 2.16" \
  -H "Content-Type: application/json" \
  -d '{
    "conversation_id": "ext-123",
    "rating": "amazing",
    "remark": "Fin solved my problem in seconds."
  }'

Example response

{
  "conversation_id": "ext-123",
  "rating": "amazing",
  "status": "rated"
}

Errors

A rating that can't be recorded returns a 422 with an errors message. Common causes:

CAUSE DESCRIPTION
Unknown conversationNo conversation exists for the given conversation_id.
No rating requestedThe conversation has no rating to record — Fin never fired a csat_requested event for it.
Rating window closedThe time window for rating this conversation has passed, per your workspace's CSAT settings.
Invalid ratingrating is not one of the supported values.
Rating lockedA remark has already been submitted for this rating, so it can no longer be changed.

Listening to Events

Fin fires events during its workflow to convey its status and deliver replies to the user. These events are delivered via Webhooks.

Webhooks

A webhook endpoint can be configured in the Fin Agent API settings. The client can then listen for Fin events sent to this webhook and handle them accordingly.

All webhook requests will include an X-Fin-Agent-API-Webhook-Signature header containing an HMAC-SHA256 signature of the request body. Validation can be done by generating a signature using the request body and the signing secret from the settings, and comparing it with the aforementioned header value.

Event Types

Status Update

Fin will report its status to the client via this event.

KEY TYPE DESCRIPTION
event_nameStringThe name of the event. Value: fin_status_updated
conversation_idStringThe ID of the conversation.
user_idStringThe ID of the user.
statusEnum<String>Fin's current status. Supported values: thinking escalated resolved complete awaiting_user_reply
reasonStringOptional. A human-readable explanation of why the conversation was escalated. Only present when status is escalated. See Escalation Reasons for possible values.
created_at_msStringThe timestamp the event was created at, with millisecond precision. Example: 2025-01-24T10:00:00.123Z

Status Update Example (Escalated)

{
  "event_name": "fin_status_updated",
  "conversation_id": "ext-123",
  "user_id": "7891",
  "status": "escalated",
  "reason": "Escalation requested by user",
  "created_at_ms": "2025-01-24T10:00:00.123Z"
}

Status Update Example (Resolved)

{
  "event_name": "fin_status_updated",
  "conversation_id": "ext-123",
  "user_id": "7891",
  "status": "resolved",
  "created_at_ms": "2025-01-24T10:00:00.123Z"
}

Status Update Example (Awaiting User Reply)

{
  "event_name": "fin_status_updated",
  "conversation_id": "ext-123",
  "user_id": "7891",
  "status": "awaiting_user_reply",
  "created_at_ms": "2025-01-24T10:00:00.123Z"
}
Knowing when Fin's reply is done

The fin_status_updated event with status awaiting_user_reply fires once Fin's reply is done — all reply parts have been sent. Use this event to know when the user can reply.

Fin Reply

Fin will reply to a user via this event. The content of the response will be contained in the Message object. Intermediate reply events have a status of replying, indicating that more reply parts may follow. Once Fin's reply is done, a separate fin_status_updated event with status awaiting_user_reply is fired.

API version required

The replying status for intermediate fin_replied events — and the fin_status_updated event with awaiting_user_reply that fires once Fin's reply is done — require API version 2.16 and above. On version 2.15 and earlier, every fin_replied event uses awaiting_user_reply as the status and no fin_status_updated event with that status is sent. See the 2.16 changelog for details.

Once Fin finishes and the conversation reaches complete, a csat_requested survey may follow if CSAT is enabled on your Fin workflow.

KEY TYPE DESCRIPTION
event_nameStringThe name of the event. Value: fin_replied
conversation_idStringThe ID of the conversation.
user_idStringThe ID of the user.
messageMessageFin's answer to the user's query. Includes timestamp_ms for millisecond precision timing.
statusEnum<String>Fin's current status. Values: replying (intermediate reply) awaiting_user_reply (legacy; see the note above) complete (Fin's reply is final and no user reply is expected; sent for one-shot Ask Fin replies only)
message.idStringA unique identifier for this message.
message.sourcesArray<Source>Optional. Available on API version 2.14 and above. The content sources Fin used to generate this reply, in the same order as the [n] citation markers in message.body. Present only when Fin cited content. Each Source has title (String), type (Enum<String>: article or external), and url (String).
message.formatStringOptional. Only present when the request set response_format to markdown. Value: markdown, or html if Fin could not convert the reply to Markdown and sent HTML instead. When absent, message.body is HTML.
created_at_msStringThe timestamp the event was created at, with millisecond precision. Example: 2025-01-24T10:00:00.123Z

Fin Reply Example (Intermediate)

{
  "event_name": "fin_replied",
  "conversation_id": "ext-123",
  "user_id": "7891",
  "message": {
    "id": "98765",
    "author": "fin",
    "body": "<p>You can see your account details by clicking on the <em>Account</em> tab in the top right corner of the screen. [1]</p>",
    "timestamp_ms": "2025-01-24T09:01:00.456Z",
    "sources": [
      {
        "title": "Managing your account",
        "type": "article",
        "url": "https://www.example.com/help/en/articles/1-managing-your-account"
      }
    ]
  },
  "status": "replying",
  "created_at_ms": "2025-01-24T10:00:00.123Z"
}
Wait for all reply parts

After receiving one or more fin_replied events with status: replying, wait for a fin_status_updated event with status: awaiting_user_reply before prompting the user to reply. This ensures all of Fin's reply parts have been delivered.

CSAT Requested

Fin fires this event when it asks the user to rate the conversation. It carries the rating options to present; collect the user's choice and submit it with POST /fin/csat. Unlike the reply events it has no message — a rating survey is a set of options to choose from, not readable text.

KEY TYPE DESCRIPTION
event_nameStringThe name of the event. Value: csat_requested
conversation_idStringThe ID of the conversation.
user_idStringThe ID of the user.
csatObjectThe rating survey to present to the user.
csat.optionsArray<Object>The ordered rating options to show the user.
csat.options[].keyEnum<String>The stable key to send back as rating on POST /fin/csat. Values: terrible bad ok good amazing
csat.options[].emojiStringThe emoji representing this rating.
csat.options[].labelStringThe human-readable label for this rating, localized to the conversation's detected language. Distinct from key — display the label, but send back the key.
created_at_msStringThe timestamp the event was created at, with millisecond precision. Example: 2025-01-24T10:00:00.123Z

CSAT Requested Example

{
  "event_name": "csat_requested",
  "conversation_id": "ext-123",
  "user_id": "7891",
  "csat": {
    "options": [
      { "key": "terrible", "emoji": "😠", "label": "Terrible" },
      { "key": "bad", "emoji": "🙁", "label": "Bad" },
      { "key": "ok", "emoji": "😐", "label": "Okay" },
      { "key": "good", "emoji": "😃", "label": "Great" },
      { "key": "amazing", "emoji": "🤩", "label": "Amazing" }
    ]
  },
  "created_at_ms": "2025-01-24T10:00:00.123Z"
}

Fin's Statuses

As Fin works through a request it reports a series of statuses. Listen for these via Webhooks and react accordingly.

Which statuses you see depends on the endpoint. thinking is sent when Fin starts running a procedure, not on every turn. A turn ends with awaiting_user_reply when Fin has answered and is waiting for the user's next message (on API version 2.16 and above this is sent as a fin_status_updated event; on 2.15 and earlier it is the status of the fin_replied events), or with complete when Fin is done: it resolved the conversation, escalated, finished a procedure, closed the conversation after the user went inactive, or detected spam. A one-shot Ask Fin call always ends with complete and never enters awaiting_user_reply. replying accompanies each reply part. escalated is covered below.

Statuses

STATUS DESCRIPTION
thinkingFin has started running a procedure. This status is not sent on every turn. When Fin is thinking, the client should consider indicating to the user that Fin is working on an answer.
replyingFin is sending reply parts. Each fin_replied event with this status contains a part of Fin's answer. Wait for the fin_status_updated event with awaiting_user_reply before prompting the user to reply.
awaiting_user_replyFin has finished replying and is waiting for the user to respond. This status is delivered via the fin_status_updated event once Fin's reply is done, after all fin_replied events.
escalatedThe conversation has been handed off to a human. This is deterministic when you call /fin/escalate. It can also happen automatically when Fin owns the conversation — /fin/start, /fin/procedures/{procedure_id}/run, and /fin/reply on a conversation started that way, let Fin decide to escalate. Fin will not decide to escalate a one-shot /fin/ask conversation, but an escalation rule can, and you can escalate one yourself.
resolvedThe user's query has been resolved. The user has indicated to Fin that it has successfully answered their query.
completeFin has completed its workflow and the conversation is over. Control of the conversation is now back with the client. When CSAT is enabled, a csat_requested survey may follow.

Escalation Reasons

When a conversation is escalated, the escalated fin_status_updated event carries a reason field.

When you escalate deterministically with /fin/escalate, the reason is always:

REASON DESCRIPTION
Escalation requested via APIYou initiated the handoff by calling /fin/escalate.
Fin-owned and rule-based escalations

The reasons below apply to Fin-decided escalations when Fin owns the conversation — /fin/start, /fin/procedures/{procedure_id}/run, and /fin/reply on a conversation started that way. /fin/ask can report any of them, because escalation rules still apply and the conversation can still be routed to a team or finish without resolution. On an Ask, Escalation requested by user normally only follows an escalation rule match.

The following reasons may be provided:

REASON DESCRIPTION
Escalation requested by userFin decided to hand the conversation over, for example because the user asked for a human, a procedure handed off, or a custom answer handed over. It also follows an escalation rule match, as a second escalated event after the rule's reason. While a procedure is running, every escalation Fin decides on reports this reason.
Escalation rule: {rule_name}An escalation rule was triggered based on conversation attributes or conditions. The {rule_name} will be replaced with the actual name of the matched rule.
Escalation rule matchedA generic escalation rule was triggered, but no specific rule name is available.
Routed to teamThe conversation was routed to a specific team based on your routing configuration.
Conversation finished without resolutionThe conversation ended without being resolved or explicitly escalated through one of the above paths.

Note: The reason field is optional and may not be present in all escalated status update events. Legacy implementations or certain edge cases may not include this field.

Authentication

All requests to this API require an API key in the HTTP header. You can obtain an API key after completing the Fin Agent API setup.

For general authentication guidelines, see Intercom authentication.

Keep your API key secure

Your API key grants access to the Fin Agent API on behalf of your workspace. Do not share it publicly or expose it in client-side code.

User Handling

When you provide a User object to the Fin Agent API, the following behavior applies:

  • User role only: The Fin Agent API only supports the User role. Leads and Visitors are not supported.

  • User lookup: The id field is used to look up existing users in Intercom. This value maps to the user_id field on the Intercom User object.

  • User creation: If no user is found with the provided id, a new user will be created with the given parameters.

  • Email updates: If an email is provided and differs from the existing user's email, the user's email will be updated.

  • Attribute updates: If attributes are provided, they will be merged with the user's existing attributes. New attributes are added and existing attributes with the same key are overwritten.

    User data update considerations

    If your users interact with Intercom across multiple channels (e.g., Messenger and the Fin Agent API), updating the email or attributes via this API will affect the user record across all channels. Ensure that the data you provide is authoritative and consistent with your other integrations to avoid unintended changes to user data.

Best practices

Handling Fin's replies

  • fin_replied — Each event carries a part of Fin's answer as rich text (HTML) in message.body.
  • fin_status_updated — A follow-up fin_status_updated event is fired after Fin's replies to indicate the conversation has moved to awaiting_user_reply, resolved, or complete status. Use this to know once Fin's reply is done.

API version

Use a stable API version (e.g., 2.14, 2.15) for production integrations. The Preview version may include experimental changes that are modified or removed before reaching a stable release. See Update your API version for guidance on choosing and upgrading versions.

HTML in Fin replies

By default, the message.body field in fin_replied events contains server-rendered HTML. If you set response_format to markdown, it contains Markdown instead and the HTML elements below do not apply, unless Fin could not convert the reply and sent HTML instead. Render each reply according to message.format. The specific elements that appear depend on the knowledge ingested into Fin — for example, if your help center articles contain tables or code blocks, Fin may include those elements in its responses.

The following HTML elements may appear in message.body:

TagWhat it doesDetails
<p>ParagraphPrimary text container.
<h1> <h2> <h3> <h4>Headings
<b> <strong> <i> <em>Text formattingBold and italic.
<a>LinkAttributes: href, target. Also used for inline citations (see below).
<ol> <ul> <li>ListsOrdered and unordered. Can be nested.
<pre> <code>Code<pre> wraps <code> for code blocks. Inline <code> for inline snippets.
<img>ImageAttributes: src, alt, width, height. Wrapped in a <div> container.
<table> <tbody> <tr> <td> <th>TableWrapped in a <div>. Cells may have colspan, rowspan.
<div>ContainerWraps images, tables, and callout blocks.
<br>Line break
<hr>Horizontal rule
Forward compatibility

Over time we may add support for new HTML elements in message.body. This will not constitute a breaking change. We advise client code to gracefully handle unknown HTML elements when rendering Fin's responses.