> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Conversations API

> Multi-channel conversation threads and threading state

# Conversations API

Multi-channel conversation threads and threading state

**Base path:** `/api/v1/conversations`

**Endpoint count:** 26

***

### List all conversations

<Note>
  `GET /api/v1/conversations/`
</Note>

Unified omnichannel inbox view across SMS, WhatsApp, email, Instagram, Messenger, RCS, Viber, and LINE. Each conversation represents a unique contact + channel pair.

<ParamField query="cursor" type="string">
  Pagination cursor
</ParamField>

<ParamField query="limit" type="integer">
  —
</ParamField>

<ParamField query="sort" type="string">
  Sort field (prefix with - for DESC)
</ParamField>

<ParamField query="channel" type="string (enum: sms|whatsapp|email|rcs|viber|instagram|…)">
  —
</ParamField>

<ParamField query="status" type="string (enum: open|closed|pending|snoozed|active|archived|…)">
  —
</ParamField>

<ParamField query="assigned_agent" type="string">
  Filter by assigned agent ID (legacy)
</ParamField>

<ParamField query="search" type="string">
  Search by contact name, phone, or email
</ParamField>

<ParamField query="tag" type="string">
  Filter by tag (legacy, single)
</ParamField>

<ParamField query="channels" type="string">
  Comma-separated list of channels
</ParamField>

<ParamField query="statuses" type="string">
  Comma-separated list of statuses (open,closed,pending,snoozed,...)
</ParamField>

<ParamField query="tags" type="string">
  Comma-separated tag list (ANY match)
</ParamField>

<ParamField query="assignee" type="string">
  "me", "unassigned", or a user id
</ParamField>

<ParamField query="date_from" type="string">
  —
</ParamField>

<ParamField query="date_to" type="string">
  —
</ParamField>

<ParamField query="segment_id" type="string">
  —
</ParamField>

<ParamField query="unread" type="string (enum: true|false)">
  —
</ParamField>

<ParamField query="has_agent" type="string (enum: true|false)">
  —
</ParamField>

<ParamField query="has_video_room" type="string (enum: true|false)">
  —
</ParamField>

<ParamField query="sentiment" type="string (enum: positive|neutral|negative)">
  —
</ParamField>

<ParamField query="mentioned_internal" type="string (enum: true|false)">
  —
</ParamField>

<ParamField query="filters" type="string">
  JSON-encoded full filter AST (takes precedence)
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X GET "https://orbit-api.devotel.io/api/v1/conversations/" \
      -H "X-API-Key: dv_live_sk_your_key_here" 
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel/orbit-sdk'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://orbit-api.devotel.io/api/v1/conversations/', {
      method: 'GET',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
      },
    })
    console.log(await res.json())


    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    r = requests.get("https://orbit-api.devotel.io/api/v1/conversations/", headers=headers)
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("GET", "https://orbit-api.devotel.io/api/v1/conversations/", nil)
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))

    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://orbit-api.devotel.io/api/v1/conversations/')
    req = Net::HTTP::Get.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']


    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://orbit-api.devotel.io/api/v1/conversations/');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),

    ]);

    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

***

### Get a conversation

<Note>
  `GET /api/v1/conversations/{id}`
</Note>

Returns details about a specific conversation including contact info, channel, status, and assignment.

<ParamField path="id" type="string" required>
  —
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X GET "https://orbit-api.devotel.io/api/v1/conversations/{id}" \
      -H "X-API-Key: dv_live_sk_your_key_here" 
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel/orbit-sdk'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://orbit-api.devotel.io/api/v1/conversations/{id}', {
      method: 'GET',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
      },
    })
    console.log(await res.json())


    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    r = requests.get("https://orbit-api.devotel.io/api/v1/conversations/{id}", headers=headers)
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("GET", "https://orbit-api.devotel.io/api/v1/conversations/{id}", nil)
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))

    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://orbit-api.devotel.io/api/v1/conversations/{id}')
    req = Net::HTTP::Get.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']


    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://orbit-api.devotel.io/api/v1/conversations/{id}');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),

    ]);

    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

***

### Conversation activity feed

<Note>
  `GET /api/v1/conversations/{id}/activity`
</Note>

Operator state-changes — assignments, status transitions (close/reopen/snooze), tag updates, merges, handoff actions, bulk operations. Cursor-paginated newest-first. Does NOT include chat messages (use /:id/messages) or internal notes (use /inbox/:id/internal-notes).

<ParamField path="id" type="string" required>
  —
</ParamField>

<ParamField query="cursor" type="string">
  —
</ParamField>

<ParamField query="limit" type="integer">
  —
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X GET "https://orbit-api.devotel.io/api/v1/conversations/{id}/activity" \
      -H "X-API-Key: dv_live_sk_your_key_here" 
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel/orbit-sdk'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://orbit-api.devotel.io/api/v1/conversations/{id}/activity', {
      method: 'GET',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
      },
    })
    console.log(await res.json())


    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    r = requests.get("https://orbit-api.devotel.io/api/v1/conversations/{id}/activity", headers=headers)
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("GET", "https://orbit-api.devotel.io/api/v1/conversations/{id}/activity", nil)
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))

    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://orbit-api.devotel.io/api/v1/conversations/{id}/activity')
    req = Net::HTTP::Get.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']


    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://orbit-api.devotel.io/api/v1/conversations/{id}/activity');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),

    ]);

    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

***

### Read agent\_active state for a conversation

<Note>
  `GET /api/v1/conversations/{id}/agent/active`
</Note>

AGT-017: Returns { agent_active, agent_id }. The agent-runtime calls this before running the executor to check if the conversation is paused for human-in-the-loop handling. Fails open (agent\_active=true) on DB error.

<ParamField path="id" type="string" required>
  —
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X GET "https://orbit-api.devotel.io/api/v1/conversations/{id}/agent/active" \
      -H "X-API-Key: dv_live_sk_your_key_here" 
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel/orbit-sdk'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://orbit-api.devotel.io/api/v1/conversations/{id}/agent/active', {
      method: 'GET',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
      },
    })
    console.log(await res.json())


    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    r = requests.get("https://orbit-api.devotel.io/api/v1/conversations/{id}/agent/active", headers=headers)
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("GET", "https://orbit-api.devotel.io/api/v1/conversations/{id}/agent/active", nil)
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))

    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://orbit-api.devotel.io/api/v1/conversations/{id}/agent/active')
    req = Net::HTTP::Get.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']


    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://orbit-api.devotel.io/api/v1/conversations/{id}/agent/active');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),

    ]);

    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

***

### Get the full handoff packet for an in-flight AI conversation

<Note>
  `GET /api/v1/conversations/{id}/handoff/packet`
</Note>

Read-only operator-facing DTO that bundles everything the human taking over needs: executive summary, last-inbound sentiment, full tool-call timeline (what the bot tried), KB articles consulted, explicit open questions the bot is uncertain about, a suggested first reply, and the per-turn confidence trajectory. Derived from existing data — no schema change. Fails open on every slot (missing LLM, missing tool-call rows, missing messages all degrade individual fields to null/\[], never 5xx the endpoint).

<ParamField path="id" type="string" required>
  —
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X GET "https://orbit-api.devotel.io/api/v1/conversations/{id}/handoff/packet" \
      -H "X-API-Key: dv_live_sk_your_key_here" 
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel/orbit-sdk'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://orbit-api.devotel.io/api/v1/conversations/{id}/handoff/packet', {
      method: 'GET',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
      },
    })
    console.log(await res.json())


    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    r = requests.get("https://orbit-api.devotel.io/api/v1/conversations/{id}/handoff/packet", headers=headers)
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("GET", "https://orbit-api.devotel.io/api/v1/conversations/{id}/handoff/packet", nil)
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))

    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://orbit-api.devotel.io/api/v1/conversations/{id}/handoff/packet')
    req = Net::HTTP::Get.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']


    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://orbit-api.devotel.io/api/v1/conversations/{id}/handoff/packet');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),

    ]);

    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

***

### Get current queue position for an in-flight handoff

<Note>
  `GET /api/v1/conversations/{id}/handoff/queue-position`
</Note>

Returns { position, est_wait_seconds, queue_length } when the conversation is currently in the org's handoff queue (Redis sorted set inbox\_queue:\<orgId>). Returns null position when the conversation has been picked up, was never enqueued, or Redis is unavailable. The customer-facing chat widget polls this endpoint to render 'You are #N in queue, est wait Mm'.

<ParamField path="id" type="string" required>
  —
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X GET "https://orbit-api.devotel.io/api/v1/conversations/{id}/handoff/queue-position" \
      -H "X-API-Key: dv_live_sk_your_key_here" 
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel/orbit-sdk'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://orbit-api.devotel.io/api/v1/conversations/{id}/handoff/queue-position', {
      method: 'GET',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
      },
    })
    console.log(await res.json())


    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    r = requests.get("https://orbit-api.devotel.io/api/v1/conversations/{id}/handoff/queue-position", headers=headers)
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("GET", "https://orbit-api.devotel.io/api/v1/conversations/{id}/handoff/queue-position", nil)
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))

    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://orbit-api.devotel.io/api/v1/conversations/{id}/handoff/queue-position')
    req = Net::HTTP::Get.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']


    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://orbit-api.devotel.io/api/v1/conversations/{id}/handoff/queue-position');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),

    ]);

    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

***

### List linked conversations

<Note>
  `GET /api/v1/conversations/{id}/links`
</Note>

Return every conversation linked to this one along with a denormalised preview (channel, status, last\_message\_at, contact\_name, last\_message) and the link metadata (id, type, note, created\_by, created\_at). Directional link types are surfaced from the queried conversation's perspective — e.g. if A is stored as `parent_of` B, then GET /A/links returns `parent_of` and GET /B/links returns `child_of` for the same row.

<ParamField path="id" type="string" required>
  —
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X GET "https://orbit-api.devotel.io/api/v1/conversations/{id}/links" \
      -H "X-API-Key: dv_live_sk_your_key_here" 
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel/orbit-sdk'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://orbit-api.devotel.io/api/v1/conversations/{id}/links', {
      method: 'GET',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
      },
    })
    console.log(await res.json())


    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    r = requests.get("https://orbit-api.devotel.io/api/v1/conversations/{id}/links", headers=headers)
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("GET", "https://orbit-api.devotel.io/api/v1/conversations/{id}/links", nil)
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))

    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://orbit-api.devotel.io/api/v1/conversations/{id}/links')
    req = Net::HTTP::Get.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']


    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://orbit-api.devotel.io/api/v1/conversations/{id}/links');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),

    ]);

    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

***

### Get conversation messages

<Note>
  `GET /api/v1/conversations/{id}/messages`
</Note>

Full thread of inbound + outbound messages for a conversation, paginated with cursor.

<ParamField path="id" type="string" required>
  —
</ParamField>

<ParamField query="cursor" type="string">
  —
</ParamField>

<ParamField query="limit" type="integer">
  —
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X GET "https://orbit-api.devotel.io/api/v1/conversations/{id}/messages" \
      -H "X-API-Key: dv_live_sk_your_key_here" 
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel/orbit-sdk'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://orbit-api.devotel.io/api/v1/conversations/{id}/messages', {
      method: 'GET',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
      },
    })
    console.log(await res.json())


    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    r = requests.get("https://orbit-api.devotel.io/api/v1/conversations/{id}/messages", headers=headers)
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("GET", "https://orbit-api.devotel.io/api/v1/conversations/{id}/messages", nil)
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))

    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://orbit-api.devotel.io/api/v1/conversations/{id}/messages')
    req = Net::HTTP::Get.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']


    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://orbit-api.devotel.io/api/v1/conversations/{id}/messages');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),

    ]);

    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

***

### Per-message sentiment timeline

<Note>
  `GET /api/v1/conversations/{id}/sentiment-timeline`
</Note>

Chronologically-ordered (oldest → newest) sentiment trajectory across the messages in this conversation. Returns `[{message_id, ts, sentiment_score, sentiment_label}]` for every analyzed message (sentiment\_score IS NOT NULL). The FE renders this as a sparkline strip at the top of the conversation detail with per-message hover. Unanalyzed messages are skipped; `scheduler-sentiment-backfill` (5-min cadence in webhook-worker) populates the columns for messages with NULL sentiment\_score. Empty array when no messages have been analyzed yet — FE renders an `Analyzing…` empty state.

<ParamField path="id" type="string" required>
  —
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X GET "https://orbit-api.devotel.io/api/v1/conversations/{id}/sentiment-timeline" \
      -H "X-API-Key: dv_live_sk_your_key_here" 
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel/orbit-sdk'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://orbit-api.devotel.io/api/v1/conversations/{id}/sentiment-timeline', {
      method: 'GET',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
      },
    })
    console.log(await res.json())


    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    r = requests.get("https://orbit-api.devotel.io/api/v1/conversations/{id}/sentiment-timeline", headers=headers)
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("GET", "https://orbit-api.devotel.io/api/v1/conversations/{id}/sentiment-timeline", nil)
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))

    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://orbit-api.devotel.io/api/v1/conversations/{id}/sentiment-timeline')
    req = Net::HTTP::Get.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']


    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://orbit-api.devotel.io/api/v1/conversations/{id}/sentiment-timeline');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),

    ]);

    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

***

### List distinct conversation tags for the tenant

<Note>
  `GET /api/v1/conversations/tags/distinct`
</Note>

Returns the complete tag vocabulary in use across the tenant's conversations table, with usage counts. Used by the inbox UI to populate the tag-chip rail and the advanced-filter tag dropdown. Tenant-scoped; caps at 200 tags; results cached server-side for 60s and invalidated on every tag update.

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X GET "https://orbit-api.devotel.io/api/v1/conversations/tags/distinct" \
      -H "X-API-Key: dv_live_sk_your_key_here" 
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel/orbit-sdk'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://orbit-api.devotel.io/api/v1/conversations/tags/distinct', {
      method: 'GET',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
      },
    })
    console.log(await res.json())


    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    r = requests.get("https://orbit-api.devotel.io/api/v1/conversations/tags/distinct", headers=headers)
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("GET", "https://orbit-api.devotel.io/api/v1/conversations/tags/distinct", nil)
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))

    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://orbit-api.devotel.io/api/v1/conversations/tags/distinct')
    req = Net::HTTP::Get.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']


    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://orbit-api.devotel.io/api/v1/conversations/tags/distinct');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),

    ]);

    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

***

### Resume AI agent after human-in-the-loop handoff

<Note>
  `POST /api/v1/conversations/{id}/agent/resume`
</Note>

AGT-017: Flips agent\_active=TRUE so the AI agent processes the next inbound message. Called from the inbox 'Resume AI' button when the conversation's agent\_active is false. Idempotent — already-active conversations return 200 with already\_active=true.

<ParamField path="id" type="string" required>
  —
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X POST "https://orbit-api.devotel.io/api/v1/conversations/{id}/agent/resume" \
      -H "X-API-Key: dv_live_sk_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{}'
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel/orbit-sdk'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://orbit-api.devotel.io/api/v1/conversations/{id}/agent/resume', {
      method: 'POST',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({}),
    })
    console.log(await res.json())
    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    headers["Content-Type"] = "application/json"
    r = requests.post("https://orbit-api.devotel.io/api/v1/conversations/{id}/agent/resume", headers=headers, json={})
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("POST", "https://orbit-api.devotel.io/api/v1/conversations/{id}/agent/resume", bytes.NewBuffer([]byte(`{}`)))
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
    	req.Header.Set("Content-Type", "application/json")
    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://orbit-api.devotel.io/api/v1/conversations/{id}/agent/resume')
    req = Net::HTTP::Post.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']
    req['Content-Type'] = 'application/json'
    req.body = {}.to_json
    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://orbit-api.devotel.io/api/v1/conversations/{id}/agent/resume');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),
      'Content-Type: application/json',
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, <<<JSON
    {}
    JSON);
    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

***

### Assign conversation

<Note>
  `POST /api/v1/conversations/{id}/assign`
</Note>

Assign a conversation to a team member by their user ID.

<ParamField path="id" type="string" required>
  —
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X POST "https://orbit-api.devotel.io/api/v1/conversations/{id}/assign" \
      -H "X-API-Key: dv_live_sk_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{}'
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel/orbit-sdk'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://orbit-api.devotel.io/api/v1/conversations/{id}/assign', {
      method: 'POST',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({}),
    })
    console.log(await res.json())
    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    headers["Content-Type"] = "application/json"
    r = requests.post("https://orbit-api.devotel.io/api/v1/conversations/{id}/assign", headers=headers, json={})
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("POST", "https://orbit-api.devotel.io/api/v1/conversations/{id}/assign", bytes.NewBuffer([]byte(`{}`)))
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
    	req.Header.Set("Content-Type", "application/json")
    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://orbit-api.devotel.io/api/v1/conversations/{id}/assign')
    req = Net::HTTP::Post.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']
    req['Content-Type'] = 'application/json'
    req.body = {}.to_json
    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://orbit-api.devotel.io/api/v1/conversations/{id}/assign');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),
      'Content-Type: application/json',
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, <<<JSON
    {}
    JSON);
    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

***

### Close conversation

<Note>
  `POST /api/v1/conversations/{id}/close`
</Note>

Close/resolve a conversation. Closed conversations can be reopened.

<ParamField path="id" type="string" required>
  —
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X POST "https://orbit-api.devotel.io/api/v1/conversations/{id}/close" \
      -H "X-API-Key: dv_live_sk_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{}'
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel/orbit-sdk'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://orbit-api.devotel.io/api/v1/conversations/{id}/close', {
      method: 'POST',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({}),
    })
    console.log(await res.json())
    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    headers["Content-Type"] = "application/json"
    r = requests.post("https://orbit-api.devotel.io/api/v1/conversations/{id}/close", headers=headers, json={})
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("POST", "https://orbit-api.devotel.io/api/v1/conversations/{id}/close", bytes.NewBuffer([]byte(`{}`)))
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
    	req.Header.Set("Content-Type", "application/json")
    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://orbit-api.devotel.io/api/v1/conversations/{id}/close')
    req = Net::HTTP::Post.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']
    req['Content-Type'] = 'application/json'
    req.body = {}.to_json
    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://orbit-api.devotel.io/api/v1/conversations/{id}/close');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),
      'Content-Type: application/json',
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, <<<JSON
    {}
    JSON);
    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

***

### Agent Copilot — suggest replies + next-best-actions

<Note>
  `POST /api/v1/conversations/{id}/copilot/suggest`
</Note>

Returns up to 3 suggested replies and up to 2 next-best-action recommendations for the human agent currently working the conversation. Reads the recent message thread, asks the platform LLM for a structured response. Optional `lookback` (default 25, max 50) controls how many recent messages are fed to the model.

<ParamField path="id" type="string" required>
  —
</ParamField>

<ParamField body="lookback" type="integer">
  How many recent messages to feed the LLM. Defaults to 25.
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X POST "https://orbit-api.devotel.io/api/v1/conversations/{id}/copilot/suggest" \
      -H "X-API-Key: dv_live_sk_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{}'
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel/orbit-sdk'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://orbit-api.devotel.io/api/v1/conversations/{id}/copilot/suggest', {
      method: 'POST',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({}),
    })
    console.log(await res.json())
    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    headers["Content-Type"] = "application/json"
    r = requests.post("https://orbit-api.devotel.io/api/v1/conversations/{id}/copilot/suggest", headers=headers, json={})
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("POST", "https://orbit-api.devotel.io/api/v1/conversations/{id}/copilot/suggest", bytes.NewBuffer([]byte(`{}`)))
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
    	req.Header.Set("Content-Type", "application/json")
    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://orbit-api.devotel.io/api/v1/conversations/{id}/copilot/suggest')
    req = Net::HTTP::Post.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']
    req['Content-Type'] = 'application/json'
    req.body = {}.to_json
    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://orbit-api.devotel.io/api/v1/conversations/{id}/copilot/suggest');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),
      'Content-Type: application/json',
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, <<<JSON
    {}
    JSON);
    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

***

### Escalate conversation

<Note>
  `POST /api/v1/conversations/{id}/escalate`
</Note>

Mark a conversation as escalated (stamps escalated\_at + metadata.escalation), fan out a Slack notification + email to the org's configured escalation recipients, write an audit row, and emit an inbox SSE event. Idempotent — re-escalating an already-escalated conversation returns the existing escalated\_at without re-fanning out the notifications. Recipients resolve from organizations.settings.escalation.user\_ids + .email\_recipients (or the per-request target\_user\_ids override). notify\_channels lets callers opt out of slack-only or email-only fan-out (default: both).

<ParamField path="id" type="string" required>
  —
</ParamField>

<ParamField body="reason" type="string">
  Free-text reason — surfaced in Slack + email body and stored on metadata.escalation.reason.
</ParamField>

<ParamField body="priority" type="string (enum: normal|high|urgent)">
  Escalation priority. Default 'urgent'. Drives Slack button styling (danger vs primary) and email subject prefix.
</ParamField>

<ParamField body="target_user_ids" type="string[]">
  Optional caller override of recipients. Each id is validated against the caller's organizationId so a malicious or buggy caller cannot leak the notification to another tenant's users. When omitted, falls back to organizations.settings.escalation.user\_ids.
</ParamField>

<ParamField body="notify_channels" type="string (enum: slack|email)[]">
  Opt-out lever for individual notification channels. Default \['slack','email'] (both). Useful for tests / programmatic callers that only want one fan-out path.
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X POST "https://orbit-api.devotel.io/api/v1/conversations/{id}/escalate" \
      -H "X-API-Key: dv_live_sk_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{}'
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel/orbit-sdk'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://orbit-api.devotel.io/api/v1/conversations/{id}/escalate', {
      method: 'POST',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({}),
    })
    console.log(await res.json())
    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    headers["Content-Type"] = "application/json"
    r = requests.post("https://orbit-api.devotel.io/api/v1/conversations/{id}/escalate", headers=headers, json={})
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("POST", "https://orbit-api.devotel.io/api/v1/conversations/{id}/escalate", bytes.NewBuffer([]byte(`{}`)))
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
    	req.Header.Set("Content-Type", "application/json")
    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://orbit-api.devotel.io/api/v1/conversations/{id}/escalate')
    req = Net::HTTP::Post.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']
    req['Content-Type'] = 'application/json'
    req.body = {}.to_json
    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://orbit-api.devotel.io/api/v1/conversations/{id}/escalate');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),
      'Content-Type: application/json',
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, <<<JSON
    {}
    JSON);
    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

***

### Handoff conversation from AI to a human

<Note>
  `POST /api/v1/conversations/{id}/handoff`
</Note>

Distinct from /assign: clears any AI agent assignment, flips status to pending so the conversation re-enters the unassigned queue, and stamps metadata.needs\_human + handoff\_reason + target\_queue + handoff\_skill\_tags + handoff\_matched\_operators so the receiving human picks up the context. Audit #HANDOFF-1 also persists a top-level handoff\_brief (LLM-generated 2-3 sentence summary) and handoff\_reason\_category (machine-readable enum) on the conversation row, and audit #HANDOFF-2 enqueues the conversation in the org's Redis sorted-set queue so the customer-facing widget can render queue position.

<ParamField path="id" type="string" required>
  —
</ParamField>

<ParamField body="reason" type="string">
  Short reason — surfaced to the receiving human (e.g. 'asked for a manager').
</ParamField>

<ParamField body="target_queue" type="string">
  Optional queue to route to. Defaults to the tenant's default queue.
</ParamField>

<ParamField body="reason_category" type="string (enum: cant_understand|policy_block|customer_request|tool_failed|escalation_threshold|manual)">
  Machine-readable handoff category for analytics. When omitted, inferred from the free-text reason.
</ParamField>

<ParamField body="skill_tags" type="string[]">
  Caller override for the skill set used to match operators. When omitted, derived from the conversation's most recent AI agent's skill\_tags.
</ParamField>

<ParamField body="brief" type="string">
  Caller-supplied 2-3 sentence summary. When omitted, the service generates one via the LLM gateway.
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X POST "https://orbit-api.devotel.io/api/v1/conversations/{id}/handoff" \
      -H "X-API-Key: dv_live_sk_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{}'
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel/orbit-sdk'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://orbit-api.devotel.io/api/v1/conversations/{id}/handoff', {
      method: 'POST',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({}),
    })
    console.log(await res.json())
    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    headers["Content-Type"] = "application/json"
    r = requests.post("https://orbit-api.devotel.io/api/v1/conversations/{id}/handoff", headers=headers, json={})
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("POST", "https://orbit-api.devotel.io/api/v1/conversations/{id}/handoff", bytes.NewBuffer([]byte(`{}`)))
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
    	req.Header.Set("Content-Type", "application/json")
    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://orbit-api.devotel.io/api/v1/conversations/{id}/handoff')
    req = Net::HTTP::Post.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']
    req['Content-Type'] = 'application/json'
    req.body = {}.to_json
    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://orbit-api.devotel.io/api/v1/conversations/{id}/handoff');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),
      'Content-Type: application/json',
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, <<<JSON
    {}
    JSON);
    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

***

### Resolve AI handoff

<Note>
  `POST /api/v1/conversations/{id}/handoff/resolve`
</Note>

Clear the AI-handoff flags (needs\_human, handoff\_reason, target\_queue) from a conversation's metadata once a human has engaged. Stamps handoff\_resolved\_at so the audit trail keeps a record.

<ParamField path="id" type="string" required>
  —
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X POST "https://orbit-api.devotel.io/api/v1/conversations/{id}/handoff/resolve" \
      -H "X-API-Key: dv_live_sk_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{}'
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel/orbit-sdk'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://orbit-api.devotel.io/api/v1/conversations/{id}/handoff/resolve', {
      method: 'POST',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({}),
    })
    console.log(await res.json())
    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    headers["Content-Type"] = "application/json"
    r = requests.post("https://orbit-api.devotel.io/api/v1/conversations/{id}/handoff/resolve", headers=headers, json={})
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("POST", "https://orbit-api.devotel.io/api/v1/conversations/{id}/handoff/resolve", bytes.NewBuffer([]byte(`{}`)))
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
    	req.Header.Set("Content-Type", "application/json")
    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://orbit-api.devotel.io/api/v1/conversations/{id}/handoff/resolve')
    req = Net::HTTP::Post.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']
    req['Content-Type'] = 'application/json'
    req.body = {}.to_json
    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://orbit-api.devotel.io/api/v1/conversations/{id}/handoff/resolve');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),
      'Content-Type: application/json',
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, <<<JSON
    {}
    JSON);
    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

***

### Link a related conversation

<Note>
  `POST /api/v1/conversations/{id}/links`
</Note>

Create a soft, reversible cross-reference between this conversation and another without collapsing either timeline (distinct from merge). The link surfaces in both threads' sidebars and can be removed at any time via DELETE /:id/links/:linkId. Supported link\_type values: relates\_to (default, symmetric), duplicate\_of (symmetric), blocks/blocked\_by (directional inverses), parent\_of/child\_of (directional inverses). Directional types respect the operator's submitted order from the perspective of /:id.

<ParamField path="id" type="string" required>
  —
</ParamField>

<ParamField body="target_conversation_id" type="string" required>
  —
</ParamField>

<ParamField body="link_type" type="string (enum: relates_to|duplicate_of|blocks|blocked_by|parent_of|child_of)">
  —
</ParamField>

<ParamField body="note" type="string">
  —
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X POST "https://orbit-api.devotel.io/api/v1/conversations/{id}/links" \
      -H "X-API-Key: dv_live_sk_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{
      "target_conversation_id": "string"
    }'
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel/orbit-sdk'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://orbit-api.devotel.io/api/v1/conversations/{id}/links', {
      method: 'POST',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
      "target_conversation_id": "string"
    }),
    })
    console.log(await res.json())
    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    headers["Content-Type"] = "application/json"
    r = requests.post("https://orbit-api.devotel.io/api/v1/conversations/{id}/links", headers=headers, json={
      "target_conversation_id": "string"
    })
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("POST", "https://orbit-api.devotel.io/api/v1/conversations/{id}/links", bytes.NewBuffer([]byte(`{
      "target_conversation_id": "string"
    }`)))
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
    	req.Header.Set("Content-Type", "application/json")
    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://orbit-api.devotel.io/api/v1/conversations/{id}/links')
    req = Net::HTTP::Post.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']
    req['Content-Type'] = 'application/json'
    req.body = {
      "target_conversation_id": "string"
    }.to_json
    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://orbit-api.devotel.io/api/v1/conversations/{id}/links');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),
      'Content-Type: application/json',
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, <<<JSON
    {
      "target_conversation_id": "string"
    }
    JSON);
    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

***

### Merge conversations

<Note>
  `POST /api/v1/conversations/{id}/merge`
</Note>

Fold one or more sibling conversation rows into this one. Source rows must belong to the same contact (matching contact\_id, phone, or email). Sources are archived with metadata.merged\_into pointing at the primary; the primary's channels\[] is unioned so the existing thread query loads messages from every channel.

<ParamField path="id" type="string" required>
  —
</ParamField>

<ParamField body="source_conversation_ids" type="string[]" required>
  —
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X POST "https://orbit-api.devotel.io/api/v1/conversations/{id}/merge" \
      -H "X-API-Key: dv_live_sk_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{
      "source_conversation_ids": []
    }'
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel/orbit-sdk'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://orbit-api.devotel.io/api/v1/conversations/{id}/merge', {
      method: 'POST',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
      "source_conversation_ids": []
    }),
    })
    console.log(await res.json())
    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    headers["Content-Type"] = "application/json"
    r = requests.post("https://orbit-api.devotel.io/api/v1/conversations/{id}/merge", headers=headers, json={
      "source_conversation_ids": []
    })
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("POST", "https://orbit-api.devotel.io/api/v1/conversations/{id}/merge", bytes.NewBuffer([]byte(`{
      "source_conversation_ids": []
    }`)))
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
    	req.Header.Set("Content-Type", "application/json")
    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://orbit-api.devotel.io/api/v1/conversations/{id}/merge')
    req = Net::HTTP::Post.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']
    req['Content-Type'] = 'application/json'
    req.body = {
      "source_conversation_ids": []
    }.to_json
    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://orbit-api.devotel.io/api/v1/conversations/{id}/merge');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),
      'Content-Type: application/json',
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, <<<JSON
    {
      "source_conversation_ids": []
    }
    JSON);
    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

***

### Reopen conversation

<Note>
  `POST /api/v1/conversations/{id}/reopen`
</Note>

Reopen a closed conversation so agents can continue the thread.

<ParamField path="id" type="string" required>
  —
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X POST "https://orbit-api.devotel.io/api/v1/conversations/{id}/reopen" \
      -H "X-API-Key: dv_live_sk_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{}'
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel/orbit-sdk'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://orbit-api.devotel.io/api/v1/conversations/{id}/reopen', {
      method: 'POST',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({}),
    })
    console.log(await res.json())
    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    headers["Content-Type"] = "application/json"
    r = requests.post("https://orbit-api.devotel.io/api/v1/conversations/{id}/reopen", headers=headers, json={})
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("POST", "https://orbit-api.devotel.io/api/v1/conversations/{id}/reopen", bytes.NewBuffer([]byte(`{}`)))
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
    	req.Header.Set("Content-Type", "application/json")
    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://orbit-api.devotel.io/api/v1/conversations/{id}/reopen')
    req = Net::HTTP::Post.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']
    req['Content-Type'] = 'application/json'
    req.body = {}.to_json
    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://orbit-api.devotel.io/api/v1/conversations/{id}/reopen');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),
      'Content-Type: application/json',
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, <<<JSON
    {}
    JSON);
    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

***

### Reply in a conversation

<Note>
  `POST /api/v1/conversations/{id}/reply`
</Note>

Send a message back to the contact on the same channel. Delegates to the existing messaging router.

<ParamField path="id" type="string" required>
  —
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X POST "https://orbit-api.devotel.io/api/v1/conversations/{id}/reply" \
      -H "X-API-Key: dv_live_sk_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{}'
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel/orbit-sdk'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://orbit-api.devotel.io/api/v1/conversations/{id}/reply', {
      method: 'POST',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({}),
    })
    console.log(await res.json())
    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    headers["Content-Type"] = "application/json"
    r = requests.post("https://orbit-api.devotel.io/api/v1/conversations/{id}/reply", headers=headers, json={})
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("POST", "https://orbit-api.devotel.io/api/v1/conversations/{id}/reply", bytes.NewBuffer([]byte(`{}`)))
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
    	req.Header.Set("Content-Type", "application/json")
    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://orbit-api.devotel.io/api/v1/conversations/{id}/reply')
    req = Net::HTTP::Post.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']
    req['Content-Type'] = 'application/json'
    req.body = {}.to_json
    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://orbit-api.devotel.io/api/v1/conversations/{id}/reply');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),
      'Content-Type: application/json',
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, <<<JSON
    {}
    JSON);
    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

***

### Snooze conversation

<Note>
  `POST /api/v1/conversations/{id}/snooze`
</Note>

Hide a conversation from the open inbox until the supplied ISO 8601 timestamp. The webhook-worker auto-reopens the row once the deadline passes.

<ParamField path="id" type="string" required>
  —
</ParamField>

<ParamField body="until" type="string" required>
  Absolute ISO 8601 timestamp (must be in the future)
</ParamField>

<ParamField body="reason" type="string">
  Optional human-readable label e.g. 'wait for invoice approval'
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X POST "https://orbit-api.devotel.io/api/v1/conversations/{id}/snooze" \
      -H "X-API-Key: dv_live_sk_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{
      "until": "1970-01-01T00:00:00.000Z"
    }'
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel/orbit-sdk'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://orbit-api.devotel.io/api/v1/conversations/{id}/snooze', {
      method: 'POST',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
      "until": "1970-01-01T00:00:00.000Z"
    }),
    })
    console.log(await res.json())
    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    headers["Content-Type"] = "application/json"
    r = requests.post("https://orbit-api.devotel.io/api/v1/conversations/{id}/snooze", headers=headers, json={
      "until": "1970-01-01T00:00:00.000Z"
    })
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("POST", "https://orbit-api.devotel.io/api/v1/conversations/{id}/snooze", bytes.NewBuffer([]byte(`{
      "until": "1970-01-01T00:00:00.000Z"
    }`)))
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
    	req.Header.Set("Content-Type", "application/json")
    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://orbit-api.devotel.io/api/v1/conversations/{id}/snooze')
    req = Net::HTTP::Post.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']
    req['Content-Type'] = 'application/json'
    req.body = {
      "until": "1970-01-01T00:00:00.000Z"
    }.to_json
    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://orbit-api.devotel.io/api/v1/conversations/{id}/snooze');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),
      'Content-Type: application/json',
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, <<<JSON
    {
      "until": "1970-01-01T00:00:00.000Z"
    }
    JSON);
    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

***

### Undo a recent conversation merge

<Note>
  `POST /api/v1/conversations/{id}/unmerge`
</Note>

Reverse a recent mergeConversations call within a 30-minute window. Restores every source row archived into this primary using the pre\_merge\_snapshot stamped at merge-time; recomputes the primary's channels\[] from pre\_merge\_channels minus any sources still merged. Mirrors the contact-merge undo gate in /contacts/unmerge/:mergeId. Returns {restored, expired, channels, restored_source_ids} — `expired` counts sources outside the 30-min window so the UI can show 'contact support for a manual revert' when the window has fully expired.

<ParamField path="id" type="string" required>
  —
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X POST "https://orbit-api.devotel.io/api/v1/conversations/{id}/unmerge" \
      -H "X-API-Key: dv_live_sk_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{}'
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel/orbit-sdk'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://orbit-api.devotel.io/api/v1/conversations/{id}/unmerge', {
      method: 'POST',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({}),
    })
    console.log(await res.json())
    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    headers["Content-Type"] = "application/json"
    r = requests.post("https://orbit-api.devotel.io/api/v1/conversations/{id}/unmerge", headers=headers, json={})
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("POST", "https://orbit-api.devotel.io/api/v1/conversations/{id}/unmerge", bytes.NewBuffer([]byte(`{}`)))
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
    	req.Header.Set("Content-Type", "application/json")
    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://orbit-api.devotel.io/api/v1/conversations/{id}/unmerge')
    req = Net::HTTP::Post.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']
    req['Content-Type'] = 'application/json'
    req.body = {}.to_json
    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://orbit-api.devotel.io/api/v1/conversations/{id}/unmerge');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),
      'Content-Type: application/json',
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, <<<JSON
    {}
    JSON);
    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

***

### Bulk action

<Note>
  `POST /api/v1/conversations/bulk`
</Note>

Apply the same action (close / reopen / assign / add\_tags / remove\_tags) to up to 200 conversations in a single request. Returns per-id success/failure so the UI can surface partial-success outcomes.

<ParamField body="action" type="string (enum: close|reopen|assign|add_tags|remove_tags)" required>
  —
</ParamField>

<ParamField body="conversation_ids" type="string[]" required>
  —
</ParamField>

<ParamField body="agent_id" type="string | null">
  Required for action=assign. Null to unassign.
</ParamField>

<ParamField body="tags" type="string[]">
  Required for action=add\_tags / remove\_tags.
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X POST "https://orbit-api.devotel.io/api/v1/conversations/bulk" \
      -H "X-API-Key: dv_live_sk_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{
      "action": "close",
      "conversation_ids": []
    }'
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel/orbit-sdk'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://orbit-api.devotel.io/api/v1/conversations/bulk', {
      method: 'POST',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
      "action": "close",
      "conversation_ids": []
    }),
    })
    console.log(await res.json())
    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    headers["Content-Type"] = "application/json"
    r = requests.post("https://orbit-api.devotel.io/api/v1/conversations/bulk", headers=headers, json={
      "action": "close",
      "conversation_ids": []
    })
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("POST", "https://orbit-api.devotel.io/api/v1/conversations/bulk", bytes.NewBuffer([]byte(`{
      "action": "close",
      "conversation_ids": []
    }`)))
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
    	req.Header.Set("Content-Type", "application/json")
    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://orbit-api.devotel.io/api/v1/conversations/bulk')
    req = Net::HTTP::Post.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']
    req['Content-Type'] = 'application/json'
    req.body = {
      "action": "close",
      "conversation_ids": []
    }.to_json
    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://orbit-api.devotel.io/api/v1/conversations/bulk');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),
      'Content-Type: application/json',
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, <<<JSON
    {
      "action": "close",
      "conversation_ids": []
    }
    JSON);
    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

***

### Update conversation tags

<Note>
  `PUT /api/v1/conversations/{id}/tags`
</Note>

Set the tags array on a conversation. Tags are normalized to lowercase, trimmed, max 20 chars each, max 10 per conversation.

<ParamField path="id" type="string" required>
  —
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X PUT "https://orbit-api.devotel.io/api/v1/conversations/{id}/tags" \
      -H "X-API-Key: dv_live_sk_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{}'
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel/orbit-sdk'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://orbit-api.devotel.io/api/v1/conversations/{id}/tags', {
      method: 'PUT',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({}),
    })
    console.log(await res.json())
    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    headers["Content-Type"] = "application/json"
    r = requests.put("https://orbit-api.devotel.io/api/v1/conversations/{id}/tags", headers=headers, json={})
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("PUT", "https://orbit-api.devotel.io/api/v1/conversations/{id}/tags", bytes.NewBuffer([]byte(`{}`)))
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
    	req.Header.Set("Content-Type", "application/json")
    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://orbit-api.devotel.io/api/v1/conversations/{id}/tags')
    req = Net::HTTP::Put.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']
    req['Content-Type'] = 'application/json'
    req.body = {}.to_json
    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://orbit-api.devotel.io/api/v1/conversations/{id}/tags');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),
      'Content-Type: application/json',
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, <<<JSON
    {}
    JSON);
    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

***

### Unlink a related conversation

<Note>
  `DELETE /api/v1/conversations/{id}/links/{linkId}`
</Note>

Remove a conversation link by its link id (NOT by the other conversation's id — the link id is returned from POST /:id/links and from GET /:id/links). Returns 404 when the link id doesn't exist in this tenant so the FE can distinguish 'no longer exists' from 'successfully removed'.

<ParamField path="id" type="string" required>
  —
</ParamField>

<ParamField path="linkId" type="string" required>
  —
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X DELETE "https://orbit-api.devotel.io/api/v1/conversations/{id}/links/{linkId}" \
      -H "X-API-Key: dv_live_sk_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{}'
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel/orbit-sdk'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://orbit-api.devotel.io/api/v1/conversations/{id}/links/{linkId}', {
      method: 'DELETE',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({}),
    })
    console.log(await res.json())
    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    headers["Content-Type"] = "application/json"
    r = requests.delete("https://orbit-api.devotel.io/api/v1/conversations/{id}/links/{linkId}", headers=headers, json={})
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("DELETE", "https://orbit-api.devotel.io/api/v1/conversations/{id}/links/{linkId}", bytes.NewBuffer([]byte(`{}`)))
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
    	req.Header.Set("Content-Type", "application/json")
    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://orbit-api.devotel.io/api/v1/conversations/{id}/links/{linkId}')
    req = Net::HTTP::Delete.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']
    req['Content-Type'] = 'application/json'
    req.body = {}.to_json
    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://orbit-api.devotel.io/api/v1/conversations/{id}/links/{linkId}');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),
      'Content-Type: application/json',
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, <<<JSON
    {}
    JSON);
    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

***
