ReferenceReference by module

Appointments API

Every Appointments action (26) and data model (6): endpoints, scopes, input, preconditions, events and examples.

Online booking that respects your hours, breaks, time off and capacity.

Module code appointments · 26 actions · 6 data models · 20 events. A key or token never does more here than the person behind it can; row-level security applies exactly as in the app. Scopes: appointments (everything), appointments:read (read only) or appointments:<capability>.

Resources

ResourceTableList and readPOSTPATCHDELETE
appointment_guest_servicesappointment_guest_service/api/v1/appointment_guest_services
appointment_guestsappointment_guest/api/v1/appointment_guests
appointment_import_jobsappointment_import_job/api/v1/appointment_import_jobs
appointment_reminder_opt_outsappointment_reminder_opt_out/api/v1/appointment_reminder_opt_outs
appointmentsappointment/api/v1/appointmentsappointments.book
banned_customersbanned_customer/api/v1/banned_customers

Lists take limit (up to 200), cursor, order (e.g. -created_at), fields and q; every other query parameter is a column filter: ?status=paid, ?total[gte]=100, ?id[in]=a,b, ?x=null.

Actions

ActionNeedsWhat it does
appointments.add_guestappointments:writeAdd a guest
appointments.add_guest_serviceappointments:writeAdd a service
appointments.assign_memberappointments:writeChange who does it
appointments.ban_customerappointments:manageBan from booking
appointments.bookappointments:writeBook an appointment
appointments.book_groupappointments:writeBook an appointment
appointments.book_onlineappointments:writeBook online
appointments.cancelappointments:writeCancel an appointment
appointments.cancel_by_tokenappointments:writeCancel by link
appointments.cancel_my_bookingappointments:writeCancel my booking
appointments.change_guest_customerappointments:writeChange the guest
appointments.completeappointments:writeMark as completed
appointments.confirmappointments:approveConfirm an appointment
appointments.exportappointments:exportExport appointments
appointments.importappointments:importImport appointments
appointments.lift_banappointments:manageLift a ban
appointments.mark_deposit_statusappointments:handleUpdate booking deposit
appointments.no_showappointments:writeMark as no-show
appointments.raise_reminderappointments:handleRaise a booking reminder
appointments.remove_guestappointments:writeRemove a guest
appointments.remove_serviceappointments:writeRemove a service
appointments.rescheduleappointments:writeMove the appointment
appointments.reschedule_by_tokenappointments:writeMove by link
appointments.reschedule_my_bookingappointments:writeMove my booking
appointments.set_reminder_opt_outappointments:writeBooking reminders for a person
appointments.update_detailsappointments:writeEdit the note

appointments.add_guest

Add a guest — Puts another person on an existing appointment.

Needs appointments:write · changes data

POSThttps://app.getera.io/api/v1/appointments/{id}/actions/add_guest
API key or OAuth token · appointments:write
  • Canonical address: POST /api/v1/actions/appointments.add_guest

Input

FieldTypeRequiredNotes
appointmentIduuidyesFilled from the path {id} · Appointment · id of appointment
customerIduuidyesCustomer · id of customer
itemVariantIduuidTheir first service · id of item_variant
memberUserIduuidTeam member · Leave empty to assign whoever is free.
expectedPriceAmountnumber (money)Price shown · The price the screen showed. A different current price is refused so the person can agree to it. · min 0

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe appointment must exist.
already_cancelledThe appointment must not be cancelled.
already_finishedThe appointment must not be finished.
already_a_guestThe person must not already be on it.
customer_not_foundThe customer must exist in this workspace.
customer_bannedThe customer must not be banned.
service_not_foundThe service must exist.
service_inactiveThe service must be active and bookable.
service_has_no_durationA non-package service must have a duration.
package_emptyA package must contain something.
service_not_offered_that_dayThe service must be offered on that weekday.
provider_cannot_performThe team member must perform this service.
closed_that_dayThe date must not be closed by an override.
outside_working_hoursThe longer appointment must still fit in opening hours.
provider_on_leaveNobody needed must be on leave.
slot_takenThe extended slot must be free.
class_fullA class must still have room.
no_provider_availableSomeone must be free when none was chosen.
price_changedThe price must still be the one the person was shown.

Events it publishes: appointments.guest_added

What it writes: appointment_guest (insert) — Adds the guest; appointment_guest_service (insert) — Adds their first service line; appointment (update) — Re-totals duration, price and end time

curl -X POST https://app.getera.io/api/v1/appointments/APPOINTMENT_ID/actions/add_guest \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"customerId":"CUSTOMER_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "appointments.guest_added"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.appointments.addGuest("APPOINTMENT_ID", {
  customerId: "CUSTOMER_ID"
})
era appointments add_guest APPOINTMENT_ID \
  --customer-id "CUSTOMER_ID"
tools/call
{
  "name": "era__appointments__add_guest",
  "arguments": {
    "appointmentId": "APPOINTMENT_ID",
    "customerId": "CUSTOMER_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold appointments:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, already_cancelled, already_finished, already_a_guest, customer_not_found, customer_banned, service_not_found, service_inactive, service_has_no_duration, package_empty, service_not_offered_that_day, provider_cannot_perform, closed_that_day, outside_working_hours, provider_on_leave, slot_taken, class_full, no_provider_available, price_changed · 429 slow down

appointments.add_guest_service

Add a service — Adds one more service for a guest, re-checking the longer appointment.

Needs appointments:write · changes data

POSThttps://app.getera.io/api/v1/appointment_guest_services/actions/add_guest_service
API key or OAuth token · appointments:write
  • Canonical address: POST /api/v1/actions/appointments.add_guest_service

Input

FieldTypeRequiredNotes
appointmentGuestIduuidyesGuest · id of appointment_guest
itemVariantIduuidyesService · id of item_variant
memberUserIduuidTeam member · Leave empty to assign whoever is free.
expectedPriceAmountnumber (money)Price shown · The price the screen showed. A different current price is refused so the person can agree to it. · min 0

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
guest_not_foundThe guest must be on an appointment.
price_changedThe price must still be the one the person was shown.
already_cancelledThe appointment must not be cancelled.
already_finishedThe appointment must not be finished.
service_inactiveThe service must be active and bookable.
service_has_no_durationA non-package service must have a duration.
package_emptyA package must contain something.
service_not_offered_that_dayThe service must be offered on that weekday.
provider_cannot_performThe team member must perform this service.
closed_that_dayThe date must not be closed by an override.
outside_working_hoursThe longer appointment must still fit in opening hours.
provider_on_leaveNobody needed must be on leave.
slot_takenThe extended slot must be free.
class_fullA class must still have room.
no_provider_availableSomeone must be free when none was chosen.

Events it publishes: appointments.service_added

What it writes: appointment_guest_service (insert) — Adds the service line; appointment (update) — Re-totals duration, price and end time

curl -X POST https://app.getera.io/api/v1/appointment_guest_services/actions/add_guest_service \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"appointmentGuestId":"APPOINTMENT_GUEST_ID","itemVariantId":"ITEM_VARIANT_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "appointments.service_added"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.appointmentGuestServices.addGuestService({
  appointmentGuestId: "APPOINTMENT_GUEST_ID",
  itemVariantId: "ITEM_VARIANT_ID"
})
era appointment_guest_services add_guest_service \
  --appointment-guest-id "APPOINTMENT_GUEST_ID" \
  --item-variant-id "ITEM_VARIANT_ID"
tools/call
{
  "name": "era__appointments__add_guest_service",
  "arguments": {
    "appointmentGuestId": "APPOINTMENT_GUEST_ID",
    "itemVariantId": "ITEM_VARIANT_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold appointments:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 guest_not_found, price_changed, already_cancelled, already_finished, service_inactive, service_has_no_duration, package_empty, service_not_offered_that_day, provider_cannot_perform, closed_that_day, outside_working_hours, provider_on_leave, slot_taken, class_full, no_provider_available · 429 slow down

appointments.assign_member

Change who does it — Moves one service line to another team member, or leaves it unassigned.

Needs appointments:write · changes data

POSThttps://app.getera.io/api/v1/appointment_guest_services/{id}/actions/assign_member
API key or OAuth token · appointments:write
  • Canonical address: POST /api/v1/actions/appointments.assign_member

Input

FieldTypeRequiredNotes
lineIduuidyesFilled from the path {id} · Service line · id of appointment_guest_service
memberUserIduuidTeam member · Leave empty to unassign.

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
line_not_foundThe service line must exist.
already_cancelledThe appointment must not be cancelled.
already_finishedThe appointment must not be finished.
provider_cannot_performThe team member must perform this service.
closed_that_dayThe day must not be closed for them.
outside_working_hoursThey must be working across the appointment.
provider_on_leaveThey must not be on leave.
slot_takenThey must be free for the whole appointment.

Events it publishes: appointments.member_assigned

What it writes: appointment_guest_service (update) — Sets the line’s team member

curl -X POST https://app.getera.io/api/v1/appointment_guest_services/APPOINTMENT_GUEST_SERVICE_ID/actions/assign_member \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"memberUserId":"MEMBER_USER_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "appointments.member_assigned"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.appointmentGuestServices.assignMember("APPOINTMENT_GUEST_SERVICE_ID", {
  memberUserId: "MEMBER_USER_ID"
})
era appointment_guest_services assign_member APPOINTMENT_GUEST_SERVICE_ID \
  --member-user-id "MEMBER_USER_ID"
tools/call
{
  "name": "era__appointments__assign_member",
  "arguments": {
    "lineId": "APPOINTMENT_GUEST_SERVICE_ID",
    "memberUserId": "MEMBER_USER_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold appointments:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 line_not_found, already_cancelled, already_finished, provider_cannot_perform, closed_that_day, outside_working_hours, provider_on_leave, slot_taken · 429 slow down

appointments.ban_customer

Ban from booking — Stops an email address or phone number from making new bookings.

Needs appointments:manage · reaches outside (sends, publishes or charges) · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/banned_customers/actions/ban_customer
API key or OAuth token · appointments:manage
  • Canonical address: POST /api/v1/actions/appointments.ban_customer

Input

FieldTypeRequiredNotes
emailemailEmail · max 255
phonephonePhone · max 32
reasonstring (enum)Reason · Allowed values: fake_appointment, no_show, inappropriate, payment_issue, other
notesstringNotes · max 2000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
no_identityAn email address or a phone number is needed.

Events it publishes: appointments.customer_banned

What it writes: banned_customer (insert) — Records the ban

curl -X POST https://app.getera.io/api/v1/banned_customers/actions/ban_customer \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"email":"[email protected]"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "appointments.customer_banned"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.bannedCustomers.banCustomer({
  email: "[email protected]"
})
era banned_customers ban_customer \
  --email "[email protected]"
tools/call
{
  "name": "era__appointments__ban_customer",
  "arguments": {
    "email": "[email protected]"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold appointments:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 no_identity · 429 slow down

appointments.book

Book an appointment — Opens an appointment with its first guest and service, once every rule allows it.

Needs appointments:write · changes data

POSThttps://app.getera.io/api/v1/appointments/actions/book
API key or OAuth token · appointments:write
  • REST alias: POST /api/v1/appointments
  • Canonical address: POST /api/v1/actions/appointments.book

Input

FieldTypeRequiredNotes
customerIduuidyesCustomer · id of customer
itemVariantIduuidyesService · id of item_variant
appointmentDatedateyesDate
startTimestringyesStart time · max 8
memberUserIduuidTeam member · Leave empty to assign whoever is free.
notesstringNote · max 2000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
service_inactiveThe service must be active and bookable.
service_has_no_durationA non-package service must have a duration.
package_emptyA package must contain something.
inside_cutoffThe time must respect the booking cutoff.
outside_windowThe time must fall inside the booking window.
service_not_offered_that_dayThe service must be offered on that weekday.
provider_cannot_performThe team member must perform this service.
closed_that_dayThe date must not be closed by an override.
outside_working_hoursThe slot and its buffer must fall inside opening hours.
provider_on_leaveNobody needed must be on leave.
slot_takenThe slot, plus its buffer, must be free.
class_fullA class must still have room.
no_provider_availableSomeone must be free when none was chosen.
customer_not_foundThe customer must exist in this workspace.
customer_bannedThe customer must not be banned.
no_workspaceThe workspace must be readable, for its clock.

Events it publishes: appointments.booked

What it writes: appointment (insert) — Creates the appointment; appointment_guest (insert) — Adds the first guest; appointment_guest_service (insert) — Adds their first service

curl -X POST https://app.getera.io/api/v1/appointments/actions/book \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"customerId":"CUSTOMER_ID","itemVariantId":"ITEM_VARIANT_ID","appointmentDate":"2026-11-02","startTime":"Start time"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "appointments.booked"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.appointments.create({
  customerId: "CUSTOMER_ID",
  itemVariantId: "ITEM_VARIANT_ID",
  appointmentDate: "2026-11-02",
  startTime: "Start time"
})
era appointments create \
  --customer-id "CUSTOMER_ID" \
  --item-variant-id "ITEM_VARIANT_ID" \
  --appointment-date "2026-11-02" \
  --start-time "Start time"
tools/call
{
  "name": "era__appointments__book",
  "arguments": {
    "customerId": "CUSTOMER_ID",
    "itemVariantId": "ITEM_VARIANT_ID",
    "appointmentDate": "2026-11-02",
    "startTime": "Start time"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold appointments:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 service_inactive, service_has_no_duration, package_empty, inside_cutoff, outside_window, service_not_offered_that_day, provider_cannot_perform, closed_that_day, outside_working_hours, provider_on_leave, slot_taken, class_full, no_provider_available, customer_not_found, customer_banned, no_workspace · 429 slow down

appointments.book_group

Book an appointment — Writes a whole booking — every guest, every service, every staff member — in one transaction.

Needs appointments:write · changes data

POSThttps://app.getera.io/api/v1/appointments/actions/book_group
API key or OAuth token · appointments:write
  • Canonical address: POST /api/v1/actions/appointments.book_group

Input

FieldTypeRequiredNotes
guestsobject / arrayyesGuests and their services · [{ customerId, lines: [{ itemVariantId, memberUserId? }] }]
appointmentDatedateyesDate
startTimestringyesStart time · max 8
notesstringNote · max 2000
addressstringAddress · Home service only — where the visit happens. · max 500

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
invalid_guestsThe guest list must name a customer and at least one service each.
service_not_foundEvery service must exist.
service_inactiveEvery service must be active and bookable.
service_has_no_durationA non-package service must have a duration.
package_emptyA package must contain something.
inside_cutoffThe time must respect the booking cutoff.
outside_windowThe time must fall inside the booking window.
service_not_offered_that_dayEvery service must be offered on that weekday.
provider_cannot_performEach named team member must perform the service given to them.
closed_that_dayThe date must not be closed by an override.
outside_working_hoursThe whole booking, plus its buffer, must fall inside opening hours.
provider_on_leaveNobody needed may be on leave.
slot_takenEvery named member must be free for the whole booking.
class_fullA class must still have room.
no_provider_availableSomeone must be free for each service where none was chosen.
already_a_guestThe same person must not be listed twice.
customer_not_foundEvery guest must exist in this workspace.
customer_bannedNo guest may be banned.
no_workspaceThe workspace must be readable, for its clock.

Events it publishes: appointments.booked

What it writes: appointment (insert) — Creates the appointment; appointment_guest (insert) — Adds every guest; appointment_guest_service (insert) — Adds every service line

curl -X POST https://app.getera.io/api/v1/appointments/actions/book_group \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"guests":{},"appointmentDate":"2026-11-02","startTime":"Start time"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "appointments.booked"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.appointments.bookGroup({
  guests: {},
  appointmentDate: "2026-11-02",
  startTime: "Start time"
})
era appointments book_group --data '{"guests":{},"appointmentDate":"2026-11-02","startTime":"Start time"}'
tools/call
{
  "name": "era__appointments__book_group",
  "arguments": {
    "guests": {},
    "appointmentDate": "2026-11-02",
    "startTime": "Start time"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold appointments:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 invalid_guests, service_not_found, service_inactive, service_has_no_duration, package_empty, inside_cutoff, outside_window, service_not_offered_that_day, provider_cannot_perform, closed_that_day, outside_working_hours, provider_on_leave, slot_taken, class_full, no_provider_available, already_a_guest, customer_not_found, customer_banned, no_workspace · 429 slow down

appointments.book_online

Book online — A visitor books one or more services at a free time from the site, under every rule the team’s booking applies, and gets a link to manage it.

Needs appointments:write · changes data · Also callable from a published site (visitor)

A website action: your site calls it through the site API (/api/site/v1/{siteId}, e.g. with @getera/storefront). With an API key or a token it is refused (403 site_only).
POSThttps://app.getera.io/api/v1/appointments/actions/book_online
API key or OAuth token · appointments:write
  • Canonical address: POST /api/v1/actions/appointments.book_online

Input

FieldTypeRequiredNotes
itemVariantIdsobject / arrayyesServices
appointmentDatedateyesDate
startTimestringyesStart time · max 8
memberUserIduuidTeam member · Empty for anyone who is free.
notesstringNote · max 1000
addressstringAddress · Where a home-service visit happens. · max 500

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
address_requiredA home-service booking needs the address the visit goes to.
guest_details_requiredA guest leaves their details before booking.
service_not_foundAt least one service must be chosen.
too_many_servicesAt most five services on one online booking.
slot_takenThe time must still be free when the booking is written.

Events it publishes: appointments.booked, appointments.service_added

What it writes: appointment (insert) — Creates the appointment with its manage link; appointment_guest (insert) — The person booking; appointment_guest_service (insert) — One line per service

Errors: 401 no or expired credential · 403 the credential does not hold appointments:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 address_required, guest_details_required, service_not_found, too_many_services, slot_taken · 429 slow down

appointments.cancel

Cancel an appointment — Frees the slot and tells the rest of the system it is free.

Needs appointments:write · removes or cannot be undone · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/appointments/{id}/actions/cancel
API key or OAuth token · appointments:write
  • Canonical address: POST /api/v1/actions/appointments.cancel

Input

FieldTypeRequiredNotes
appointmentIduuidyesFilled from the path {id} · Appointment · id of appointment
reasonstringReason · One of the workspace’s cancellation reasons. · max 64

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
already_cancelledIt must not already be cancelled.
already_completedA completed appointment cannot be cancelled.
unknown_reasonThe reason must be one the workspace uses.

Events it publishes: appointments.cancelled

What it writes: appointment (update) — Marks it cancelled and records why

curl -X POST https://app.getera.io/api/v1/appointments/APPOINTMENT_ID/actions/cancel \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"reason":"Reason"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "appointments.cancelled"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.appointments.cancel("APPOINTMENT_ID", {
  reason: "Reason"
})
era appointments cancel APPOINTMENT_ID \
  --reason "Reason"
tools/call
{
  "name": "era__appointments__cancel",
  "arguments": {
    "appointmentId": "APPOINTMENT_ID",
    "reason": "Reason"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold appointments:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 already_cancelled, already_completed, unknown_reason · 429 slow down

appointments.cancel_by_token

Cancel by link — Whoever holds a booking’s manage link cancels it, before the booking cutoff; the time is freed for others.

Needs appointments:write · removes or cannot be undone · asks a person to confirm when Era runs it · Also callable from a published site (visitor)

A website action: your site calls it through the site API (/api/site/v1/{siteId}, e.g. with @getera/storefront). With an API key or a token it is refused (403 site_only).
POSThttps://app.getera.io/api/v1/appointments/actions/cancel_by_token
API key or OAuth token · appointments:write
  • Canonical address: POST /api/v1/actions/appointments.cancel_by_token

Input

FieldTypeRequiredNotes
tokenstringyesLink · max 100
reasonstringReason · max 80

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
booking_link_invalidThe link must name a booking of this business.
booking_startedNot once it has started.
inside_cutoffNot inside the booking cutoff — the business is called instead.
already_cancelledIt must not already be cancelled.

Events it publishes: appointments.cancelled

What it writes: appointment (update) — Marks it cancelled

Errors: 401 no or expired credential · 403 the credential does not hold appointments:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 booking_link_invalid, booking_started, inside_cutoff, already_cancelled · 429 slow down

appointments.cancel_my_booking

Cancel my booking — A signed-in customer cancels one of their own upcoming bookings from their site account; the slot is freed.

Needs appointments:write · removes or cannot be undone · asks a person to confirm when Era runs it · Also callable from a published site (customer)

A website action: your site calls it through the site API (/api/site/v1/{siteId}, e.g. with @getera/storefront). With an API key or a token it is refused (403 site_only).
POSThttps://app.getera.io/api/v1/appointments/{id}/actions/cancel_my_booking
API key or OAuth token · appointments:write
  • Canonical address: POST /api/v1/actions/appointments.cancel_my_booking

Input

FieldTypeRequiredNotes
appointmentIduuidyesFilled from the path {id} · Booking · id of appointment

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_your_bookingOnly a booking the customer is a guest on.
booking_startedNot once it has started.
already_cancelledIt must not already be cancelled.

Events it publishes: appointments.cancelled

What it writes: appointment (update) — Marks it cancelled

Errors: 401 no or expired credential · 403 the credential does not hold appointments:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_your_booking, booking_started, already_cancelled · 429 slow down

appointments.change_guest_customer

Change the guest — Puts a different person on a guest’s place, keeping their services, staff and prices.

Needs appointments:write · changes data

POSThttps://app.getera.io/api/v1/appointment_guests/{id}/actions/change_guest_customer
API key or OAuth token · appointments:write
  • Canonical address: POST /api/v1/actions/appointments.change_guest_customer

Input

FieldTypeRequiredNotes
appointmentGuestIduuidyesFilled from the path {id} · Guest · id of appointment_guest
customerIduuidyesCustomer · id of customer

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
guest_not_foundThe guest must be on an appointment.
already_cancelledThe appointment must not be cancelled.
already_finishedThe appointment must not be finished.
same_customerThe new person must be a different person.
customer_not_foundThe new person must exist.
customer_bannedThe new person must not be banned.
already_a_guestThe new person must not already be on the booking.

Events it publishes: appointments.guest_customer_changed

What it writes: appointment_guest (update) — Points the guest at the new customer

curl -X POST https://app.getera.io/api/v1/appointment_guests/APPOINTMENT_GUEST_ID/actions/change_guest_customer \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"customerId":"CUSTOMER_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "appointments.guest_customer_changed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.appointmentGuests.changeGuestCustomer("APPOINTMENT_GUEST_ID", {
  customerId: "CUSTOMER_ID"
})
era appointment_guests change_guest_customer APPOINTMENT_GUEST_ID \
  --customer-id "CUSTOMER_ID"
tools/call
{
  "name": "era__appointments__change_guest_customer",
  "arguments": {
    "appointmentGuestId": "APPOINTMENT_GUEST_ID",
    "customerId": "CUSTOMER_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold appointments:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 guest_not_found, already_cancelled, already_finished, same_customer, customer_not_found, customer_banned, already_a_guest · 429 slow down

appointments.complete

Mark as completed — Records that the appointment took place.

Needs appointments:write · changes data

POSThttps://app.getera.io/api/v1/appointments/{id}/actions/complete
API key or OAuth token · appointments:write
  • Canonical address: POST /api/v1/actions/appointments.complete

Input

FieldTypeRequiredNotes
appointmentIduuidyesFilled from the path {id} · Appointment · id of appointment

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
already_cancelledA cancelled appointment cannot be completed.
already_finishedIt must not already be finished.
not_yet_startedIt must have started.

Events it publishes: appointments.completed

What it writes: appointment (update) — Moves it to completed

curl -X POST https://app.getera.io/api/v1/appointments/APPOINTMENT_ID/actions/complete \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "appointments.completed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.appointments.complete("APPOINTMENT_ID")
era appointments complete APPOINTMENT_ID
tools/call
{
  "name": "era__appointments__complete",
  "arguments": {
    "appointmentId": "APPOINTMENT_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold appointments:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 already_cancelled, already_finished, not_yet_started · 429 slow down

appointments.confirm

Confirm an appointment — Accepts a pending booking.

Needs appointments:approve · changes data

POSThttps://app.getera.io/api/v1/appointments/{id}/actions/confirm
API key or OAuth token · appointments:approve
  • Canonical address: POST /api/v1/actions/appointments.confirm

Input

FieldTypeRequiredNotes
appointmentIduuidyesFilled from the path {id} · Appointment · id of appointment

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_pendingOnly a pending appointment can be confirmed.

Events it publishes: appointments.confirmed

What it writes: appointment (update) — Moves it to confirmed

curl -X POST https://app.getera.io/api/v1/appointments/APPOINTMENT_ID/actions/confirm \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "appointments.confirmed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.appointments.confirm("APPOINTMENT_ID")
era appointments confirm APPOINTMENT_ID
tools/call
{
  "name": "era__appointments__confirm",
  "arguments": {
    "appointmentId": "APPOINTMENT_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold appointments:approve · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_pending · 429 slow down

appointments.export

Export appointments — Every booking in a period, one row per service line — the list turns them into a CSV or Excel file.

Needs appointments:export · reaches outside (sends, publishes or charges)

POSThttps://app.getera.io/api/v1/actions/appointments.export
API key or OAuth token · appointments:export

Input

FieldTypeRequiredNotes
fromdateyesFrom
todateyesTo
statusstringStatus · max 20

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
invalid_rangeThe period must not end before it starts.

Events it publishes: appointments.exported

curl -X POST https://app.getera.io/api/v1/actions/appointments.export \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"from":"2026-11-02","to":"2026-11-02"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "appointments.exported"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.actions.run("appointments.export", {
  from: "2026-11-02",
  to: "2026-11-02"
})
era run appointments.export \
  --from "2026-11-02" \
  --to "2026-11-02"
tools/call
{
  "name": "era__appointments__export",
  "arguments": {
    "from": "2026-11-02",
    "to": "2026-11-02"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold appointments:export · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 invalid_range · 429 slow down

appointments.import

Import appointments — Queues a mapped file, books every valid row and keeps a result for the summary screen.

Needs appointments:import · changes data

POSThttps://app.getera.io/api/v1/appointment_import_jobs/actions/import
API key or OAuth token · appointments:import
  • Canonical address: POST /api/v1/actions/appointments.import

Input

FieldTypeRequiredNotes
fileNamestringyesFile · max 255
mappingobject / arrayyesColumn mapping
rowsobject / arrayyesPrepared rows

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
invalid_importThe mapped file must contain no more than 2,000 usable rows.

Events it publishes: appointments.import_completed

What it writes: appointment_import_job (insert) — Records and tracks the import; appointment_import_job (update) — Stores progress and the result; appointment (insert) — Books valid rows; appointment_guest (insert) — Adds each matched customer; appointment_guest_service (insert) — Adds the matched service and staff member

curl -X POST https://app.getera.io/api/v1/appointment_import_jobs/actions/import \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"fileName":"File","mapping":{},"rows":{}}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "appointments.import_completed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.appointmentImportJobs.import({
  fileName: "File",
  mapping: {},
  rows: {}
})
era appointment_import_jobs import --data '{"fileName":"File","mapping":{},"rows":{}}'
tools/call
{
  "name": "era__appointments__import",
  "arguments": {
    "fileName": "File",
    "mapping": {},
    "rows": {}
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold appointments:import · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 invalid_import · 429 slow down

appointments.lift_ban

Lift a ban — Lets that person book again.

Needs appointments:manage · removes or cannot be undone

POSThttps://app.getera.io/api/v1/banned_customers/{id}/actions/lift_ban
API key or OAuth token · appointments:manage
  • Canonical address: POST /api/v1/actions/appointments.lift_ban

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Ban · id of banned_customer

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe ban must exist.

Events it publishes: appointments.ban_lifted

What it writes: banned_customer (delete) — Removes the ban

curl -X POST https://app.getera.io/api/v1/banned_customers/BANNED_CUSTOMER_ID/actions/lift_ban \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "appointments.ban_lifted"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.bannedCustomers.liftBan("BANNED_CUSTOMER_ID")
era banned_customers lift_ban BANNED_CUSTOMER_ID
tools/call
{
  "name": "era__appointments__lift_ban",
  "arguments": {
    "id": "BANNED_CUSTOMER_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold appointments:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found · 429 slow down

appointments.mark_deposit_status

Update booking deposit — Records the booking-side result of the shared finance payment flow.

Needs appointments:handle · changes data

POSThttps://app.getera.io/api/v1/appointments/{id}/actions/mark_deposit_status
API key or OAuth token · appointments:handle
  • Canonical address: POST /api/v1/actions/appointments.mark_deposit_status

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Appointment · id of appointment
statusstring (enum)yesDeposit status · Allowed values: pending, paid, failed, refunded

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
appointment_not_foundThe appointment must exist.
deposit_not_requiredThe booking must require a deposit.

Events it publishes: appointments.deposit_changed

What it writes: appointment (update) — Updates the frozen deposit status

curl -X POST https://app.getera.io/api/v1/appointments/APPOINTMENT_ID/actions/mark_deposit_status \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"status":"pending"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "appointments.deposit_changed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.appointments.markDepositStatus("APPOINTMENT_ID", {
  status: "pending"
})
era appointments mark_deposit_status APPOINTMENT_ID \
  --status "pending"
tools/call
{
  "name": "era__appointments__mark_deposit_status",
  "arguments": {
    "id": "APPOINTMENT_ID",
    "status": "pending"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold appointments:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 appointment_not_found, deposit_not_required · 429 slow down

appointments.no_show

Mark as no-show — Records that the customer did not arrive.

Needs appointments:write · changes data

POSThttps://app.getera.io/api/v1/appointments/{id}/actions/no_show
API key or OAuth token · appointments:write
  • Canonical address: POST /api/v1/actions/appointments.no_show

Input

FieldTypeRequiredNotes
appointmentIduuidyesFilled from the path {id} · Appointment · id of appointment

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
already_cancelledA cancelled appointment cannot be a no-show.
already_finishedIt must not already be finished.
not_yet_startedIt must have started.

Events it publishes: appointments.no_show

What it writes: appointment (update) — Moves it to no_show

curl -X POST https://app.getera.io/api/v1/appointments/APPOINTMENT_ID/actions/no_show \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "appointments.no_show"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.appointments.noShow("APPOINTMENT_ID")
era appointments no_show APPOINTMENT_ID
tools/call
{
  "name": "era__appointments__no_show",
  "arguments": {
    "appointmentId": "APPOINTMENT_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold appointments:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 already_cancelled, already_finished, not_yet_started · 429 slow down

appointments.raise_reminder

Raise a booking reminder — Marks a booking as due for its reminder and tells the notification rules. Run by the reminder job.

Needs appointments:handle · changes data

POSThttps://app.getera.io/api/v1/appointments/{id}/actions/raise_reminder
API key or OAuth token · appointments:handle
  • Canonical address: POST /api/v1/actions/appointments.raise_reminder

Input

FieldTypeRequiredNotes
appointmentIduuidyesFilled from the path {id} · Appointment · id of appointment

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
reminders_offReminders must be switched on.
not_foundThe appointment must exist.
not_upcomingIt must be pending or confirmed and still ahead.
not_yet_dueIt must be inside the reminder lead time.
already_remindedA booking is reminded once per slot.
opted_outAt least one guest must accept reminders.
no_workspaceThe workspace must be readable, for its clock.

Events it publishes: appointments.reminder_due

What it writes: appointment (update) — Stamps reminder_due_at

curl -X POST https://app.getera.io/api/v1/appointments/APPOINTMENT_ID/actions/raise_reminder \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "appointments.reminder_due"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.appointments.raiseReminder("APPOINTMENT_ID")
era appointments raise_reminder APPOINTMENT_ID
tools/call
{
  "name": "era__appointments__raise_reminder",
  "arguments": {
    "appointmentId": "APPOINTMENT_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold appointments:handle · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 reminders_off, not_found, not_upcoming, not_yet_due, already_reminded, opted_out, no_workspace · 429 slow down

appointments.remove_guest

Remove a guest — Takes one person off a booking along with everything they had booked.

Needs appointments:write · removes or cannot be undone · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/appointment_guests/{id}/actions/remove_guest
API key or OAuth token · appointments:write
  • Canonical address: POST /api/v1/actions/appointments.remove_guest

Input

FieldTypeRequiredNotes
appointmentGuestIduuidyesFilled from the path {id} · Guest · id of appointment_guest

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
guest_not_foundThe guest must be on an appointment.
already_cancelledThe appointment must not be cancelled.
already_finishedThe appointment must not be finished.
last_guestThe last guest cannot be removed — cancel instead.

Events it publishes: appointments.guest_removed

What it writes: appointment_guest (delete) — Removes the guest and their lines; appointment (update) — Re-totals duration, price and end time

curl -X POST https://app.getera.io/api/v1/appointment_guests/APPOINTMENT_GUEST_ID/actions/remove_guest \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "appointments.guest_removed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.appointmentGuests.removeGuest("APPOINTMENT_GUEST_ID")
era appointment_guests remove_guest APPOINTMENT_GUEST_ID
tools/call
{
  "name": "era__appointments__remove_guest",
  "arguments": {
    "appointmentGuestId": "APPOINTMENT_GUEST_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold appointments:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 guest_not_found, already_cancelled, already_finished, last_guest · 429 slow down

appointments.remove_service

Remove a service — Drops one service line and shortens the appointment to match.

Needs appointments:write · removes or cannot be undone · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/appointment_guest_services/{id}/actions/remove_service
API key or OAuth token · appointments:write
  • Canonical address: POST /api/v1/actions/appointments.remove_service

Input

FieldTypeRequiredNotes
lineIduuidyesFilled from the path {id} · Service line · id of appointment_guest_service

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
line_not_foundThe service line must exist.
already_cancelledThe appointment must not be cancelled.
already_finishedThe appointment must not be finished.
last_serviceThe last service cannot be removed — cancel instead.

Events it publishes: appointments.service_removed

What it writes: appointment_guest_service (delete) — Removes the line; appointment_guest (delete) — Removes a guest left with nothing; appointment (update) — Re-totals duration, price and end time

curl -X POST https://app.getera.io/api/v1/appointment_guest_services/APPOINTMENT_GUEST_SERVICE_ID/actions/remove_service \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "appointments.service_removed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.appointmentGuestServices.removeService("APPOINTMENT_GUEST_SERVICE_ID")
era appointment_guest_services remove_service APPOINTMENT_GUEST_SERVICE_ID
tools/call
{
  "name": "era__appointments__remove_service",
  "arguments": {
    "lineId": "APPOINTMENT_GUEST_SERVICE_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold appointments:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 line_not_found, already_cancelled, already_finished, last_service · 429 slow down

appointments.reschedule

Move the appointment — Moves a booking to another day or time, re-checking every rule against the new slot.

Needs appointments:write · changes data

POSThttps://app.getera.io/api/v1/appointments/{id}/actions/reschedule
API key or OAuth token · appointments:write
  • Canonical address: POST /api/v1/actions/appointments.reschedule

Input

FieldTypeRequiredNotes
appointmentIduuidyesFilled from the path {id} · Appointment · id of appointment
appointmentDatedateyesNew date
startTimestringyesNew start time · max 8

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe appointment must exist.
already_cancelledA cancelled appointment cannot be moved.
already_finishedA finished appointment cannot be moved.
unchangedThe new slot must differ from the current one.
no_servicesThe appointment must still have service lines.
inside_cutoffThe new time must respect the booking cutoff.
outside_windowThe new time must fall inside the booking window.
service_not_offered_that_dayEvery service must be offered on the new weekday.
closed_that_dayThe new date must not be closed by an override.
outside_working_hoursThe whole booking must fit inside opening hours.
provider_on_leaveNobody on the booking may be on leave then.
slot_takenEveryone on the booking must be free in the new window.
class_fullA class must still have room at the new time.
no_workspaceThe workspace must be readable, for its clock.

Events it publishes: appointments.rescheduled

What it writes: appointment (update) — Moves the date, start and end

curl -X POST https://app.getera.io/api/v1/appointments/APPOINTMENT_ID/actions/reschedule \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"appointmentDate":"2026-11-02","startTime":"New start time"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "appointments.rescheduled"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.appointments.reschedule("APPOINTMENT_ID", {
  appointmentDate: "2026-11-02",
  startTime: "New start time"
})
era appointments reschedule APPOINTMENT_ID \
  --appointment-date "2026-11-02" \
  --start-time "New start time"
tools/call
{
  "name": "era__appointments__reschedule",
  "arguments": {
    "appointmentId": "APPOINTMENT_ID",
    "appointmentDate": "2026-11-02",
    "startTime": "New start time"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold appointments:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found, already_cancelled, already_finished, unchanged, no_services, inside_cutoff, outside_window, service_not_offered_that_day, closed_that_day, outside_working_hours, provider_on_leave, slot_taken, class_full, no_workspace · 429 slow down

appointments.reschedule_by_token

Move by link — Whoever holds a booking’s manage link moves it to another free time under the same rules as the team; the old time is freed.

Needs appointments:write · changes data · Also callable from a published site (visitor)

A website action: your site calls it through the site API (/api/site/v1/{siteId}, e.g. with @getera/storefront). With an API key or a token it is refused (403 site_only).
POSThttps://app.getera.io/api/v1/appointments/actions/reschedule_by_token
API key or OAuth token · appointments:write
  • Canonical address: POST /api/v1/actions/appointments.reschedule_by_token

Input

FieldTypeRequiredNotes
tokenstringyesLink · max 100
appointmentDatedateyesNew date
startTimestringyesNew start time · max 8

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
booking_link_invalidThe link must name a booking of this business.
booking_startedNot once it has started.
slot_takenEveryone on the booking must be free at the new time.

Events it publishes: appointments.rescheduled

What it writes: appointment (update) — Moves the date, start and end

Errors: 401 no or expired credential · 403 the credential does not hold appointments:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 booking_link_invalid, booking_started, slot_taken · 429 slow down

appointments.reschedule_my_booking

Move my booking — A signed-in customer moves one of their own upcoming bookings to another free time, under the same booking rules as the team.

Needs appointments:write · changes data · Also callable from a published site (customer)

A website action: your site calls it through the site API (/api/site/v1/{siteId}, e.g. with @getera/storefront). With an API key or a token it is refused (403 site_only).
POSThttps://app.getera.io/api/v1/appointments/{id}/actions/reschedule_my_booking
API key or OAuth token · appointments:write
  • Canonical address: POST /api/v1/actions/appointments.reschedule_my_booking

Input

FieldTypeRequiredNotes
appointmentIduuidyesFilled from the path {id} · Booking · id of appointment
appointmentDatedateyesNew date
startTimestringyesNew start time · max 8

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_your_bookingOnly a booking the customer is a guest on.
booking_startedNot once it has started.
slot_takenEveryone on the booking must be free in the new window.

Events it publishes: appointments.rescheduled

What it writes: appointment (update) — Moves the date, start and end

Errors: 401 no or expired credential · 403 the credential does not hold appointments:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_your_booking, booking_started, slot_taken · 429 slow down

appointments.set_reminder_opt_out

Booking reminders for a person — Records that a customer does, or does not, want booking reminders.

Needs appointments:write · removes or cannot be undone

POSThttps://app.getera.io/api/v1/appointment_reminder_opt_outs/actions/set_reminder_opt_out
API key or OAuth token · appointments:write
  • Canonical address: POST /api/v1/actions/appointments.set_reminder_opt_out

Input

FieldTypeRequiredNotes
customerIduuidyesCustomer · id of customer
optOutbooleanyesDo not remind

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
customer_not_foundThe customer must exist.

Events it publishes: appointments.reminder_opt_out_changed

What it writes: appointment_reminder_opt_out (insert) — Records the opt-out; appointment_reminder_opt_out (delete) — Removes it again

curl -X POST https://app.getera.io/api/v1/appointment_reminder_opt_outs/actions/set_reminder_opt_out \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"customerId":"CUSTOMER_ID","optOut":true}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "appointments.reminder_opt_out_changed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.appointmentReminderOptOuts.setReminderOptOut({
  customerId: "CUSTOMER_ID",
  optOut: true
})
era appointment_reminder_opt_outs set_reminder_opt_out \
  --customer-id "CUSTOMER_ID" \
  --opt-out true
tools/call
{
  "name": "era__appointments__set_reminder_opt_out",
  "arguments": {
    "customerId": "CUSTOMER_ID",
    "optOut": true
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold appointments:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 customer_not_found · 429 slow down

appointments.update_details

Edit the note — The note staff read, and the address for a visit that is not at the business.

Needs appointments:write · changes data

POSThttps://app.getera.io/api/v1/appointments/{id}/actions/update_details
API key or OAuth token · appointments:write
  • Canonical address: POST /api/v1/actions/appointments.update_details

Input

FieldTypeRequiredNotes
appointmentIduuidyesFilled from the path {id} · Appointment · id of appointment
notesstringNote · max 2000
addressstringAddress · Home service only — where the visit happens. · max 500

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_foundThe appointment must exist.

Events it publishes: appointments.details_updated

What it writes: appointment (update) — Stores the note and the address

curl -X POST https://app.getera.io/api/v1/appointments/APPOINTMENT_ID/actions/update_details \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"notes":"Note"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "appointments.details_updated"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.appointments.updateDetails("APPOINTMENT_ID", {
  notes: "Note"
})
era appointments update_details APPOINTMENT_ID \
  --notes "Note"
tools/call
{
  "name": "era__appointments__update_details",
  "arguments": {
    "appointmentId": "APPOINTMENT_ID",
    "notes": "Note"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold appointments:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_found · 429 slow down

Data models

AppointmentGuestService

Table appointment_guest_service · Resource appointment_guest_services

  • GET /api/v1/appointment_guest_services
  • GET /api/v1/appointment_guest_services/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
appointment_idstring (uuid) · uuidappointment
appointment_guest_idstring (uuid) · uuidappointment_guest
item_variant_idstring (uuid) · uuiditem_variant
member_user_idstring (uuid) · uuidyesapp_user
durationinteger · int4
price_amountstring (int64) · int8
price_currencystring · bpcharyesmax 3
sort_orderinteger · int4
created_atstring (date-time) · timestamptz

Primary key: id

Checks:

  • CHECK ((duration > 0))
  • CHECK ((price_amount >= 0))

AppointmentGuest

Table appointment_guest · Resource appointment_guests

  • GET /api/v1/appointment_guests
  • GET /api/v1/appointment_guests/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
appointment_idstring (uuid) · uuidappointment
customer_idstring (uuid) · uuidcustomer
sort_orderinteger · int4
created_atstring (date-time) · timestamptz

Primary key: id

AppointmentImportJob

Table appointment_import_job · Resource appointment_import_jobs

  • GET /api/v1/appointment_import_jobs
  • GET /api/v1/appointment_import_jobs/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
created_bystring (uuid) · uuidapp_user
file_namestring · text
mappingobject · jsonb
statusstring · textqueued, running, completed, failed
total_rowsinteger · int4
imported_rowsinteger · int4
failed_rowsinteger · int4
resultobject · jsonb
started_atstring (date-time) · timestamptzyes
finished_atstring (date-time) · timestamptzyes
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz

Primary key: id

Checks:

  • CHECK ((failed_rows >= 0))
  • CHECK ((imported_rows >= 0))
  • CHECK ((total_rows >= 0))

AppointmentReminderOptOut

Table appointment_reminder_opt_out · Resource appointment_reminder_opt_outs

  • GET /api/v1/appointment_reminder_opt_outs
  • GET /api/v1/appointment_reminder_opt_outs/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
customer_idstring (uuid) · uuidcustomer
created_bystring (uuid) · uuidyesapp_user
created_atstring (date-time) · timestamptz

Primary key: id

Unique: workspace_id, customer_id

Appointment

Table appointment · Resource appointments

  • GET /api/v1/appointments
  • GET /api/v1/appointments/{id}
  • POST /api/v1/appointments → appointments.book
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
appointment_datestring (date) · date
start_timestring (time) · time
end_timestring (time) · time
total_durationinteger · int4
total_price_amountstring (int64) · int8
total_price_currencystring · bpcharyesmax 3
statusstring · textpending, confirmed, completed, cancelled, no_show
notesstring · textyes
cancellation_reasonstring · textyes
cancelled_atstring (date-time) · timestamptzyes
owner_user_idstring (uuid) · uuidyesapp_user
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz
addressstring · textyes
reminder_due_atstring (date-time) · timestamptzyes
deposit_amountstring (int64) · int8
deposit_statusstring · textnot_required, required, pending, paid, failed, refunded
booked_online_atstring (date-time) · timestamptzyes

Primary key: id

Checks:

  • CHECK ((end_time > start_time))
  • CHECK (((status = 'cancelled'::text) = (cancelled_at IS NOT NULL)))
  • CHECK ((deposit_amount >= 0))
  • CHECK ((total_duration >= 0))
  • CHECK ((total_price_amount >= 0))

BannedCustomer

Table banned_customer · Resource banned_customers

  • GET /api/v1/banned_customers
  • GET /api/v1/banned_customers/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
emailstring · textyes
phonestring · textyes
reasonstring · textyes
notesstring · textyes
banned_bystring (uuid) · uuidyesapp_user
created_atstring (date-time) · timestamptz

Primary key: id

Checks:

  • CHECK (((email IS NOT NULL) OR (phone IS NOT NULL)))

Events

Workflows and webhooks react to these events.

EventWhat happensPayload
appointments.ban_liftedA ban was removed.id
appointments.bookedA slot was reserved.id, appointmentDate, startTime, customerId, itemVariantId, memberUserId, totalAmount, currency, depositAmount
appointments.cancelledA booking was cancelled and the slot freed.id, appointmentDate, startTime, reason, customerId
appointments.completedThe appointment took place.id, customerId, customerName, appointmentDate, startTime, memberUserId, itemVariantId
appointments.confirmedA pending booking was accepted.id, appointmentDate, customerId, totalAmount, currency
appointments.customer_bannedSomeone was stopped from booking.id, email, phone
appointments.deposit_changedA required booking deposit changed state through finance.id, from, to, amount
appointments.details_updatedThe note or the address changed.id
appointments.exportedBookings were taken out as a file.count, from, to, truncated
appointments.guest_addedAnother person joined an appointment.id, guestId, customerId, lineId
appointments.guest_customer_changedA guest’s place was given to another person.id, guestId, fromCustomerId, customerId, lineIds
appointments.guest_removedSomeone was taken off a booking.id, guestId
appointments.import_completedA mapped appointment file finished importing.id, imported, failed
appointments.member_assignedA service line changed hands.id, lineId, memberUserId
appointments.no_showThe customer did not arrive.id, customerId, customerName, appointmentDate, startTime, memberUserId, itemVariantId
appointments.reminder_dueA booking is inside its reminder lead time — notification rules send it.id, appointmentDate, startTime, customerId, customerIds, channels, hoursBefore, timezone
appointments.reminder_opt_out_changedA customer started or stopped accepting booking reminders.customerId, optOut
appointments.rescheduledA booking moved to another slot.id, fromDate, fromStartTime, appointmentDate, startTime, customerId
appointments.service_addedAnother service was added to an appointment.id, lineId, itemVariantId
appointments.service_removedA service was dropped from a booking.id, lineId
Was this page helpful?