API

  • External calendars: Free/transparent provider events (for example Google Working Location or Outlook showAs: free) are not ingested and do not create Unavailability rows.
  • GET /v3/unavailability: Optional row fields external_transparency, external_event_type, external_busy for intervals from external calendar sync (null for other sources).
  • POST /v3/migration/sync: Imports v1 resource blocks (/setup/v1/resources/:id/blocks) as v3 recurring blocks per resource, with legacyId when the v1 block id is present.

Unavailability calendar rebuild & recurring-block timezones

Public docs: Unavailability blocks, Recurring blocks.

Unavailability calendar (GET /v3/unavailability)

  • Responses are one row per concrete interval, tagged with source (weekly, recurring, appointment, holiday, block) and the affected entity_type / entity_id. Rows are not merged.
  • The roundRobin query parameter is removed (previously unused for distinct merged modes).
  • ServiceId is optional; scope still requires at least one of LocationIds, ResourceIds, or ServiceId.
  • GET /v3/unavailability/calendar is deprecated and returns the same payload as GET /v3/unavailability — migrate callers to the canonical path.

Stored blocks

  • DELETE /v3/unavailability/blocks/:id removes an out-of-office (OOF) block by id (company ownership enforced). Use native flows for appointment-linked rows.

Recurring blocks

  • Rules persist iana with wall-clock startTime / endTime; expansions respect DST and fractional UTC offsets. Availability and calendar queries share this interpretation.

Integrators

  • Update any UI that assumed merged start_time/end_time only, or that passed roundRobin.
  • Expect snake_case fields on calendar rows (start_time, entity_type, …).

Dashboard — recurring unavailability on Availability tabs

See also Recurring blocks.

Merchant dashboard

On the Availability tab for locations, services, and resources, you can now manage recurring unavailable periods in addition to one-off blocks:

  • List recurring rules with schedule summary, wall-clock time window, timezone, and active date range.
  • Add or edit a rule (name, frequency, interval, start/end dates, times, weekday selection for weekly/biweekly, day-of-month for monthly; yearly repeats on the month and day of the start date).
  • Delete a rule with confirmation.

Behavior matches the existing /v3/unavailability/recurringBlock APIs (structured recurrence, not RFC 5545 RRULE strings). One-off blocks are still saved with the main Save action; recurring rules save immediately from the recurring-block dialog.

V1 → V3 migration sync expands service field coverage

Guide: Migrating from V1.

Migration

POST /v3/migration/sync now persists the full set of mappable Service fields when creating V3 services from V1, instead of only name, description, duration, weekly availability, and the schedule-vs-allocation type. Migrated services now also retain:

  • Duration options: durationSelect, durationMin, durationMax, durationInterval.
  • Book-ahead window: bookAheadUnit, bookAheadValue, bookInAdvance.
  • Capacity and limits: bookingLimit, bookingInterval, padding, bookingsPerSlot (from V1 maxCapacity), dailyBookingLimitCount, dailyBookingLimitMinutes, maxBookingLimit, maxResourceBookingLimit.
  • Fees: feeAmount, feeTaxable, cancellationFeeAmount, cancellationFeeTaxable, nonRefundable.
  • Other: imageUrl, showOnline, roundRobin (V1 integer mapped to V3 NONE / RANDOM / BALANCED / COMBINED), and custom fields (field1field10).

Type → availabilityType mapping (unchanged, now documented)

The mapping from V1 type to V3 availabilityType is unchanged but worth restating: V1 type=1 (Appointment) maps to V3 availabilityType=schedule; V1 type=2 (Event) maps to V3 availabilityType=allocation.

Re-running migration

Existing migrated tenants can backfill these fields by re-running POST /v3/migration/sync. The migration is idempotent on legacyId, so previously migrated services that already exist in V3 will be skipped — to pick up the expanded coverage on those rows, delete the V3 service (or update it manually) before re-syncing.

Fields still not migrated

Some V1 fields remain unmapped because V3 has no equivalent column or the concept has been retired (serviceGroupId/serviceGroupName, calendarId/calendarResourceGroupId, mediaPageUrl, defaultService, consumerPadding, maxGroupSize). For those, use native /v3/* endpoints to configure the equivalent V3 behavior after migration.

Allocation legacy IDs and mapping

Guides: Weekly allocations, Single allocations.

Mapping

  • GET /v3/mapping/ids accepts allocationId (V1 allocation ID). The response value is always a JSON array of matching V3 UUIDs (weekly and/or single allocation rows). Scalar keys (locationId, serviceId, etc.) remain a single UUID or null.

Allocations

  • POST /v3/singleAllocation/setSingleAllocations and POST /v3/weeklyAllocation/setWeeklyAllocations accept an optional legacyId on each allocation item so migrated data can retain the V1 identifier.

Existing Stage tenants can repopulate legacyId by re-running the V1 migration sync (or an allocation-only sync) so allocations are recreated from V1 with IDs attached.

Multi-resource template variables

  • Use case — Appointments that book more than one resource (e.g. couples or multi-provider sessions) can now show all resource names in custom email and SMS bodies, not only the first.
  • {{appointment.resources}} — Comma-separated names of every resource on the appointment.
  • {{#each resources}}...{{/each}} — Repeat a fragment once per resource. Inside the block, use bare keys like {{name}} or {{email}} for the current row; outer paths like {{appointment.time}} still work. Nested {{#each}} blocks are not supported in this release.
  • {{resources.0.name}}, {{resources.1.name}}, … — Optional indexed access when you want fixed slots in the copy.
  • Backward compatibility{{resource.name}}, {{appointment.resource}}, and {{customer.resource}} still refer to the first resource only, so existing templates do not change unless you edit them.

See the Notification guide for full merge syntax and examples.

Availability — allocation mode

  • Weekly schedule merge — Prevents a spurious full-day unavailability block (UTC 00:00–23:59) from merging with partial “closed outside open hours” blocks on the same calendar day, which could erase operating-hour gaps and return no slots (or the wrong slot set) when a SingleAllocation narrows the bookable window.
  • service_allocation_days — Avoids treating every date in a weekly allocation’s calendar range as a “covered” service day; that over-approximation could stop the “outside allocation” full-day guard from running and incorrectly open weekdays that the allocation pattern does not cover.

Company notification channels

  • Dashboard — Company settings — Under Company Info, Disable notifications is replaced by Disable email notifications and Disable SMS notifications. Each toggle suppresses only that channel org-wide (appointment and related outbound notifications); webhooks are unchanged.
  • API — Company model — Responses include disableEmailNotifications and disableSmsNotifications instead of disableNotifications. PATCH / POST bodies should send those fields when updating behavior per channel.
  • Backward compatibility — writes — If the body includes disableNotifications and neither disableEmailNotifications nor disableSmsNotifications, both channel flags are set from that single value (same semantics as before). The legacy field is deprecated in OpenAPI.
  • Database — Migration adds the two columns, copies values from disableNotifications, then drops disableNotifications. Deploy migrations before relying on the new API shape.
  • V1 shimdisableEmailAndSmsNotifications is true only when both email and SMS are disabled for the company (equivalent to “all outbound email/SMS off”).

What changed

  • Faster list endpointsGET /v3/locations, GET /v3/services, GET /v3/resources, and GET /v3/companies now read with a smaller include shape (the list responses no longer eager-load Address or Company objects) and count the total with a direct COUNT(*) on the base table when no association filter is applied. The response fields stay the same when you list without include; fetch a single resource (or pass include=services,resources / include=locations,resources etc.) to hydrate the full object graph. A short per-request Redis cache collapses dashboard refresh bursts into a single database round trip.

  • Company search is now a prefix match — The optional search query parameter on GET /v3/companies (used by the dashboard company switcher) now matches company names by prefix, backed by a functional lower-name index. A search for acme matches “Acme Corp” and “ACME Clinic” but no longer matches “Great Acme Holdings.” If you rely on the old substring behavior, switch your client to match by leading characters (for example, type the start of the company name rather than a substring).

  • Dashboard navigation — The company switcher debounces typed input a little longer and skips one-character queries (which match almost everything and only add load) before issuing a request. The locations, services, and resources dashboards no longer re-fetch the full company-wide lists on every row click; they prime reference data once per company context and leave warm caches alone.

Integration notes

  • Public API callers that pass include=services,resources (or analogous combinations) continue to receive the same nested payload on the list endpoints. Only the default (no-include) list shape trims the always-on Address/Company objects, which were never documented as part of the list payload.
  • The prefix search is the only customer-visible behavior change on GET /v3/companies?search=…. The parameter name, limit, and offset semantics are unchanged.
  • No migrations are required on the caller side. A Postgres functional index (company_lower_name_prefix_active_idx) is added by a platform migration for the new prefix search.

Separate SMS and email notification templates

  • Channel — Notification templates support channel: EMAIL or SMS. Existing rows default to EMAIL; SMS copy can differ while reusing the same merge variables and tooling as email.
  • Resolution — When sending SMS, the API uses an SMS template if present, otherwise falls back to the email template for that type and recipient (then platform defaults), so behavior stays unchanged until you add SMS-specific templates.
  • Sending — If a recipient uses both email and SMS (ALL), email and SMS bodies are built separately from the matching channel (or fallback).
  • APIGET / POST /v3/emailTemplates accept optional channel (default EMAIL). EXTERNAL_CALENDAR_EVENT cannot be saved as SMS (calendar descriptions stay email-only). Database: unique (CompanyId, type, recipientType, channel); run migrations before deploy.
  • PortalNotification templates page adds Email | SMS tabs; SMS body is what is sent as plain text after merge (subject is stored but not sent via SMS).

Dashboard RBAC

  • RolesOwner, Admin, User, and Resource dashboard roles now drive what company data and scheduling entities each login can read or change. Machine integrations using OAuth client credentials are unchanged: access remains scope-based (read/write as today); dashboard role flags on the synthetic principal do not apply.
  • User location scopeUser accounts may have locationAccessMode (COMPANY = all locations, or LOCATIONS with an allowedLocationIds list). Writes and list results for locations, services, resources, and appointments respect this scope (including company-scoped services vs location-linked services).
  • Resource role — A Resource login is tied to a linkedResourceId and may read non-secret company settings and address, read linked locations/services, and read/write only that resource row. Company responses for this role omit API keys, client secrets, and similar credentials.
  • User managementOwner may assign any role including Owner. Admin may not assign Owner, may not change another Admin’s role, and may promote User → Admin (per product rules). At least one Owner must remain; demoting the last owner continues to be rejected with the same error the dashboard expects.

API

  • setUsers / adjustUsers — Request bodies may include locationAccessMode, allowedLocationIds, and linkedResourceId per user; linkedResourceId is required when the role is resource and must belong to the company. OpenAPI and bundled openapi.json are updated.

Portal

  • NavigationAPI Settings is hidden for Resource roles; Company settings tabs that require user management are gated for User roles.
  • API SettingsUser roles can view the page but cannot regenerate or rotate credentials where the API forbids it; Resource roles are redirected away.
  • Company users — Forms support location scope, allowed locations, and resource linking, with client-side rules aligned to the admin matrix.

v3.1.0 Release

by ReadMe CLI

These notes describe customer-visible changes currently on staging compared to production as it runs today. They cover API and dashboard behavior only—not internal tooling, tests, or infrastructure.

Baseline: content reflects deltas relative to the 3.0.0 platform line on production; staging may include work slated for a future minor or patch release. Use staging to validate integrations before these changes reach production. Exact production timing will be communicated separately.

Features

  • Bookings per time slot (allocations) — Weekly and single allocations support an optional bookingsPerSlot value (minimum 1) where the API accepts it, so you can cap how many bookings may share the same slot for a given allocation. The dashboard allocations experience was updated to support this.
  • Weekly allocations: service and resource together — Creating a weekly allocation may include both ServiceId and ResourceId when your integration needs that combination. The API no longer rejects requests solely because both identifiers are present.
  • Service weekly unavailability — Service-level weekly unavailability can be associated with an optional resource when you need resource-specific unavailability in a service context.
  • Appointment reserve (RS) without hold expiryPOST /v3/appointment/reserve lets you place an appointment in reserved (RS) state when you need that outcome without relying on a short-lived hold window. Use this when your product flow reserves first and confirms later. OpenAPI and the appointment guides were updated for reserve, confirm, and calendar behavior alongside this.
  • Support requests (API) — The create-ticket request still supports the legacy shape (subject and body only). You can also send a structured request with additional fields (for example environment, product area, topics, and optional request metadata) when you want the support platform to receive a richer, labeled ticket. Existing simple integrations can keep using subject and body unchanged.

Improvements

  • Availability — Availability behavior for allocation-based scheduling was improved, including clearer handling in edge cases that involve resources, services, and validation of bookable slots. Availability and slot validation now apply bound parameters consistently through allocation-related SQL (including optional fallbacks where CTEs need them), which reduces subtle mismatches between computed slots and what the API will accept when booking.
  • Unavailability reasons — Additional reason values are supported where the API exposes unavailability reasons, giving more accurate classification without changing how you authenticate or call the API.
  • Legacy (v1-style) API usage — Integrations that rely on v1-compatible behavior benefit from compatibility and reliability improvements across those flows (no change to your obligation to use current credentials and endpoints as documented). The v1 reserve shim now sends a valid book payload and persists reserved (RS) state so downstream steps behave like native v3 flows.
  • Appointment by ID — The appointment-by-id response may include customer data so integrations and UI can show who the booking is for without an extra customer lookup in typical cases.
  • Calendars on delete — When deleting an appointment, the API loads resource-level external calendar configuration as needed so external cancellations align with the calendars actually in use.
  • Reserved (RS) state and delete — Delete routes handle RS appointments correctly for cancellation and permanent deletion expectations (including cases that previously left reserved rows or external state out of sync).
  • OpenAPI — The confirm appointment operation in OpenAPI now matches the live implementation; the bundled openapi.json was refreshed accordingly.

Dashboard

  • Allocations — Forms and flows for managing allocations align with the API changes above (including bookings-per-slot where applicable).
  • Support — The in-app support request experience supports the same legacy and structured options as the API, so users can submit either a short message or a structured request when the product presents those fields.

These notes describe customer-visible changes currently on staging compared to production as it runs today. They cover API and dashboard behavior only—not internal tooling, tests, or infrastructure.

Use staging to validate integrations before these changes reach production. Exact production timing will be communicated separately.

Features

  • Bookings per time slot (allocations) — Weekly and single allocations support an optional bookingsPerSlot value (minimum 1) where the API accepts it, so you can cap how many bookings may share the same slot for a given allocation. The dashboard allocations experience was updated to support this.
  • Weekly allocations: service and resource together — Creating a weekly allocation may include both ServiceId and ResourceId when your integration needs that combination. The API no longer rejects requests solely because both identifiers are present.
  • Service weekly unavailability — Service-level weekly unavailability can be associated with an optional resource when you need resource-specific unavailability in a service context.
  • Support requests (API) — The create-ticket request still supports the legacy shape (subject and body only). You can also send a structured request with additional fields (for example environment, product area, topics, and optional request metadata) when you want the support platform to receive a richer, labeled ticket. Existing simple integrations can keep using subject and body unchanged.

Improvements

  • Availability — Availability behavior for allocation-based scheduling was improved, including clearer handling in edge cases that involve resources, services, and validation of bookable slots.
  • Unavailability reasons — Additional reason values are supported where the API exposes unavailability reasons, giving more accurate classification without changing how you authenticate or call the API.
  • Legacy (v1-style) API usage — Integrations that rely on v1-compatible behavior benefit from compatibility and reliability improvements across those flows (no change to your obligation to use current credentials and endpoints as documented).

Dashboard

  • Allocations — Forms and flows for managing allocations align with the API changes above (including bookings-per-slot where applicable).
  • Support — The in-app support request experience supports the same legacy and structured options as the API, so users can submit either a short message or a structured request when the product presents those fields.

New changelog post

by ReadMe API
  • info about the changelog
  • blah blah blah
  • updated