curl --request POST \
--url https://api.zivio.net/api/v4/projects/{id}/conversations \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'zivio-tenant-id: <api-key>' \
--data '
{
"supplier_id": 45,
"body": "Could you confirm who will lead accessibility testing? Please respond by Friday."
}
'import requests
url = "https://api.zivio.net/api/v4/projects/{id}/conversations"
payload = {
"supplier_id": 45,
"body": "Could you confirm who will lead accessibility testing? Please respond by Friday."
}
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({
supplier_id: 45,
body: JSON.stringify('Could you confirm who will lead accessibility testing? Please respond by Friday.')
})
};
fetch('https://api.zivio.net/api/v4/projects/{id}/conversations', 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",
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([
'supplier_id' => 45,
'body' => 'Could you confirm who will lead accessibility testing? Please respond by Friday.'
]),
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"
payload := strings.NewReader("{\n \"supplier_id\": 45,\n \"body\": \"Could you confirm who will lead accessibility testing? Please respond by Friday.\"\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")
.header("Authorization", "Bearer <token>")
.header("zivio-tenant-id", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"supplier_id\": 45,\n \"body\": \"Could you confirm who will lead accessibility testing? Please respond by Friday.\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.zivio.net/api/v4/projects/{id}/conversations")
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 \"supplier_id\": 45,\n \"body\": \"Could you confirm who will lead accessibility testing? Please respond by Friday.\"\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": 87,
"sender": {
"id": 1,
"name": "Jane Smith",
"side": "client"
},
"body": "Could you confirm who will lead accessibility testing?",
"sent_at": "2026-07-01T12: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": "supplier_not_engaged",
"message": "Supplier 45 has no engagement on project 123 - invite them first (POST /projects/{id}/invitations)"
}Message a supplier about a project (start or continue their thread)
Sends a message to the named supplier on their project thread, mirroring the web UI’s messaging: it starts the conversation if none exists or appends to the existing one — the same call either way, so retries cannot duplicate threads. Recipients (all the supplier’s users plus the project’s messaging members) are notified by email automatically. The supplier must already be engaged on the project (invited, matched, or bidding) — invite first via POST /projects//invitations otherwise.
curl --request POST \
--url https://api.zivio.net/api/v4/projects/{id}/conversations \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'zivio-tenant-id: <api-key>' \
--data '
{
"supplier_id": 45,
"body": "Could you confirm who will lead accessibility testing? Please respond by Friday."
}
'import requests
url = "https://api.zivio.net/api/v4/projects/{id}/conversations"
payload = {
"supplier_id": 45,
"body": "Could you confirm who will lead accessibility testing? Please respond by Friday."
}
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({
supplier_id: 45,
body: JSON.stringify('Could you confirm who will lead accessibility testing? Please respond by Friday.')
})
};
fetch('https://api.zivio.net/api/v4/projects/{id}/conversations', 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",
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([
'supplier_id' => 45,
'body' => 'Could you confirm who will lead accessibility testing? Please respond by Friday.'
]),
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"
payload := strings.NewReader("{\n \"supplier_id\": 45,\n \"body\": \"Could you confirm who will lead accessibility testing? Please respond by Friday.\"\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")
.header("Authorization", "Bearer <token>")
.header("zivio-tenant-id", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"supplier_id\": 45,\n \"body\": \"Could you confirm who will lead accessibility testing? Please respond by Friday.\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.zivio.net/api/v4/projects/{id}/conversations")
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 \"supplier_id\": 45,\n \"body\": \"Could you confirm who will lead accessibility testing? Please respond by Friday.\"\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": 87,
"sender": {
"id": 1,
"name": "Jane Smith",
"side": "client"
},
"body": "Could you confirm who will lead accessibility testing?",
"sent_at": "2026-07-01T12: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": "supplier_not_engaged",
"message": "Supplier 45 has no engagement on project 123 - invite them first (POST /projects/{id}/invitations)"
}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
Body
Response
The message sent, with its conversation
The response is of type object.

