curl --request POST \
--url https://api.zivio.net/api/v4/projects/{id}/conversations/{conversation_id}/reply \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'zivio-tenant-id: <api-key>' \
--data '
{
"body": "Thanks - please also confirm your WCAG 2.2 audit coverage."
}
'import requests
url = "https://api.zivio.net/api/v4/projects/{id}/conversations/{conversation_id}/reply"
payload = { "body": "Thanks - please also confirm your WCAG 2.2 audit coverage." }
headers = {
"Authorization": "Bearer <token>",
"zivio-tenant-id": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
Authorization: 'Bearer <token>',
'zivio-tenant-id': '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
body: JSON.stringify('Thanks - please also confirm your WCAG 2.2 audit coverage.')
})
};
fetch('https://api.zivio.net/api/v4/projects/{id}/conversations/{conversation_id}/reply', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.zivio.net/api/v4/projects/{id}/conversations/{conversation_id}/reply",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'body' => 'Thanks - please also confirm your WCAG 2.2 audit coverage.'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json",
"zivio-tenant-id: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.zivio.net/api/v4/projects/{id}/conversations/{conversation_id}/reply"
payload := strings.NewReader("{\n \"body\": \"Thanks - please also confirm your WCAG 2.2 audit coverage.\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("zivio-tenant-id", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.zivio.net/api/v4/projects/{id}/conversations/{conversation_id}/reply")
.header("Authorization", "Bearer <token>")
.header("zivio-tenant-id", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"body\": \"Thanks - please also confirm your WCAG 2.2 audit coverage.\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.zivio.net/api/v4/projects/{id}/conversations/{conversation_id}/reply")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["zivio-tenant-id"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"body\": \"Thanks - please also confirm your WCAG 2.2 audit coverage.\"\n}"
response = http.request(request)
puts response.read_body{
"project_id": 123,
"conversation": {
"conversation_id": 31,
"subject": "Re: Website Redesign (Acme Consulting)",
"kind": "proposal",
"supplier": {
"id": 45,
"name": "Acme Consulting"
}
},
"message": {
"message_id": 89,
"sender": {
"id": 1,
"name": "Jane Smith",
"side": "client"
},
"body": "Thanks - please also confirm your WCAG 2.2 audit coverage.",
"sent_at": "2026-07-03T10:00:00Z",
"attachments": []
}
}{
"error": "invalid_token",
"error_description": "The access token provided is expired, revoked, malformed, or invalid for other reasons"
}{
"error": "insufficient_scope",
"error_description": "The request requires higher privileges than provided by the access token",
"required_scope": "project_conversations:write",
"provided_scopes": [
"welcome:read"
]
}{
"error": "body_required",
"message": "body is required"
}Reply to an existing conversation
Appends a message to the thread, whatever its kind: supplier proposal threads reply to the supplier, project threads keep their audience (all parties / client-only / supplier). Legacy proposal threads are migrated to the supplier’s engagement first, exactly as the web UI’s reply does. Prefer POST /projects//conversations with a supplier_id when messaging a supplier — it starts or continues the right thread automatically.
curl --request POST \
--url https://api.zivio.net/api/v4/projects/{id}/conversations/{conversation_id}/reply \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'zivio-tenant-id: <api-key>' \
--data '
{
"body": "Thanks - please also confirm your WCAG 2.2 audit coverage."
}
'import requests
url = "https://api.zivio.net/api/v4/projects/{id}/conversations/{conversation_id}/reply"
payload = { "body": "Thanks - please also confirm your WCAG 2.2 audit coverage." }
headers = {
"Authorization": "Bearer <token>",
"zivio-tenant-id": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
Authorization: 'Bearer <token>',
'zivio-tenant-id': '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
body: JSON.stringify('Thanks - please also confirm your WCAG 2.2 audit coverage.')
})
};
fetch('https://api.zivio.net/api/v4/projects/{id}/conversations/{conversation_id}/reply', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.zivio.net/api/v4/projects/{id}/conversations/{conversation_id}/reply",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'body' => 'Thanks - please also confirm your WCAG 2.2 audit coverage.'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json",
"zivio-tenant-id: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.zivio.net/api/v4/projects/{id}/conversations/{conversation_id}/reply"
payload := strings.NewReader("{\n \"body\": \"Thanks - please also confirm your WCAG 2.2 audit coverage.\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("zivio-tenant-id", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.zivio.net/api/v4/projects/{id}/conversations/{conversation_id}/reply")
.header("Authorization", "Bearer <token>")
.header("zivio-tenant-id", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"body\": \"Thanks - please also confirm your WCAG 2.2 audit coverage.\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.zivio.net/api/v4/projects/{id}/conversations/{conversation_id}/reply")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["zivio-tenant-id"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"body\": \"Thanks - please also confirm your WCAG 2.2 audit coverage.\"\n}"
response = http.request(request)
puts response.read_body{
"project_id": 123,
"conversation": {
"conversation_id": 31,
"subject": "Re: Website Redesign (Acme Consulting)",
"kind": "proposal",
"supplier": {
"id": 45,
"name": "Acme Consulting"
}
},
"message": {
"message_id": 89,
"sender": {
"id": 1,
"name": "Jane Smith",
"side": "client"
},
"body": "Thanks - please also confirm your WCAG 2.2 audit coverage.",
"sent_at": "2026-07-03T10:00:00Z",
"attachments": []
}
}{
"error": "invalid_token",
"error_description": "The access token provided is expired, revoked, malformed, or invalid for other reasons"
}{
"error": "insufficient_scope",
"error_description": "The request requires higher privileges than provided by the access token",
"required_scope": "project_conversations:write",
"provided_scopes": [
"welcome:read"
]
}{
"error": "body_required",
"message": "body is required"
}Authorizations
OAuth 2.0 client credentials. The access token from POST /oauth/token is sent as a bearer token. Request only the scopes you need.
Your Zivio organisation identifier. The regional API endpoints serve every Zivio organisation, so each request must identify yours.
Path Parameters
Project ID
Conversation id from GET /projects/{id}/conversations.
Body
The reply text.
"Thanks - please also confirm your WCAG 2.2 audit coverage."
Response
The reply sent, with its conversation
The response is of type object.

