Send a message
POST/api/v1/conversations/{id}/messages
- Since
- 1.14.0
- Auth
- Bearer key
- operationId
- sendMessage
Sends a message to the conversation's patient on the channel you choose, through the same path as a reply from the clinic's inbox: it is stored in the conversation, reaches the patient through the clinic's own channel account, and fires the message.sent webhook. The rules are the inbox's: a patient who opted out of a channel gets nothing on it; WhatsApp and Instagram take free text only within 24 hours of the patient's last message on that channel; the chat only after the patient wrote on it. A refusal is a 409 with its code, and nothing was sent. Send text, or templateId to send one of the clinic's templates filled in for the patient. Needs the scope conversations:write; every attempt is written to the clinic's audit log.
Request
Send the clinic's key in Authorization: Bearer (see API keys).
Path parameters
- idstringrequired
The conversation id (
conversationIdof webhook events).
Body application/json
The channel and the text or the template, as JSON.
- channelstringrequired
The channel: email, sms, whatsapp, instagram or chat (the chat on the clinic's site). Pick one GET /conversations/{id} lists as available.
One of
emailsmswhatsappinstagramchat - textstring
The text, 1 to 4000 characters, trimmed. An SMS carries its first 480 characters.
- templateIdstring
A template id from GET /conversations/{id}
templates.
A template of the clinic, filled in for the patient
{
"channel": "sms",
"templateId": "nHv4l_ALArlouEEfxHLOO"
}curl -X POST "https://app.pozyskajpacjenta.pl/api/v1/conversations/Vb3nQ8sLk2PzX5wRt9YcE/messages" \
-H "Authorization: Bearer $PP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"channel": "sms",
"text": "Dziękujemy, do zobaczenia w czwartek o 10:00."
}'import { PozyskajPacjentaClient, isSendRefused } from "@pozyskajpacjenta/sdk";
const client = new PozyskajPacjentaClient({
apiKey: process.env.PP_API_KEY ?? "",
baseUrl: "https://app.pozyskajpacjenta.pl",
});
try {
const { message } = await client.sendMessage("Vb3nQ8sLk2PzX5wRt9YcE", {
channel: "sms",
text: "Dziękujemy, do zobaczenia w czwartek o 10:00.",
});
console.log(message.id);
} catch (error) {
// 409: the inbox's rules said no (opt-out, a closed 24 h window…); nothing was sent
if (isSendRefused(error)) console.log(error.code);
else throw error;
}<?php
$body = array(
'channel' => 'sms',
'text' => 'Dziękujemy, do zobaczenia w czwartek o 10:00.',
);
$response = wp_remote_post(
'https://app.pozyskajpacjenta.pl/api/v1/conversations/Vb3nQ8sLk2PzX5wRt9YcE/messages',
array(
'headers' => array(
'Authorization' => 'Bearer ' . getenv( 'PP_API_KEY' ),
'Content-Type' => 'application/json',
),
'body' => wp_json_encode( $body ),
'timeout' => 10,
)
);
if ( is_wp_error( $response ) ) {
error_log( $response->get_error_message() );
return;
}
$status = wp_remote_retrieve_response_code( $response );
$data = json_decode( wp_remote_retrieve_body( $response ), true );Response
201The message was sent.
- messageConversationMessagerequired
The message as stored (status sent).
Fields (12)
- idstringrequired
Message id: the
messageIdof webhook events. - conversationIdstringrequired
The conversation it belongs to.
- channelstringrequired
The channel: email, sms, whatsapp, instagram or chat (the chat on the clinic's site).
One of
emailsmswhatsappinstagramchat - directionstringrequired
inbound: from the patient. outbound: from the clinic (the inbox, an automation or this API). outbound_echo: the clinic replied from the WhatsApp or Instagram app itself.
One of
inboundoutboundoutbound_echo - statusstringrequired
received for every inbound message. Outbound messages move forward only: queued, sent, delivered, read; or failed.
One of
queuedsentdeliveredreadfailedreceived - subjectstring | nullrequired
E-mail subject; null on other channels.
- textstring | nullrequired
The text; null for a message with attachments only.
- attachmentsMessageAttachment[]required
Files that came with the message.
Fields of each item (3)
- filenamestringrequired
File name.
- mimestringrequired
Media type, e.g. image/jpeg.
- sizeintegerrequired
Size in bytes.
- createdAtstring (date-time)required
When the message was stored.
- sentAtstring (date-time) | nullrequired
Outbound: when the provider accepted it; null otherwise.
- deliveredAtstring (date-time) | nullrequired
Outbound: when it reached the patient's device, where the channel reports it.
- readAtstring (date-time) | nullrequired
Inbound: when someone at the clinic opened it. Outbound: the provider's read receipt, where the channel reports it.
Sent by SMS
{
"message": {
"id": "gbOmbsye_UFcdrpeY4LuZ",
"conversationId": "Vb3nQ8sLk2PzX5wRt9YcE",
"channel": "sms",
"direction": "outbound",
"status": "sent",
"subject": null,
"text": "Dziękujemy, do zobaczenia w czwartek o 10:00.",
"attachments": [],
"createdAt": "2026-10-12T08:30:49.000Z",
"sentAt": "2026-10-12T08:30:49.000Z",
"deliveredAt": null,
"readAt": null
}
}Sent from a template
{
"message": {
"id": "FcGPiB1Bsve8C3tp3Agyt",
"conversationId": "Vb3nQ8sLk2PzX5wRt9YcE",
"channel": "sms",
"direction": "outbound",
"status": "sent",
"subject": null,
"text": "Dzień dobry Anna, wolne terminy i rezerwacja online: https://wzorcowa.pozyskajpacjenta.pl Klinika Wzorcowa",
"attachments": [],
"createdAt": "2026-10-12T08:30:50.000Z",
"sentAt": "2026-10-12T08:30:50.000Z",
"deliveredAt": null,
"readAt": null
}
}Errors
| Status | code | When |
|---|---|---|
| 400 | invalid_body | Both text and templateId |
| 401 | No Authorization header | |
| 401 | Unknown or rotated key | |
| 403 | missing_scope | The key can read but not send |
| 404 | not_found | Unknown id, another clinic's conversation or an archived lead's |
| 409 | opted_out | The patient opted out of e-mail |
| 409 | chat_not_started | The patient never wrote on the chat |
| 429 | Over 120 requests in this minute | |
| 502 | send_failed | The WhatsApp provider did not take the message |
Branch on the status and code, never on the Polish error text. All statuses in Errors.
{
"error": "podaj text albo templateId (jedno z nich)",
"code": "invalid_body"
}{
"error": "missing Authorization: Bearer <klucz z panelu Ustawienia>"
}{
"error": "invalid key"
}{
"error": "Klucz nie ma uprawnienia conversations:write. Placówka nadaje je w panelu: Ustawienia › Integracje › Klucz API.",
"code": "missing_scope",
"scope": "conversations:write"
}{
"error": "Nie ma takiej rozmowy.",
"code": "not_found"
}{
"error": "Pacjent wypisał się z tego kanału.",
"code": "opted_out"
}{
"error": "Pacjent nie pisał na czacie.",
"code": "chat_not_started"
}{
"error": "przekroczono limit 120 zapytań/min"
}{
"error": "Dostawca kanału nie przyjął wiadomości. Jest w rozmowie jako niewysłana.",
"code": "send_failed",
"retryable": true
}Rate limit
- 120 requests per minute per key, shared by every endpoint.
- Every response to a valid key carries
RateLimit-RemainingandRateLimit-Reset; see Rate limits.
SDK method
client.sendMessage(conversationId: string, input: SendMessageInput): Promise<MessageSent>
Throws ApiError 409 with the reason in code (isSendRefused); 502 send_failed.
The TypeScript tab above uses it. Install and errors: TypeScript SDK.