> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs-parschat.parstechai.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs-parschat.parstechai.com/_mcp/server.

# Send a message

POST https://api-chat.parstechai.com/v1/conversations/{conversation_id}/messages
Content-Type: application/json

Sends a message to the end customer as your operator.

This is how your own support agents reply through your panel instead of ours. The message reaches the customer on whichever channel the conversation belongs to, so the same call works for Instagram and Telegram.

**Send a file by first uploading it** with the attachments endpoint and passing the `attachment_id` you get back. A message may carry text, attachments, or both.

Set `external_id` to your own id for the message. Sending the same one twice returns the original message rather than creating a duplicate, so a retry after a timeout is safe.

A `status` of `queued` means the channel is rate limited and we will deliver it when capacity frees. Nothing is discarded.

Reference: https://docs-parschat.parstechai.com/api-reference/conversations/send-message

## Authentication

- `Authorization` header (bearer token, required) — Your API key, issued by ParsChat sales. One key per account, spanning every robot you own. Send it as `Authorization: Bearer pk_live_…`.

## Request

### Path parameters

- `conversation_id` (string, required) — The conversation's id, as it arrives on every webhook event.

### Body (application/json)

This endpoint expects an object.

- `text` (string, optional) — The message body. Required unless you send attachments.
- `attachment_ids` (list of string, optional) — Ids from the attachments endpoint. Send files, voice notes or images.
- `external_id` (string, optional) — Your own id for this message. Reusing one returns the original instead of sending twice.
- `reply_to_id` (string, optional) — The `message_id` this replies to, to thread it.

## Response

### 201

The message was accepted.

- `conversation_id` (string, optional) — The conversation it belongs to.
- `message_id` (string, optional) — ParsChat identifier for the message.
- `external_id` (string, optional) — Your own identifier, echoed back. Send the same one twice and we do not create a duplicate.
- `role` (enum, optional) — Who sent it.
  - Allowed values: `client`, `operator`, `ai`, `system`
- `type` (enum, optional) — What the message carries.
  - Allowed values: `text`, `voice`, `image`, `video`, `multimedia`, `document`, `poll`, `form`, `template`
- `text` (string, optional) — Plain text body. Empty on a file-only message.
- `attachments` (list of Attachment, optional) — Files on this message. **Empty on the response to sending a message** — populated on inbound messages and on the `message` webhook event.
- `status` (enum, optional) — `sent` once we have handed it to the channel. `queued` when the channel is rate limited and it will go out later.
  - Allowed values: `sent`, `queued`, `failed`
- `created_at` (datetime, optional) — When the message was created.

## Errors

### 400 Bad Request Error

The request itself is malformed: a parameter is missing, unparseable, or out of range. Unlike 422, the request never reached validation of its contents.

- `error` (ErrorError, optional)

### 401 Unauthorized Error

The key is missing, wrong, revoked, or past its expiry date. All four answer `invalid_key`: telling an expired key from an unknown one would confirm to whoever holds a leaked key that it was once real.

- `error` (ErrorError, optional)

### 403 Forbidden Error

The call is understood but not permitted: a missing role, a lapsed plan, or exhausted capacity.

- `error` (ErrorError, optional)

### 404 Not Found Error

No such resource, or it belongs to another partner. We return 404 rather than 403 here, because a 403 would confirm the id exists.

- `error` (ErrorError, optional)

### 409 Conflict Error

The request collides with something that already exists.

- `error` (ErrorError, optional)

### 422 Unprocessable Entity Error

The payload is malformed, or a prerequisite is not configured.

- `error` (ErrorError, optional)

### 429 Too Many Requests Error

You are calling the API too fast. `details` names the limit you are held to. This never means an automated reply to an end customer was discarded: those are queued, not dropped.

- `error` (ErrorError, optional)

### 500 Internal Server Error

Something failed on our side. Retry with backoff, and quote `request_id` if it persists.

- `error` (ErrorError, optional)

### 503 Service Unavailable Error

A service we depend on (a channel provider, or our AI service) is unavailable. Retry with backoff.

- `error` (ErrorError, optional)

## Types

### Attachment

A file on a message.

- `type` (enum, optional) — What kind of file this is.
  - Allowed values: `image`, `video`, `voice`, `document`
- `url` (string, optional) — Where to download it. Time-limited: fetch it when the event arrives.
- `mime` (string, optional) — Media type.
- `filename` (string, optional) — Original filename, when the sender supplied one.
- `size_bytes` (integer, optional) — File size.
- `duration_seconds` (integer, optional) — Length of a voice or video file.

### ErrorError

- `code` (string, optional) — Stable machine-readable code. Branch on this, not on the message.
- `message` (string, optional) — Human-readable explanation. Wording may change.
- `request_id` (string, optional) — Log this. It is what lets support find your exact request.
- `details` (map from string to any, optional) — Machine-readable context for the code. On `capacity_exhausted` it carries `occupied`, `requested` and `capacity`; on `rate_limited`, `limit_per_minute` and `window_seconds`.

## Examples

### A text reply

**Request**

```json
{
  "text": "Yes, we deliver on Fridays. Shall I reserve one for you?",
  "external_id": "op_reply_5521"
}
```

**Response**

```json
{
  "conversation_id": "cnv_4dR8nW",
  "message_id": "msg_9wY7zA",
  "external_id": "op_reply_5521",
  "role": "operator",
  "type": "text",
  "text": "Yes, we deliver on Fridays. Shall I reserve one for you?",
  "attachments": [],
  "status": "sent",
  "created_at": "2026-09-20T11:33:05Z"
}
```

**SDK Code**

```python A text reply
import requests

url = "https://api-chat.parstechai.com/v1/conversations/cnv_4dR8nW/messages"

payload = {
    "text": "Yes, we deliver on Fridays. Shall I reserve one for you?",
    "external_id": "op_reply_5521"
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript A text reply
const url = 'https://api-chat.parstechai.com/v1/conversations/cnv_4dR8nW/messages';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"text":"Yes, we deliver on Fridays. Shall I reserve one for you?","external_id":"op_reply_5521"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go A text reply
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api-chat.parstechai.com/v1/conversations/cnv_4dR8nW/messages"

	payload := strings.NewReader("{\n  \"text\": \"Yes, we deliver on Fridays. Shall I reserve one for you?\",\n  \"external_id\": \"op_reply_5521\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby A text reply
require 'uri'
require 'net/http'

url = URI("https://api-chat.parstechai.com/v1/conversations/cnv_4dR8nW/messages")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"text\": \"Yes, we deliver on Fridays. Shall I reserve one for you?\",\n  \"external_id\": \"op_reply_5521\"\n}"

response = http.request(request)
puts response.read_body
```

```java A text reply
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api-chat.parstechai.com/v1/conversations/cnv_4dR8nW/messages")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"text\": \"Yes, we deliver on Fridays. Shall I reserve one for you?\",\n  \"external_id\": \"op_reply_5521\"\n}")
  .asString();
```

```php A text reply
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api-chat.parstechai.com/v1/conversations/cnv_4dR8nW/messages', [
  'body' => '{
  "text": "Yes, we deliver on Fridays. Shall I reserve one for you?",
  "external_id": "op_reply_5521"
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp A text reply
using RestSharp;

var client = new RestClient("https://api-chat.parstechai.com/v1/conversations/cnv_4dR8nW/messages");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"text\": \"Yes, we deliver on Fridays. Shall I reserve one for you?\",\n  \"external_id\": \"op_reply_5521\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift A text reply
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "text": "Yes, we deliver on Fridays. Shall I reserve one for you?",
  "external_id": "op_reply_5521"
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api-chat.parstechai.com/v1/conversations/cnv_4dR8nW/messages")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### A document

**Request**

```json
{
  "attachment_ids": [
    "att_5kR2nP"
  ],
  "external_id": "op_invoice_77"
}
```

**Response**

```json
{
  "conversation_id": "cnv_4dR8nW",
  "message_id": "msg_1xZ8bC",
  "external_id": "op_invoice_77",
  "role": "operator",
  "type": "document",
  "text": "",
  "attachments": [],
  "status": "sent",
  "created_at": "2026-09-20T11:35:41Z"
}
```

**SDK Code**

```python A document
import requests

url = "https://api-chat.parstechai.com/v1/conversations/cnv_4dR8nW/messages"

payload = {
    "attachment_ids": ["att_5kR2nP"],
    "external_id": "op_invoice_77"
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript A document
const url = 'https://api-chat.parstechai.com/v1/conversations/cnv_4dR8nW/messages';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"attachment_ids":["att_5kR2nP"],"external_id":"op_invoice_77"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go A document
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api-chat.parstechai.com/v1/conversations/cnv_4dR8nW/messages"

	payload := strings.NewReader("{\n  \"attachment_ids\": [\n    \"att_5kR2nP\"\n  ],\n  \"external_id\": \"op_invoice_77\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby A document
require 'uri'
require 'net/http'

url = URI("https://api-chat.parstechai.com/v1/conversations/cnv_4dR8nW/messages")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"attachment_ids\": [\n    \"att_5kR2nP\"\n  ],\n  \"external_id\": \"op_invoice_77\"\n}"

response = http.request(request)
puts response.read_body
```

```java A document
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api-chat.parstechai.com/v1/conversations/cnv_4dR8nW/messages")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"attachment_ids\": [\n    \"att_5kR2nP\"\n  ],\n  \"external_id\": \"op_invoice_77\"\n}")
  .asString();
```

```php A document
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api-chat.parstechai.com/v1/conversations/cnv_4dR8nW/messages', [
  'body' => '{
  "attachment_ids": [
    "att_5kR2nP"
  ],
  "external_id": "op_invoice_77"
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp A document
using RestSharp;

var client = new RestClient("https://api-chat.parstechai.com/v1/conversations/cnv_4dR8nW/messages");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"attachment_ids\": [\n    \"att_5kR2nP\"\n  ],\n  \"external_id\": \"op_invoice_77\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift A document
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "attachment_ids": ["att_5kR2nP"],
  "external_id": "op_invoice_77"
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api-chat.parstechai.com/v1/conversations/cnv_4dR8nW/messages")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### A voice note

**Request**

```json
{
  "attachment_ids": [
    "att_8mT4qS"
  ],
  "external_id": "op_voice_12"
}
```

**Response**

```json
{
  "conversation_id": "cnv_4dR8nW",
  "message_id": "msg_4aD2eF",
  "external_id": "op_voice_12",
  "role": "operator",
  "type": "voice",
  "text": "",
  "attachments": [],
  "status": "sent",
  "created_at": "2026-09-20T11:37:02Z"
}
```

**SDK Code**

```python A voice note
import requests

url = "https://api-chat.parstechai.com/v1/conversations/cnv_4dR8nW/messages"

payload = {
    "attachment_ids": ["att_8mT4qS"],
    "external_id": "op_voice_12"
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript A voice note
const url = 'https://api-chat.parstechai.com/v1/conversations/cnv_4dR8nW/messages';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"attachment_ids":["att_8mT4qS"],"external_id":"op_voice_12"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go A voice note
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api-chat.parstechai.com/v1/conversations/cnv_4dR8nW/messages"

	payload := strings.NewReader("{\n  \"attachment_ids\": [\n    \"att_8mT4qS\"\n  ],\n  \"external_id\": \"op_voice_12\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby A voice note
require 'uri'
require 'net/http'

url = URI("https://api-chat.parstechai.com/v1/conversations/cnv_4dR8nW/messages")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"attachment_ids\": [\n    \"att_8mT4qS\"\n  ],\n  \"external_id\": \"op_voice_12\"\n}"

response = http.request(request)
puts response.read_body
```

```java A voice note
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api-chat.parstechai.com/v1/conversations/cnv_4dR8nW/messages")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"attachment_ids\": [\n    \"att_8mT4qS\"\n  ],\n  \"external_id\": \"op_voice_12\"\n}")
  .asString();
```

```php A voice note
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api-chat.parstechai.com/v1/conversations/cnv_4dR8nW/messages', [
  'body' => '{
  "attachment_ids": [
    "att_8mT4qS"
  ],
  "external_id": "op_voice_12"
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp A voice note
using RestSharp;

var client = new RestClient("https://api-chat.parstechai.com/v1/conversations/cnv_4dR8nW/messages");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"attachment_ids\": [\n    \"att_8mT4qS\"\n  ],\n  \"external_id\": \"op_voice_12\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift A voice note
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "attachment_ids": ["att_8mT4qS"],
  "external_id": "op_voice_12"
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api-chat.parstechai.com/v1/conversations/cnv_4dR8nW/messages")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```