Umovi integration platform
Booking flow
Recommended sequence
Use startsAt consistently for the proposed time in reservation requests, returned slots, and reservation responses.
- Resolve the requested service and optional employee with
POST /catalog/resolve. - If the result is ambiguous, ask the user to choose instead of silently taking the first match.
- For caller recognition, use
POST /clients/identifywith an exact phone number. Withclients:read, a unique match also includes the client profile, so a second search is unnecessary. Use/clients/searchfor broader name, email or phone searches. - Call
/slotsfor a known range or/slots/first-availablefor the nearest appointment. - For a matched provider client, call
/reservations/duplicate-checkfor the proposed service and time. If a similar reservation exists, ask whether another reservation is intended. - Confirm the selected service, employee and time with the user.
- Create the reservation with a stable
Idempotency-Key. - Subscribe to webhooks for changes made outside your integration.
Catalog service and employee searches rank exact names first, then prefixes, aliases, and conservative typo matches. Service lookup also uses provider service aliases, assigned service type names and translations, suggested phrases, and translated platform categories when these are configured. A category match ranks below a direct service-name match. Resolve returns multiple candidates and ambiguous: true when it cannot choose safely; ask the caller to select one instead of taking the first result. Client search supports typo-tolerant suggestions for authorised workflows, but POST /clients/identify requires an exact E.164 phone match and must not infer identity from a similar name or number.
Caller recognition
All short paths on this page are relative to /integrations/v1/providers/{providerId}.
POST /clients/identify requires clients:identify and accepts { "phone": "+48123456789" }. Send an international E.164 phone number. Without clients:read, the response contains only status and providerClientId:
| Status | Result |
|---|---|
MATCHED | Exactly one provider client matched; providerClientId is its UUID. |
NOT_FOUND | No match; providerClientId is null. |
AMBIGUOUS | Multiple matches; providerClientId is null. |
Caller ID is a recognition signal, not proof of identity. Do not use it alone to disclose bookings or authorize changes. Do not choose an arbitrary record after an ambiguous result.
When creating a reservation, supply exactly one of providerClientId, client, or walkIn: true. Use providerClientId for an existing record within this provider; use client with firstName and at least an email address or phone number for contact-based creation. Use walkIn: true for an anonymous reservation without a client record.
Confirmation of client data use
Every POST /reservations request with client or providerClientId must include
clientDataUseConfirmed: true. The integration operator confirms that it has a
lawful basis to supply these contact details for handling the reservation. This
is an operational attestation, not marketing consent and not proof that the
client personally supplied a GDPR consent statement.
{
"serviceId": "67bb6ca0-b376-40e8-ae99-169db1db6e65",
"employeeSelectionMode": "ANY",
"startsAt": "2026-11-02T10:00:00+01:00",
"clientDataUseConfirmed": true,
"client": {
"firstName": "Jan",
"email": "jan@example.com"
}
}The same flag is required when using an existing providerClientId, even if that
contact was previously confirmed. Missing, false, null, string or numeric values
are rejected with HTTP 400 before creating or restoring a contact or reservation.
A successful client resolution sets the contact's confirmation timestamp if it
was missing; an existing timestamp is preserved. Restoring a deleted contact
records a new confirmation. The creation audit records the attestation with the integration identity and
request context. Marketing remains independent and is never enabled by this flag. Existing contacts are not automatically backfilled.
Anonymous walkIn: true requests without client or providerClientId can omit
the flag because they create no client record.
Contract change: update REST callers to explicitly supply the boolean true
before deploying this requirement. Retrying an existing request must retain the
same body and idempotency key; old requests lacking the flag are rejected even
when their idempotency key was used before. Do not replay a modified request with
an old key; reconcile its earlier result first.
The public MCP tools keep their existing inputs and confirmation flow. The gateway supplies the operational attestation internally after an explicit booking request; required user acknowledgement of the provider data-sharing notice remains separate. No extra MCP parameter or confirmation step is introduced.
Similar reservation check
When clients/identify returns one providerClientId, a voicebot can check for a possible duplicate before creating a reservation. This requires reservations:read:
POST /reservations/duplicate-check
Authorization: Bearer <access_token>
Content-Type: application/json
{
"providerClientId": "65aa7b83-d328-428d-b220-8932f4ce37ca",
"serviceId": "67bb6ca0-b376-40e8-ae99-169db1db6e65",
"startsAt": "2026-10-02T10:00:00+02:00",
"windowMinutes": 1440
}The response is { "possibleDuplicate": true } or false. The check looks for a reservation for the same provider client and service within the symmetric time window around startsAt. Only PENDING and CONFIRMED reservations whose start is still in the future count. Past and already-started reservations are excluded, even if their status has not been updated. windowMinutes defaults to 1440 (24 hours) and accepts 1–1440. The endpoint returns no reservation identifiers or contact details.
For a trusted workflow that needs reservation details, GET /reservations accepts providerClientId together with serviceId, employeeId, startsAfter, startsBefore, status, sortOrder (ASC or DESC), page, and pageSize. It also requires reservations:read. For example:
GET /reservations?providerClientId=65aa7b83-d328-428d-b220-8932f4ce37ca&serviceId=67bb6ca0-b376-40e8-ae99-169db1db6e65&status=CONFIRMED
Authorization: Bearer <access_token>To find the most recent completed service for a verified returning client, filter by providerClientId, status=COMPLETED, and startsBefore (the current time), then request sortOrder=DESC&pageSize=1. Resolve the returned serviceId against the current catalog before offering a new slot; a historical service may have been removed or made unavailable.
Caller ID alone is not identity verification. Do not disclose reservation details to a caller solely because their number matched. A positive duplicate check should trigger a short confirmation that another reservation is intended, without reading out details of the earlier reservation unless identity has been verified separately.
This check is advisory: another reservation can be created between the check and creation. Keep using a stable Idempotency-Key for each logical creation attempt; it prevents retrying that attempt from creating another reservation, while duplicate checking looks for earlier similar reservations.
Anonymous walk-in reservations
An authorised integration with reservations:create can create an anonymous reservation when a caller does not provide usable contact details. Set walkIn: true and omit both providerClientId and client:
POST /reservations
Authorization: Bearer <access_token>
Idempotency-Key: voicebot-call-7d8f4a-attempt-1
Content-Type: application/json
{
"serviceId": "67bb6ca0-b376-40e8-ae99-169db1db6e65",
"employeeSelectionMode": "ANY",
"startsAt": "2026-10-02T10:00:00+02:00",
"walkIn": true,
"internalNote": "Caller not identified; ask for their name at reception."
}The reservation has no client account or provider-client record, so Umovi cannot send client notifications or offer the client self-service management for it. A walk-in has no providerClientId, so the client-specific duplicate check cannot be used for that reservation. Use a stable Idempotency-Key to protect retries.
If the caller later provides enough information to select an existing provider client, an integration with reservations:update can attach that record:
POST /reservations/{reservationId}/assign-client
Authorization: Bearer <access_token>
Content-Type: application/json
{ "providerClientId": "65aa7b83-d328-428d-b220-8932f4ce37ca" }The target client must belong to the same provider. Only an unassigned walk-in can be linked; the operation returns 409 if the reservation already has a client and 404 if the reservation or provider client does not exist. The assignment is recorded in the reservation's history. If the provider client is linked to a Umovi account, earlier staff-only messages remain hidden from that account. Normal client notifications may start after assignment when the reservation is still eligible for them. The provider team can also perform this operation in its workspace.
Client identity and account claims
Reservation contact data belongs first to the provider. When an integration creates a reservation using an email address or phone number, Umovi creates or reuses a provider-scoped client record. It does not create a global Umovi user or silently link the contact to an existing account.
If that provider client has already been claimed by an authenticated Umovi client, the new reservation remains linked to that account. Otherwise the booking is treated as a guest reservation. Guest email communication contains a secure claim link, so the recipient can create an account or sign in and explicitly attach the provider record and its bookings later.
Do not treat a matching email address as proof that the person submitting the reservation owns an existing Umovi account.
The same account-claim flow is used for manually created provider contacts, Integration API contacts and guest reservation emails. Internally, Umovi checks exact verified email and phone matches against active client accounts. An existing match receives a sign-in-and-connect invitation; no match receives a registration invitation. Conflicting matches require correction before linking. Account matches are not exposed by provider client search or by the Integration API.
Opening an invitation or signing in does not link records. The client must explicitly
confirm the connection to the named provider, including current and past reservations.
The email opens /account-link?token=… on the configured AUTH_PORTAL_URL.
Login, registration, 2FA, social login and the explicit linking step stay in the auth
portal. A newly registered client first verifies their email, then returns to confirm
the connection. The claim endpoint requires confirmed: true. Client web and the
native apps do not handle this claim flow; no mobile update is required.
Marketing remains optional and separate;
omitting a marketing choice preserves existing preferences. Internal notes stay private.
Account invitations use the existing platform-funded email outbox and mail queue.
Guest reservation emails retain their existing delivery and billing rules and contain
the relevant claim action, rather than triggering a second invitation.
Employee selection and time windows
For a selected employee, send employeeSelectionMode: "SPECIFIC" with employeeId. To let Umovi allocate an eligible available employee atomically, send employeeSelectionMode: "ANY" and omit employeeId. Availability results contain employeeIds; they do not reserve an employee until creation succeeds.
GET /slots accepts inclusive dateFrom/dateTo bounds spanning at most 31 days. Both slot endpoints accept optional local timeFrom/timeTo in HH:mm format and an IANA timezone. An end time must not precede the start time.
GET /slots/first-available also accepts inclusive dateFrom/dateTo, direction: "forward" | "backward" to choose the search direction, and slotPreference: "earliest" | "latest" to choose a slot on the resulting day. Its inDays horizon defaults to 30 and permits 1–90 days. For the last slot on one day, set both date bounds to that day and slotPreference: "latest". No availability returns null date, startsAt and label, with an empty employeeIds array.
Provider-only context
Use internalNote when the provider team needs operational context that must
not be shown to the client. For a voicebot reservation, it can record that the
caller was not matched to an existing provider client and include relevant
details the caller provided. It can also hold a demo request's company name,
website, team size and questions for the sales team. The note does not make an
anonymous walk-in identifiable or enable client notifications.
{
"serviceId": "67bb6ca0-b376-40e8-ae99-169db1db6e65",
"employeeId": "65aa7b83-d328-428d-b220-8932f4ce37ca",
"startsAt": "2026-09-03T08:30:00.000Z",
"client": {
"firstName": "Anna",
"lastName": "Nowak",
"email": "anna@example.com"
},
"internalNote": "Company: Example Studio\nTeam size: 12\nTopic: migration"
}The field accepts up to 2,000 characters. It is visible only on provider surfaces, is omitted from Integration API reservation reads and is never added to client notifications or the booking conversation. Do not put secrets or authentication data in it.
Idempotency
Generate one key for one logical reservation attempt. When a network timeout leaves the outcome unknown, retry the same request body with the same key.
Idempotency-Key: booking-attempt-7d8f4aKeys must contain 8–180 printable ASCII characters without whitespace. A repeated request with the same key and body returns the original reservation and includes Idempotency-Replayed: true. The reservation is not created again and creation webhooks or audit entries are not duplicated.
Do not reuse a key for a different customer, service or time. Reuse with a different body returns 409 with code IDEMPOTENCY_KEY_REUSED. Validation failures do not consume the key.
An external reservation identifier can be included in the body to make reconciliation easier. When Idempotency-Key is omitted and externalReservationId is present, Umovi derives the idempotency key from that external identifier for the authenticated integration client.
Slot races
Availability is a snapshot. A slot can be taken between lookup and reservation creation. Treat a conflict as an expected business event: fetch availability again and let the user select another time.
Catalog ambiguity
Catalog resolution returns candidates, valid service/employee pairs and a machine-readable state. When it reports an ambiguous service or employee, preserve the candidates and ask a short disambiguation question. Fuzzy matching is a fallback for speech and typing errors, not permission to book a low-confidence result.
Client profile during caller identification
A token with both clients:identify and clients:read receives an additional client object for a unique MATCHED result. It has the same provider-scoped profile schema as client search (including firstName and userFirstName). This lets a voicebot greet the caller by name without a second search request. The client property is omitted without clients:read, and for NOT_FOUND or AMBIGUOUS results. Never choose one of several matches automatically.
Provider time zone
GET /catalog returns timezone, the provider’s configured IANA time zone. Use it to interpret calendar dates and display appointment times. A receptionist flow should not ask the caller to configure a time zone. Slot queries may omit timezone to use the provider default.
Availability when rescheduling
Pass rescheduleReservationId to GET /slots together with the reservation’s existing serviceId and employeeId. This excludes the reservation itself from availability conflicts, including its buffer. Requires slots:read, reservations:read, and reservations:reschedule for the same provider. Only pending or confirmed reservations may use this context. The final reschedule operation still validates the provider policy and current availability.
Caller messages and internal notes
POST /integrations/v1/providers/{providerId}/reservations accepts two separate optional fields:
initialMessage: the caller’s first message in the booking conversation (1–2,000 characters after trimming). Supported for an existing client, a guest contact, and an anonymouswalkIn: truereservation. It is saved in the same transaction as the reservation. Replaying the sameIdempotency-Keydoes not create another message.internalNote: operational context supplied by the integration, visible only to the provider team. Do not use it for a caller-authored message.
The initial message is marked as relayed by the integration, without claiming an authenticated user authored it. It does not create a client record or account for a walk-in, and a walk-in receives no client notification. Existing conversation access and walk-in reply restrictions still apply.
{
"serviceId": "00000000-0000-4000-8000-000000000001",
"employeeSelectionMode": "ANY",
"startsAt": "2026-10-15T10:00:00+01:00",
"walkIn": true,
"initialMessage": "Please call me when you are ready.",
"internalNote": "Caller not identified by phone; booked through the voicebot."
}