Skip to content
Documentation
pozyskajpacjentaDocs

API referenceConversations

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 (conversationId of 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[].address on 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 messageId of 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

StatuscodeWhen
400invalid_bodyBoth text and templateId
401No Authorization header
401Unknown or rotated key
403missing_scopeThe key can read but not send
404not_foundUnknown id, another clinic's conversation or an archived lead's
409opted_outThe patient opted out of e-mail
409chat_not_startedThe patient never wrote on the chat
429Over 120 requests in this minute
502send_failedThe WhatsApp provider did not take the message

Branch on the status and code, never on the Polish error text. All statuses in Errors.

400 · Both text and templateId
{
  "error": "podaj text albo templateId (jedno z nich)",
  "code": "invalid_body"
}
401 · No Authorization header
{
  "error": "missing Authorization: Bearer <klucz z panelu Ustawienia>"
}
401 · Unknown or rotated key
{
  "error": "invalid key"
}
403 · The key can read but not send
{
  "error": "Klucz nie ma uprawnienia conversations:write. Placówka nadaje je w panelu: Ustawienia › Integracje › Klucz API.",
  "code": "missing_scope",
  "scope": "conversations:write"
}
404 · Unknown id, another clinic's conversation or an archived lead's
{
  "error": "Nie ma takiej rozmowy.",
  "code": "not_found"
}
409 · The patient opted out of e-mail
{
  "error": "Pacjent wypisał się z tego kanału.",
  "code": "opted_out"
}
409 · The patient never wrote on the chat
{
  "error": "Pacjent nie pisał na czacie.",
  "code": "chat_not_started"
}
429 · Over 120 requests in this minute
{
  "error": "przekroczono limit 120 zapytań/min"
}
502 · The WhatsApp provider did not take the message
{
  "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-Remaining and RateLimit-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.