Skip to content
Documentation
pozyskajpacjentaDocs

API referenceLeads

Create a lead

POST/api/v1/leads

Since
1.0.0
Auth
Bearer key
operationId
createLead

Stores a patient enquiry from your own form in the clinic's CRM. It follows the same path as an enquiry from the clinic's site: reception gets an e-mail, and a configured webhook receives lead.created. Call it from your server: the key is a secret. Field names are matched loosely (Polish and English aliases, any letter case), unknown fields are kept, and a lead without a phone or e-mail is stored with a warning unless you ask for ?strict=1.

Request

Send the clinic's key in Authorization: Bearer (see API keys).

Query parameters

  • strictstringsince 1.3.0

    Reject a lead without a phone and e-mail (400 missing_contact), and one with an invalid timeRequest (400 invalid_time_request), instead of storing it with warnings.

    One of 1true

Body application/json

The enquiry as JSON.

  • namestring

    Patient's name. Aliases: full_name, fullname, imie, imię, imie_nazwisko.

  • phonestring

    Phone number. Aliases: tel, telefon, phone_number, numer.

  • emailstring

    E-mail address. Aliases: e-mail, mail.

  • messagestring

    The patient's message. Aliases: wiadomosc, wiadomość, msg, comment, opis.

  • utm_sourcestring

    Campaign source, stored with the lead's tracking data.

  • utm_mediumstringsince 1.1.0

    Campaign medium.

  • utm_campaignstringsince 1.1.0

    Campaign name.

  • gclidstring

    Google Ads click id.

  • fbclidstring

    Meta click id.

  • privacyNoticeVersionstringsince 1.7.0

    The version of the information clause your form showed (from GET /privacy). Stored with the submission and its time; without it the submission is stored without a clause version.

  • marketingConsentbooleansince 1.7.0

    true when the patient ticked the optional marketing box (label marketingText from GET /privacy). Recorded for SMS (when there is a phone) and e-mail (when there is an address) together with the clause version. It counts only with a valid privacyNoticeVersion: without it no consent is recorded and an earlier opt-out stays. For a returning patient booking an appointment, the e-mail consent covers only the address stored with their lead (or the given address when the lead had none). Omit it, or send false, when the box was not ticked.

  • timeRequestTimeRequestInputsince 1.9.0

    A request for a time: the patient's preferred windows, for clinics that keep their calendar in their own system.

    Fields (5)
    • windowsTimeWindow[]required

      1 to 3 preferred windows.

      Fields of each item (3)
      • datestring (date)required

        Day, YYYY-MM-DD.

      • fromstringrequired

        Start, HH:MM.

      • tostringrequired

        End, HH:MM.

    • serviceSlugstring

      Slug of a published treatment (GET /services).

    • doctorSlugstring

      Slug of a published team member (GET /doctors); omit for any doctor.

    • serviceNamestring

      Treatment name as text, when your frontend has no slug.

    • doctorNamestring

      Doctor's name as text, when your frontend has no slug.

Polish field names are accepted too

{
  "imie": "Anna Kowalska",
  "telefon": "+48 600 100 200",
  "wiadomosc": "Proszę o kontakt."
}

A lead with a request for a time (API 1.9.0)

{
  "name": "Jan Nowak",
  "phone": "+48 600 100 200",
  "timeRequest": {
    "windows": [
      {
        "date": "2026-10-05",
        "from": "08:00",
        "to": "12:00"
      },
      {
        "date": "2026-10-06",
        "from": "15:00",
        "to": "18:00"
      }
    ],
    "serviceSlug": "higienizacja",
    "doctorSlug": "maria-zielinska"
  }
}
curl -X POST "https://app.pozyskajpacjenta.pl/api/v1/leads" \
  -H "Authorization: Bearer $PP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Anna Kowalska",
    "phone": "+48 600 100 200",
    "email": "anna.kowalska@example.com",
    "message": "Proszę o kontakt w sprawie higienizacji.",
    "utm_source": "google",
    "utm_medium": "cpc",
    "utm_campaign": "higienizacja-jesien",
    "formularz": "landing-higienizacja"
  }'
import { PozyskajPacjentaClient } from "@pozyskajpacjenta/sdk";

const client = new PozyskajPacjentaClient({
  apiKey: process.env.PP_API_KEY ?? "",
  baseUrl: "https://app.pozyskajpacjenta.pl",
});

const { leadId, warnings } = await client.createLead({
  name: "Anna Kowalska",
  phone: "+48 600 100 200",
  email: "anna.kowalska@example.com",
  message: "Proszę o kontakt w sprawie higienizacji.",
  utm_source: "google",
  utm_medium: "cpc",
  utm_campaign: "higienizacja-jesien",
  formularz: "landing-higienizacja",
});
<?php
$body = array(
    'name'         => 'Anna Kowalska',
    'phone'        => '+48 600 100 200',
    'email'        => 'anna.kowalska@example.com',
    'message'      => 'Proszę o kontakt w sprawie higienizacji.',
    'utm_source'   => 'google',
    'utm_medium'   => 'cpc',
    'utm_campaign' => 'higienizacja-jesien',
    'formularz'    => 'landing-higienizacja',
);

$response = wp_remote_post(
    'https://app.pozyskajpacjenta.pl/api/v1/leads',
    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

200The lead was stored.

  • okbooleanrequired

    Always true.

  • leadIdstringrequired

    Id of the lead in the clinic's CRM.

  • warningsstring[]since 1.3.0

    Only when there is a warning. missing_contact: stored, but without a phone or e-mail, so nobody can call back. invalid_time_request: stored without the request for a time, because timeRequest failed validation.

    One of missing_contactinvalid_time_request

  • timeRequestLeadTimeRequestsince 1.9.0

    The stored request for a time, when you sent a valid one.

    Fields (5)
    • idstringrequired

      Id of the request.

    • statusstringrequired

      pending until reception acts; confirmed (a time was confirmed), proposed (reception proposed another time) or cancelled.

      One of pendingconfirmedproposedcancelled

    • windowsTimeWindow[]required

      The windows that were stored.

      Fields as in TimeWindow above.

    • servicestring | nullrequired

      Name of the matched treatment, or the text you sent.

    • doctorstring | nullrequired

      The doctor (matched or as sent); null for any doctor.

Lead stored

{
  "ok": true,
  "leadId": "rS2bIhedVSStSQLc7DL_T"
}

Stored without a phone or e-mail

{
  "ok": true,
  "leadId": "Sj2mY5tuT1UZP0QfgdvLH",
  "warnings": [
    "missing_contact"
  ]
}

Stored with the request for a time

{
  "ok": true,
  "leadId": "PydHg_es-Kqw5bs1Y0-wu",
  "timeRequest": {
    "id": "tqf-L61QFuCLO2ZXCM1OL",
    "status": "pending",
    "windows": [
      {
        "date": "2026-10-05",
        "from": "08:00",
        "to": "12:00"
      },
      {
        "date": "2026-10-06",
        "from": "15:00",
        "to": "18:00"
      }
    ],
    "service": "Higienizacja",
    "doctor": "lek. dent. Maria Zielińska"
  }
}

Stored without an invalid request for a time

{
  "ok": true,
  "leadId": "J_ashz8nW11Am5rfS90rB",
  "warnings": [
    "invalid_time_request"
  ]
}

Errors

StatuscodeWhen
400invalid_jsonThe body is not JSON
400invalid_bodyThe JSON is not an object
400missing_contact?strict=1 and no phone or e-mail
400invalid_time_request?strict=1 and an invalid request for a time
401No Authorization header
401Unknown or rotated key
429Over 120 requests in this minute

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

400 · The body is not JSON
{
  "error": "invalid JSON",
  "code": "invalid_json"
}
400 · The JSON is not an object
{
  "error": "treść musi być obiektem JSON z danymi leada",
  "code": "invalid_body"
}
400 · ?strict=1 and no phone or e-mail
{
  "error": "wymagany kontakt: telefon (pole phone/tel/telefon/phone_number/numer, min. 6 cyfr) albo e-mail (pole email/e-mail/mail)",
  "code": "missing_contact"
}
400 · ?strict=1 and an invalid request for a time
{
  "error": "timeRequest: windows.0.date: nie ma takiego dnia w kalendarzu",
  "code": "invalid_time_request"
}
401 · No Authorization header
{
  "error": "missing Authorization: Bearer <klucz z panelu Ustawienia>"
}
401 · Unknown or rotated key
{
  "error": "invalid key"
}
429 · Over 120 requests in this minute
{
  "error": "przekroczono limit 120 zapytań/min"
}

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.createLead(lead: LeadInput, options?: { strict?: boolean }): Promise<LeadCreated>

Throws ApiError (400) with code; with { strict: true } also missing_contact and invalid_time_request.

The TypeScript tab above uses it. Install and errors: TypeScript SDK.