Skip to content
Documentation
pozyskajpacjentaDocs

Concepts

Booking modes

Online booking, a request for a time, an external widget or the phone: what each clinic uses and what your frontend does.

A clinic takes bookings where its calendar already is, so your frontend has to follow the clinic's mode. This page lists the modes, how to recognise each in the API, and which endpoints to call.

Which mode a clinic uses

The clinic's site shows exactly one booking call to action per page. Read the blocks of its pages (GET /content) and follow the same order of priority:

ModeThe clinic's calendarOn the clinic's siteYour frontend
Online bookingIn the panel (the clinic's schedule)block booking.v1catalog, slots, book
ZnanyLekarz widgetAt ZnanyLekarzblock znanylekarz-embed.v1embed the same widget with the block's props
Booksy widgetAt Booksyblock booksy-embed.v1embed the same widget with the block's props
PhoneAnywhere, reception answersthe page's cta is a tel: linkshow the phone from site.profile.phone
Request a timeIn the clinic's own practice softwareblock lead-form.v1 with mode: "time-request"POST /leads with timeRequest

A plain enquiry form (lead-form.v1 without a mode, or mode: "contact") is the fallback: send it to POST /leads. Bookings through ZnanyLekarz or Booksy go to those services, not to this API.

Online booking

The clinic keeps its schedule in the panel; the API books into the same calendar the clinic's site uses, so there is no second schedule to sync.

  1. Catalog. GET /booking/catalog lists the active services and specialists. A service with an empty resourceIds cannot be booked today. A treatment points at its service with bookingServiceId, a team member at their specialist with bookingResourceId, so a "book" button on a treatment page knows its service.
  2. Slots. GET /booking/slots returns free slots of a service for up to 31 days, counted live (never cached). Omit resource for any specialist. Times are UTC epoch seconds; localDate and localStartMin give the clinic's local date and minute (Europe/Warsaw) for display.
  3. Book. POST /booking/appointments with the slot's startAt and resourceId and the patient's name and phone. Reception gets a "Nowa wizyta" e-mail with an .ics file; when you send the patient's e-mail, the patient gets the confirmation and reminder e-mails the clinic's site sends.

When someone takes the slot between your two calls you get 409 with error: "slot_taken" and slots, that day's free slots right now: offer one of them, nothing was stored. Sending the same data again within 5 minutes returns the existing appointment instead of booking twice, so a retry after a timeout is safe.

TypeScript
import { isSlotTaken } from "@pozyskajpacjenta/sdk";

try {
  await client.createAppointment({ serviceId, resourceId, startAt, patient });
} catch (error) {
  if (isSlotTaken(error)) showSlots(error.slots);
  else throw error;
}

Request a time

Since API 1.9.0, clinics that keep their calendar in their own practice software ask for a time instead of showing free slots: the patient picks a treatment, optionally a doctor, and 1 to 3 preferred windows. Reception gets a "Prośba o termin" e-mail, confirms one time or proposes another in the panel, and enters the visit in its own system; the patient hears back by e-mail when they left an address. Nothing is synced with the clinic's calendar.

Send it as timeRequest on POST /leads:

Request body
{
  "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"
  }
}
  • Windows are in Polish time. The clinic's site offers parts of the day: morning 08:00–12:00, midday 12:00–15:00, afternoon 15:00–18:00 and evening 18:00–20:00, and only on days and parts of the day the clinic is open (site.profile.openingHours). The SDK exports TIME_OF_DAY and windowFromPart(date, part) to build them.
  • serviceSlug and doctorSlug come from GET /services and GET /doctors; without slugs send serviceName or doctorName as text.
  • Windows in the past or more than 180 days ahead are dropped, and date has to be a real calendar day.
  • An invalid request never loses the lead: it is stored without the request and the response carries warnings: ["invalid_time_request"]. With ?strict=1 you get 400 invalid_time_request instead.

The response echoes the stored request with status: "pending"; reception then confirms a time or proposes another in the panel.

Phone and NFZ

Services under the public health fund are phone-only. A catalog service with nfz: true is for information: show it with a note that booking is by phone and the clinic's phone number. GET /booking/slots and POST /booking/appointments answer 400 with code: "nfz_phone_only" for it, and nothing is stored.