# OpenReserve for AI agents

> Booking, payments and your own app for barbershops and tattoo studios.

A workspace owner or admin creates an agent key at https://app.openreserve.app/settings/api-keys. Send it as
`Authorization: Bearer <key>`. The key can only do what it was given.

---
# OpenReserve API for agents

Contract 3.2.0. Base URL: https://api.openreserve.app. OpenAPI: https://api.openreserve.app/v1/agent/openapi.json

## Connecting

A person who administers a tenant creates an agent key in the dashboard (API keys → kind "agent", with the permissions you need) and gives it to you. Send it on every call as `Authorization: Bearer ak_…`. Never print it or put it in a URL. An agent key acts for one tenant, only within its permissions, and only on the operations listed here.

## Conventions

- JSON in and out. Ids are prefixed (`ten_…`, `usr_…`). Times are ISO-8601 in UTC.
- Lists take `?cursor=&limit=` and answer `{ items, nextCursor }`; pass `nextCursor` back for the next page.
- Send a new `Idempotency-Key` (a UUID) with every POST, PATCH and DELETE. Retrying with the same key never repeats the action.
- Errors answer `{ "error": { "code", "message", "outcome", "remediation" } }`. `outcome: not_committed` means nothing changed.

## Ask the person first

- `PATCH /v1/tenants/{tenantId}` — Update a tenant's settings
- `DELETE /v1/tenants/{tenantId}/media/{mediaId}` — Delete a file
- `POST /v1/tenants/{tenantId}/domains` — Connect a custom domain
- `DELETE /v1/tenants/{tenantId}/domains/{domainId}` — Disconnect a custom domain
- `POST /v1/tenants/{tenantId}/payments/charges` — Take a payment on the tenant's account (direct charge)
- `POST /v1/tenants/{tenantId}/payments/charges/{paymentId}/refunds` — Refund a payment, fully or in part
- `DELETE /v1/tenants/{tenantId}/notes/{noteId}` — Delete a note
- `POST /v1/tenants/{tenantId}/booking/locations` — Add a location
- `PATCH /v1/tenants/{tenantId}/booking/locations/{locationId}` — Change a location
- `POST /v1/tenants/{tenantId}/booking/services` — Add a service
- `PATCH /v1/tenants/{tenantId}/booking/services/{serviceId}` — Change a service
- `DELETE /v1/tenants/{tenantId}/booking/services/{serviceId}` — Remove a service from the menu
- `POST /v1/tenants/{tenantId}/booking/staff` — Add a staff member
- `PATCH /v1/tenants/{tenantId}/booking/staff/{staffId}` — Change a staff member (profile, login link, services performed)
- `DELETE /v1/tenants/{tenantId}/booking/staff/{staffId}` — Archive a staff member who left
- `PATCH /v1/tenants/{tenantId}/booking/policy` — Change the shop's booking policy
- `PUT /v1/tenants/{tenantId}/booking/staff/{staffId}/hours` — Replace a staff member's weekly hours
- `PUT /v1/tenants/{tenantId}/booking/locations/{locationId}/hours` — Replace a location's opening hours
- `POST /v1/tenants/{tenantId}/booking/time-off` — Add time off (a staff member) or a closure (a location)
- `DELETE /v1/tenants/{tenantId}/booking/time-off/{timeOffId}` — Remove time off or a closure
- `POST /v1/tenants/{tenantId}/booking/appointments` — Book a held time for a client
- `POST /v1/tenants/{tenantId}/booking/appointments/{appointmentId}/reschedule` — Move an appointment to another time (or staff member)
- `POST /v1/tenants/{tenantId}/booking/appointments/{appointmentId}/cancel` — Cancel an appointment
- `POST /v1/tenants/{tenantId}/booking/clients` — Create a client record
- `PATCH /v1/tenants/{tenantId}/booking/clients/{clientId}` — Update client contact details or private notes

## Operations

- `GET /v1/health` — Check that the API is up. When: To check that the API is reachable and which contract version it serves.
- `GET /v1/app-config` — Get client configuration (versions, features, sign-in client ids). When: Rarely: it describes app versions and sign-in client ids for the official apps.
- `GET /v1/public/tenants/{slug}` — Get a tenant's public profile by slug. When: To read a tenant's public name and description from its slug.
- `GET /v1/public/domains/{host}` — Find the tenant behind a verified custom domain. When: Rarely: it maps a custom domain to the tenant slug.
- `GET /v1/tenants/{tenantId}` — Get a tenant. When: To read the tenant's name, slug, time zone and locale.
- `PATCH /v1/tenants/{tenantId}` — Update a tenant's settings. When: When the person asks to rename the tenant or change its time zone or locale. Permission: `tenant.manage`. **Ask the person first.**
- `GET /v1/tenants/{tenantId}/members` — List a tenant's members. When: To see who is on the team and their roles. Permission: `members.view`.
- `GET /v1/tenants/{tenantId}/roles` — List the roles of a tenant (platform and vertical roles). When: To explain what each role can do, or before suggesting a role change. Permission: `members.view`.
- `GET /v1/tenants/{tenantId}/invites` — List pending invites. When: To see who has been invited but has not joined yet. Permission: `members.view`.
- `GET /v1/tenants/{tenantId}/audit-log` — Read a tenant's audit log (newest first). When: When the person asks who changed something, or what happened recently. Permission: `audit.view`.
- `POST /v1/tenants/{tenantId}/media/uploads` — Start an upload (returns a presigned POST). When: To store a file for the workspace (first step; then send the file and call media.complete). Not when: Never make a file public unless the person asked for a public file (logo, product photo). Permission: `media.manage`.
- `POST /v1/tenants/{tenantId}/media/uploads/{uploadId}/complete` — Finish an upload: check the file and store it. When: After sending the file to the upload URL. Permission: `media.manage`.
- `GET /v1/tenants/{tenantId}/media` — List the workspace's files (newest first). When: To find a file the person mentions. Permission: `media.view`.
- `GET /v1/tenants/{tenantId}/media/{mediaId}` — Get a file (with a fresh link for private files). When: To get a link to one file. Permission: `media.view`.
- `DELETE /v1/tenants/{tenantId}/media/{mediaId}` — Delete a file. When: When the person explicitly asks to delete a file. Permission: `media.manage`. **Ask the person first.** Cannot be undone.
- `GET /v1/tenants/{tenantId}/domains` — List the workspace's custom domains. When: To see which custom domains are connected and what DNS they still need. Permission: `settings.manage`.
- `POST /v1/tenants/{tenantId}/domains` — Connect a custom domain. When: When the person wants their public pages on a domain they own. Not when: DNS changes happen at the domain owner’s provider: say what to add, then verify. Permission: `settings.manage`. **Ask the person first.**
- `POST /v1/tenants/{tenantId}/domains/{domainId}/verify` — Check the DNS records of a custom domain. When: After the person says they added the DNS records. Permission: `settings.manage`.
- `DELETE /v1/tenants/{tenantId}/domains/{domainId}` — Disconnect a custom domain. When: When the person explicitly asks to disconnect a domain. Permission: `settings.manage`. **Ask the person first.** Cannot be undone.
- `GET /v1/agent/guide` — The guide for AI agents (markdown by default, JSON with ?format=json). When: First: it explains how to connect, what each operation is for, and when to ask the person.
- `GET /v1/agent/openapi.json` — OpenAPI 3.1 document of the operations an agent may call. When: To generate tool definitions; `x-agent-*` fields say when to ask the person first.
- `GET /v1/billing/plans` — List the plans tenants can subscribe to. When: To compare plans, their prices and what each one includes.
- `GET /v1/tenants/{tenantId}/billing/subscription` — Get the tenant's plan, subscription status and entitlements. When: To check the tenant's plan, whether it is paid up, and what it includes.
- `GET /v1/tenants/{tenantId}/payments/account` — Get the tenant's payments account and whether it can take payments. When: To check whether the tenant can take payments, or what Stripe still needs. Permission: `payments.manage`.
- `POST /v1/tenants/{tenantId}/payments/customers` — Add one of the tenant's customers (to keep a card on file). When: Before saving a card for a customer of the tenant, when your vertical has none for them yet. Permission: `payments.charge`. Cannot be undone.
- `GET /v1/tenants/{tenantId}/payments/customers/{customerId}` — Get a customer and their card on file. When: To see whether a customer has a card on file before charging it. Permission: `payments.charge`.
- `POST /v1/tenants/{tenantId}/payments/charges` — Take a payment on the tenant's account (direct charge). When: When the person asks to charge a customer, e.g. a no-show fee to the card on file. Not when: Never to charge a card the customer did not agree to have charged. Permission: `payments.charge`. **Ask the person first.** Cannot be undone.
- `GET /v1/tenants/{tenantId}/payments/charges/{paymentId}` — Get a payment. When: To check whether a payment succeeded, failed or was refunded. Permission: `payments.charge`.
- `POST /v1/tenants/{tenantId}/payments/charges/{paymentId}/refunds` — Refund a payment, fully or in part. When: When the person asks to refund a payment. Permission: `payments.refund`. **Ask the person first.** Cannot be undone.
- `GET /v1/tenants/{tenantId}/notes` — List notes (newest first). When: To find the team's notes before reading or changing one. Permission: `notes.view`.
- `POST /v1/tenants/{tenantId}/notes` — Create a note. When: When the person asks you to write something down for the team. Permission: `notes.manage`.
- `GET /v1/tenants/{tenantId}/notes/{noteId}` — Get a note. When: To read one note in full. Permission: `notes.view`.
- `PATCH /v1/tenants/{tenantId}/notes/{noteId}` — Change a note. When: When the person asks you to edit a note. Not when: Never to rewrite a note the person did not ask about: earlier text is not kept. Permission: `notes.manage`. Cannot be undone.
- `DELETE /v1/tenants/{tenantId}/notes/{noteId}` — Delete a note. When: When the person explicitly asks to delete a note. Permission: `notes.manage`. **Ask the person first.** Cannot be undone.
- `GET /v1/tenants/{tenantId}/booking/locations` — List the shop's locations. When: To find a location id or a location time zone. Permission: `booking.view`.
- `POST /v1/tenants/{tenantId}/booking/locations` — Add a location. When: When the owner asks you to change how the shop is set up. Permission: `booking.setup.manage`. **Ask the person first.**
- `PATCH /v1/tenants/{tenantId}/booking/locations/{locationId}` — Change a location. When: When the owner asks you to change how the shop is set up. Permission: `booking.setup.manage`. **Ask the person first.**
- `GET /v1/tenants/{tenantId}/booking/services` — List services (the menu), including ones not offered online. When: To find a service id, its duration, price and who performs it. Permission: `booking.view`.
- `POST /v1/tenants/{tenantId}/booking/services` — Add a service. When: When the owner asks you to change how the shop is set up. Permission: `booking.setup.manage`. **Ask the person first.**
- `PATCH /v1/tenants/{tenantId}/booking/services/{serviceId}` — Change a service. When: When the owner asks you to change how the shop is set up. Permission: `booking.setup.manage`. **Ask the person first.**
- `DELETE /v1/tenants/{tenantId}/booking/services/{serviceId}` — Remove a service from the menu. When: When the owner explicitly removes a service. Permission: `booking.setup.manage`. **Ask the person first.** Cannot be undone.
- `GET /v1/tenants/{tenantId}/booking/staff` — List staff members. When: To find a staff id and what they perform. Permission: `booking.view`.
- `POST /v1/tenants/{tenantId}/booking/staff` — Add a staff member. When: When the owner asks you to change how the shop is set up. Permission: `booking.setup.manage`. **Ask the person first.**
- `PATCH /v1/tenants/{tenantId}/booking/staff/{staffId}` — Change a staff member (profile, login link, services performed). When: When the owner asks you to change how the shop is set up. Permission: `booking.setup.manage`. **Ask the person first.**
- `DELETE /v1/tenants/{tenantId}/booking/staff/{staffId}` — Archive a staff member who left. When: When the owner says someone left. Permission: `booking.setup.manage`. **Ask the person first.** Cannot be undone.
- `GET /v1/tenants/{tenantId}/booking/policy` — Get the shop's booking policy. When: To explain notice, cancellation, reminder or fee rules. Permission: `booking.view`.
- `PATCH /v1/tenants/{tenantId}/booking/policy` — Change the shop's booking policy. When: When the owner asks you to change how the shop is set up. Permission: `booking.setup.manage`. **Ask the person first.**
- `GET /v1/tenants/{tenantId}/booking/staff/{staffId}/hours` — Get a staff member's weekly hours. When: To see when someone works each week. Permission: `booking.view`.
- `PUT /v1/tenants/{tenantId}/booking/staff/{staffId}/hours` — Replace a staff member's weekly hours. When: When the owner tells you when someone works, is away, or when the shop is closed. Permission: `booking.schedule.manage`. **Ask the person first.**
- `GET /v1/tenants/{tenantId}/booking/locations/{locationId}/hours` — Get a location's opening hours. When: To see when the shop is open. Permission: `booking.view`.
- `PUT /v1/tenants/{tenantId}/booking/locations/{locationId}/hours` — Replace a location's opening hours. When: When the owner tells you when someone works, is away, or when the shop is closed. Permission: `booking.schedule.manage`. **Ask the person first.**
- `GET /v1/tenants/{tenantId}/booking/time-off` — List time off and closures overlapping some dates. When: To see who is away or when the shop is closed. Permission: `booking.view`.
- `POST /v1/tenants/{tenantId}/booking/time-off` — Add time off (a staff member) or a closure (a location). When: When the owner tells you when someone works, is away, or when the shop is closed. Permission: `booking.schedule.manage`. **Ask the person first.**
- `DELETE /v1/tenants/{tenantId}/booking/time-off/{timeOffId}` — Remove time off or a closure. When: When the owner tells you when someone works, is away, or when the shop is closed. Permission: `booking.schedule.manage`. **Ask the person first.** Cannot be undone.
- `GET /v1/tenants/{tenantId}/booking/availability` — Open slots for a service, for the front desk. When: Before booking or moving an appointment, to find a free time. Permission: `booking.view`.
- `POST /v1/tenants/{tenantId}/booking/holds` — Hold a time while the front desk enters the client. When: First step of booking for the shop: hold the time, then book it with the client. Permission: `booking.appointments.manage`.
- `POST /v1/tenants/{tenantId}/booking/appointments` — Book a held time for a client. When: After createHold, once you have the client and the shop agreed to the booking. Permission: `booking.appointments.manage`. **Ask the person first.**
- `GET /v1/tenants/{tenantId}/booking/appointments` — Appointments starting on some dates (the calendar). When: To read the shop's calendar for a day or a week. Permission: `booking.view`.
- `GET /v1/tenants/{tenantId}/booking/appointments/{appointmentId}` — Get an appointment. When: To read one appointment and its current version. Permission: `booking.view`.
- `POST /v1/tenants/{tenantId}/booking/appointments/{appointmentId}/reschedule` — Move an appointment to another time (or staff member). When: When the shop or the client asks to move a booking (check availability first). Permission: `booking.appointments.manage`. **Ask the person first.**
- `POST /v1/tenants/{tenantId}/booking/appointments/{appointmentId}/cancel` — Cancel an appointment. When: When asked to cancel a booking. Permission: `booking.appointments.manage`. **Ask the person first.** Cannot be undone.
- `GET /v1/tenants/{tenantId}/booking/customers` — Search the shop's clients. When: To find an existing client before booking for them. Permission: `booking.clients.view`.
- `GET /v1/public/tenants/{slug}/booking/locations` — A shop's locations open for online booking. When: To tell someone where a shop is and which time zone it books in.
- `GET /v1/public/tenants/{slug}/booking/services` — A shop's services offered online, and its booking policy. When: To tell someone what a shop offers, for how long and at what price.
- `GET /v1/public/tenants/{slug}/booking/staff` — A shop's staff who take online bookings. When: To tell someone who works at a shop and what each person does.
- `GET /v1/public/tenants/{slug}/booking/availability` — Open slots for a service. When: To find when a service can be booked.
- `GET /v1/tenants/{tenantId}/booking/clients` — List or search the shop's client directory. When: To find a client when acting for the shop. Permission: `booking.clients.view`.
- `GET /v1/tenants/{tenantId}/booking/clients/{clientId}` — Read one client and their booking history. When: To read a client and their prior bookings for the shop. Permission: `booking.clients.view`.
- `POST /v1/tenants/{tenantId}/booking/clients` — Create a client record. When: When the shop asks to add a client. Permission: `booking.clients.manage`. **Ask the person first.** Cannot be undone.
- `PATCH /v1/tenants/{tenantId}/booking/clients/{clientId}` — Update client contact details or private notes. When: When the shop asks to correct a client record. Permission: `booking.clients.manage`. **Ask the person first.**

## Errors

| Code | Status | Meaning | What to do |
|---|---|---|---|
| `VALIDATION_FAILED` | 400 | The request is invalid. | Fix the fields listed in `issues` and send the request again. |
| `UNAUTHENTICATED` | 401 | Sign-in required. | Send a valid access token or API key in the Authorization header (refresh the session if it expired). |
| `FORBIDDEN` | 403 | You do not have permission to do this. | Ask an owner or admin of the tenant for the permission named in the message. |
| `TENANT_NOT_FOUND` | 404 | Tenant not found. | Check the tenant id; you must be a member of the tenant (or hold one of its API keys). |
| `IDEMPOTENCY_KEY_REUSED` | 422 | This Idempotency-Key was already used for a different request. | Use a new key for a new request; reuse a key only to retry the exact same request. |
| `RATE_LIMITED` | 429 | Too many requests. | Wait the number of seconds in remediation.retryAfterSeconds (or Retry-After) and try again. |
| `INTERNAL` | 500 | Something went wrong on our side. | Re-read the resource to see whether the change happened, then retry with the same Idempotency-Key. |
| `UNAVAILABLE` | 503 | The service is temporarily unavailable. | Retry later with the same Idempotency-Key. |
| `MEMBER_NOT_FOUND` | 404 | Member not found. | List the members and use one of their ids. |
| `STRIPE_NOT_CONFIGURED` | 503 | Stripe is not set up for this environment yet. | The product's operator must store this stage's Stripe keys (docs/billing-and-payments.md); retry after that. |
| `STRIPE_ERROR` | 502 | Stripe could not complete the request. | Retry with the same Idempotency-Key; if it keeps failing, look at the request in the Stripe Dashboard. |
| `PLAN_UPGRADE_REQUIRED` | 402 | The tenant's plan does not include this. | Upgrade the plan (billing.checkout, or the billing portal for a paid plan), then retry. |
| `PAYMENTS_NOT_ENABLED` | 404 | Payments are not part of this product. | Nothing to do: this product does not take payments for its tenants. |
| `PAYMENTS_ACCOUNT_MISSING` | 409 | The tenant has not set up payments yet. | Create the payments account (POST …/payments/account), then finish onboarding in the dashboard. |
| `PAYMENTS_ACCOUNT_NOT_READY` | 409 | The payments account cannot take payments yet. | Finish onboarding in the dashboard: Stripe still needs some details or is reviewing them. |
| `PAYMENT_CUSTOMER_NOT_FOUND` | 404 | Payment customer not found. | Use the id returned when the customer was created. |
| `PAYMENT_NOT_FOUND` | 404 | Payment not found. | Use the id of a payment of this tenant. |
| `PAYMENT_CARD_MISSING` | 409 | This customer has no card on file. | Save a card first (…/card-setup), or take the payment with the customer present. |
| `PAYMENT_NOT_REFUNDABLE` | 409 | This payment cannot be refunded. | Only succeeded payments with an amount left to refund can be refunded. |
| `MEDIA_TOO_LARGE` | 413 | This file is too large. | Images (JPEG, PNG, WebP, GIF) up to 10 MB, PDFs up to 25 MB, videos (MP4, MOV) up to 200 MB. |
| `UPLOAD_NOT_FOUND` | 404 | This upload is unknown or has expired. | Start a new upload (POST …/media/uploads) and send the file within 10 minutes. |
| `UPLOAD_INCOMPLETE` | 409 | The file has not arrived yet. | POST the file to the upload URL first (every field, then the file as `file`), then complete again. |
| `MEDIA_REJECTED` | 422 | The file is not what the upload announced. | Check the file type and size, then start a new upload with the real values. |
| `MEDIA_NOT_FOUND` | 404 | File not found. | List the files and use one of their ids. |
| `DOMAIN_NOT_ALLOWED` | 400 | This domain cannot be connected. | Use a domain you own, such as book.example.com: not one of ours, an IP address or a *.vercel.app name. |
| `DOMAIN_TAKEN` | 409 | This domain is already connected somewhere else. | Remove it from the workspace that verified ownership or the other Vercel project, then add it again. |
| `DOMAIN_LIMIT_REACHED` | 409 | A workspace can connect at most 10 domains. | Remove a domain you no longer use. |
| `DOMAIN_NOT_FOUND` | 404 | Domain not found. | List the domains and use one of their ids. |
| `CUSTOM_DOMAINS_UNAVAILABLE` | 503 | Custom domains are not available right now. | Try again in a few minutes; if it keeps failing, contact support. |
| `NOTE_NOT_FOUND` | 404 | Note not found. | List the notes and use one of their ids. |
| `LOCATION_NOT_FOUND` | 404 | Location not found. | Locations: List them and use one of their ids. |
| `SERVICE_NOT_FOUND` | 404 | Service not found. | Services: List them and use one of their ids. |
| `STAFF_NOT_FOUND` | 404 | Staff member not found. | Staff: List them and use one of their ids. |
| `TIME_OFF_NOT_FOUND` | 404 | Time off not found. | Time off: List them and use one of their ids. |
| `CUSTOMER_NOT_FOUND` | 404 | Client not found. | Clients: List them and use one of their ids. |
| `APPOINTMENT_NOT_FOUND` | 404 | Appointment not found. | Appointments: List them and use one of their ids. |
| `HOLD_NOT_FOUND` | 404 | This hold does not exist or was released. | Ask for availability again and pick one of the slots it returns. Then create a new hold. |
| `LOCATION_REQUIRED` | 400 | This shop has more than one location: say which one. | Pass locationId (from the locations list). |
| `SERVICE_NOT_OFFERED` | 409 | That staff member does not offer this service online. | Pick a staff member from the service's staffIds, or leave staffId out. |
| `SLOT_UNAVAILABLE` | 409 | That time cannot be booked (outside hours, too soon, too far ahead, or off the booking grid). | Ask for availability again and pick one of the slots it returns. |
| `SLOT_TAKEN` | 409 | Someone else just booked that time. | Ask for availability again and pick one of the slots it returns. |
| `APPOINTMENT_NOT_CHANGEABLE` | 409 | This appointment is not confirmed any more, so it cannot be moved or cancelled. | Read the appointment. Book a new one if the client still wants to come. |
| `VERSION_CONFLICT` | 409 | The appointment changed since you read it. | Read it again and retry with its current version as expectedVersion. |
| `STAFF_HAS_APPOINTMENTS` | 409 | This staff member has upcoming appointments. | Move them to someone else or cancel them, then archive the profile. |
| `MEMBERSHIP_ALREADY_LINKED` | 409 | That login is already linked to another staff profile. | Unlink it from the other profile first (membershipId: null). |
| `HOURS_OVERLAP` | 409 | Weekly hours overlap. | Remove the overlap: one person (or one location) has one window at a time on a weekday. |
| `BOOKING_HOLD_REFRESH_REQUIRED` | 409 | This hold needs to be refreshed before checkout. | Read availability and create a new hold with its holdCapability. |
| `BOOKING_DEPOSIT_REQUIRED` | 409 | A deposit is required before this booking can be confirmed. | Reserve, prepare and start the deposit checkout, then await verified payment finalization. |
| `CLIENT_CONTACT_CONFLICT` | 409 | A client already uses that contact. | Find the existing client or correct the email address or phone number. |
| `CLIENT_VERSION_CONFLICT` | 409 | The client changed since you read it. | Read the client again and retry with its current version as expectedVersion. |
