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. Since 1.16.0 the message goes to the address the patient last wrote from on that channel, or to to (one of identities of GET /conversations/{id}); an address that opted out gets nothing. 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, chat (the chat on the clinic's site) or, since 1.15.0, messenger (the clinic's Facebook Page). Pick one GET /conversations/{id} lists as available.
One of
emailsmswhatsappinstagramchatmessenger - textstring
The text, 1 to 4000 characters, trimmed. An SMS carries its first 480 characters.
- templateIdstring
A template id from GET /conversations/{id}
templates. - tostring
Since 1.16.0: the address to send to, one of the conversation's
identities[].addresson that channel. Omit it to answer where the patient last wrote from (replyDefault).
A template of the clinic, filled in for the patient
{
"channel": "sms",
"templateId": "nHv4l_ALArlouEEfxHLOO"
}Since 1.16.0: to another of the patient's numbers
{
"channel": "sms",
"text": "Dziękujemy, do zobaczenia w czwartek o 10:00.",
"to": "+48600100200"
}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, chat (the chat on the clinic's site) or, since 1.15.0, messenger (the clinic's Facebook Page).
One of
emailsmswhatsappinstagramchatmessenger - 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. queued is a reply from the clinic's panel held on the server for a few seconds before it leaves: it may still vanish from the conversation when the person takes it back, and a message taken back is in no answer of the API. A message you send through the API is never held.
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.