Skip to content

Developers

Read your community’s amenities and bookings, check availability, approve, decline or cancel requests, and receive signed events when a booking changes.

Base URL https://app.amenityreserve.com/api/v1

On this page
  1. Before you start
  2. Keys and authorization
  3. Requests and responses
  4. Pagination
  5. Errors
  6. Idempotency-Key
  7. Rate limits
  8. Endpoints
  9. Webhooks
  10. Verifying signatures
  11. Privacy and data

Before you start

The API works on one community at a time. A key belongs to a community, and every request answers for that community only. There is nothing to choose in the request, and an id from another community is reported as not found.

  • It comes with Amenity Gold and above. On a lower plan a key cannot be made, and an existing key answers plan_required.
  • Only the community’s main account makes, rolls and revokes keys. No team seat can be given that permission. Better Seasons staff can also revoke a key that has leaked.
  • Keys are for servers. A request that carries a browser Origin or Sec-Fetch-Site header is refused, and no CORS header is ever sent.

Keys and authorization

Sign in to Amenity Reserve and open Settings, choose Open API keys, then Make a key. Name it, and tick the scopes it needs. Making, rolling and revoking a key each ask for a fresh second-factor code. The full key is shown once, in that dialog, and cannot be shown again. Only a hash of it is stored. A community can hold up to ten live keys, and the list shows each key’s creator, last use and expiry. Rolling a key makes a new one and lets the old one run on for a short overlap. Revoking one takes effect on the next request.

A key looks like this, with a fixed prefix so secret scanners can find a leaked one. Keys made in a test deployment start with ar_test_ and work only there.

ar_live_7q2mk4x9hv3b_k3Jz9nQe0W1c8Yt5uR2bXoP6dLsA4vGh7mFiNwZ3xK9

^ar_(live|test)_([0-9a-hjkmnp-tv-z]{12})_([A-Za-z0-9_-]{43})$

Send the key as a bearer token on every request.

curl "https://app.amenityreserve.com/api/v1/bookings?status=pending" \
  -H "Authorization: Bearer ar_live_<your key>"

A key that is missing, malformed, unknown, revoked, expired or from the wrong deployment gets the same invalid_key answer. A key can do only what its scopes allow, and scopes cannot be changed after the key is made. Make a new key instead.

ScopeWhat it opens
amenities:readRead amenities. Spaces, their booking rules and the times already taken.
bookings:readRead bookings. Requests and bookings, with each member shown as a first name and a last initial.
bookings:writeApprove, decline and cancel. Answer requests and cancel bookings that have no payment on them. It also shows the notes admins wrote.
members:readRead members. The names, email addresses and street addresses of everyone on your list.
webhooks:manageManage webhooks. Let the tool subscribe to booking events.

Requests and responses

  • Every response is JSON, is never cached, and carries an AR-Request-Id header. Quote it when you ask us about a request.
  • Request bodies are JSON objects of at most 16 KB. Unknown fields are ignored. A wrongly typed field is a bad_param error.
  • Times are RFC 3339 in UTC, such as 2026-11-02T18:00:00Z. Each amenity carries its own timezone for display.
  • Within v1 changes only add fields, endpoints, event types and enum values. Ignore fields you do not know.
  • No endpoint creates a booking. Software can check when a space is free with the availability endpoint, and members book in Amenity Reserve. The only changes the API makes are to approve, decline or cancel an existing booking.
  • There are no money endpoints. A booking with a payment attached cannot be approved, declined or cancelled through the API: it answers payment_needs_dashboard.

Pagination

Lists take limit and cursor. Each page answers with has_more and next_cursor. Send next_cursor back as cursor to get the next page, and stop when has_more is false. A cursor is opaque. It works only for the list and community that made it, and anything else is a bad_cursor error.

{
  "data": [
    "…"
  ],
  "has_more": true,
  "next_cursor": "eyJ2IjoxLC…"
}

Errors

An error has a status, a type, a code to branch on, and a plain sentence for people. param appears when one field or scope is to blame.

{
  "error": {
    "type": "permission",
    "code": "missing_scope",
    "message": "This key cannot do that. It needs the members:read scope.",
    "param": "members:read",
    "request_id": "req_01JC8M4Q2R6T0V3X5Z7B9D1F3H"
  }
}
StatusTypeCodeMessage
400invalid_requestbad_jsonThe body must be a JSON object.
400invalid_requestbad_paramlimit must be a whole number from 1 to 100.
400invalid_requestbad_cursorThat cursor is not valid for this list.
400invalid_requestbody_too_largeThe body can be at most 16 KB.
400invalid_requestidempotency_key_requiredSend an Idempotency-Key header of 1 to 255 printable characters.
401authenticationinvalid_keyThis key is missing, wrong, expired or revoked.
403permissionmissing_scopeThis key cannot do that. It needs the members:read scope.
403permissionplan_requiredThis community’s plan does not include the API.
403permissionbrowser_not_allowedSend API requests from a server, not from a browser.
403permissioncommunity_unverifiedThis community has not been verified yet.
403permissionpayment_needs_dashboardThis booking has a payment. Approve, decline or cancel it in the Amenity Reserve dashboard.
404not_foundnot_foundThat does not exist.
409conflictstateThe booking is not waiting for an answer.
409conflictidempotency_in_progressA request with that Idempotency-Key is still running. Wait a moment and send it again.
409conflictcommunity_leavingThis community is leaving Amenity Reserve.
422invalid_requestidempotency_key_reusedThat Idempotency-Key was already used with a different request.
429rate_limitrate_limitedToo many requests. Wait a moment and try again.
500api_errorinternalSomething went wrong on our side.
503api_errorunavailableThe API cannot answer just now. Try again in a moment.

A 5xx answer carries no detail. The request id is enough for us to find it.

Idempotency-Key

Every POST needs an Idempotency-Key header of 1 to 255 printable characters. Send the same key and the same request to retry safely: the first answer comes back with an Idempotent-Replayed: true header, and nothing happens twice. The same key with a different request is idempotency_key_reused. A key whose first request is still running is idempotency_in_progress. Answers are kept for 24 hours. A 5xx answer is not kept, so a retry runs again.

curl -X POST https://app.amenityreserve.com/api/v1/bookings/9b1d7e44-2c0a-4f6b-8e3d-5a7f1c2e9d01/approve \
  -H "Authorization: Bearer ar_live_<your key>" \
  -H "Idempotency-Key: approve-9b1d7e44-2c0a-4f6b-8e3d-5a7f1c2e9d01" \
  -H "Content-Type: application/json" \
  -d '{"note":"Approved."}'

Rate limits

LimitRequests
Reads, per key120 every minute
Writes, per key30 every minute
All keys of one community600 every minute
All requests from one IP address300 every minute
Requests with a wrong key, per IP address20 every 10 minutes

Over a limit the answer is 429 rate_limited with Retry-After, RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset. Wait the number of seconds in Retry-After. The windows are fixed, not sliding.

Endpoints

Paths are relative to https://app.amenityreserve.com/api/v1.

GET /me

The community and the key making the request. Any valid key may call it, so it works as a connection test.

Scope: any valid key. Success: 200.

{
  "community": {
    "id": "0a7e3c11-8d24-4b6f-9c05-2e1f7a6b3d90",
    "name": "Robins Drive HOA",
    "kind": "association",
    "timezone": "America/Chicago",
    "plan": "Amenity Gold"
  },
  "key": {
    "id": "e8b2f6a0-3c14-4a9d-8f70-1d5c9b2a7e33",
    "kid": "7q2mk4x9hv3b",
    "name": "Zapier",
    "scopes": [
      "bookings:read"
    ],
    "mode": "live"
  }
}

GET /amenities

The community’s spaces that are set up, with the booking-shape rules for each, by name.

Scope: amenities:read. Success: 200.

  • limit query Page size from 1 to 100. The default is 50.
  • cursor query The next_cursor from the previous page.
{
  "data": [
    {
      "id": "3f6c0d52-7a1b-4e0e-9a55-6d1c1b0f8a11",
      "object": "amenity",
      "name": "Clubhouse",
      "template": "clubhouse",
      "status": "published",
      "timezone": "America/Chicago",
      "rules": {
        "needs_approval": true,
        "min_minutes": 60,
        "max_minutes": 240,
        "max_headcount": 50,
        "advance_days": 90,
        "rules_version": 4
      }
    }
  ],
  "has_more": false,
  "next_cursor": null
}

GET /amenities/{id}

One space.

Scope: amenities:read. Success: 200.

  • id path, required The amenity id.
{
  "id": "3f6c0d52-7a1b-4e0e-9a55-6d1c1b0f8a11",
  "object": "amenity",
  "name": "Clubhouse",
  "template": "clubhouse",
  "status": "published",
  "timezone": "America/Chicago",
  "rules": {
    "needs_approval": true,
    "min_minutes": 60,
    "max_minutes": 240,
    "max_headcount": 50,
    "advance_days": 90,
    "rules_version": 4
  }
}

GET /amenities/{id}/availability

The times already held for a space, with no names. It shows what a member sees.

Scope: amenities:read. Success: 200.

  • id path, required The amenity id.
  • from query, required RFC 3339 time.
  • to query, required RFC 3339 time after from, at most 31 days later.
{
  "busy": [
    {
      "starts_at": "2026-11-02T18:00:00Z",
      "ends_at": "2026-11-02T22:00:00Z"
    }
  ]
}

GET /bookings

Bookings and requests, newest first. Each member shows as a first name and a last initial.

Scope: bookings:read. Success: 200.

  • status query pending, confirmed, declined, cancelled or completed. Repeat the parameter to ask for several.
  • amenity_id query Only this space.
  • starts_after query RFC 3339 time.
  • starts_before query RFC 3339 time.
  • updated_since query RFC 3339 time.
  • order query starts_at lists by start time, earliest first. Without it the list is newest first.
  • expand query resident adds the member’s name, email, address and role. It needs the members:read scope.
  • limit query Page size from 1 to 100. The default is 50.
  • cursor query The next_cursor from the previous page.
{
  "data": [
    {
      "id": "9b1d7e44-2c0a-4f6b-8e3d-5a7f1c2e9d01",
      "object": "booking",
      "amenity_id": "3f6c0d52-7a1b-4e0e-9a55-6d1c1b0f8a11",
      "status": "pending",
      "starts_at": "2026-11-02T18:00:00Z",
      "ends_at": "2026-11-02T22:00:00Z",
      "headcount": 40,
      "rules_version": 4,
      "answers": {
        "purpose": "Birthday",
        "alcohol": false
      },
      "payment_status": "none",
      "resident": {
        "id": "5d2e8a90-1b3c-4d7e-a6f1-0c9b8a7d6e52",
        "display_name": "Dana K."
      },
      "agreement": {
        "mode": "checkbox",
        "signed_at": "2026-10-28T15:02:11Z"
      },
      "decided_at": null,
      "cancelled_at": null,
      "cancelled_by": null,
      "time_changes": 0,
      "created_at": "2026-10-28T15:02:11Z",
      "updated_at": "2026-10-28T15:02:11Z"
    }
  ],
  "has_more": true,
  "next_cursor": "eyJ2IjoxLCJvIjoiY3JlYXRlZF9hdCIsImsiOlsiMjAyNi0xMC0yOFQxNTowMjoxMS4wMDAwMDBaIiwiOWIxZDdlNDQtMmMwYS00ZjZiLThlM2QtNWE3ZjFjMmU5ZDAxIl0sImEiOiI0YzFlOWYyYSJ9"
}

GET /bookings/{id}

One booking.

Scope: bookings:read. Success: 200.

  • id path, required The booking id.
  • expand query resident, as above.
{
  "id": "9b1d7e44-2c0a-4f6b-8e3d-5a7f1c2e9d01",
  "object": "booking",
  "amenity_id": "3f6c0d52-7a1b-4e0e-9a55-6d1c1b0f8a11",
  "status": "pending",
  "starts_at": "2026-11-02T18:00:00Z",
  "ends_at": "2026-11-02T22:00:00Z",
  "headcount": 40,
  "rules_version": 4,
  "answers": {
    "purpose": "Birthday",
    "alcohol": false
  },
  "payment_status": "none",
  "resident": {
    "id": "5d2e8a90-1b3c-4d7e-a6f1-0c9b8a7d6e52",
    "display_name": "Dana K."
  },
  "agreement": {
    "mode": "checkbox",
    "signed_at": "2026-10-28T15:02:11Z"
  },
  "decided_at": null,
  "cancelled_at": null,
  "cancelled_by": null,
  "time_changes": 0,
  "created_at": "2026-10-28T15:02:11Z",
  "updated_at": "2026-10-28T15:02:11Z"
}

POST /bookings/{id}/approve

Approve a pending request. The member and the attendant are mailed exactly as when an admin approves in the dashboard.

Scope: bookings:write. Success: 200.

  • id path, required The booking id.
  • Idempotency-Key header, required One to 255 printable characters. Send the same key again to retry the same request.
  • note body Optional text of up to 500 characters.
{
  "id": "9b1d7e44-2c0a-4f6b-8e3d-5a7f1c2e9d01",
  "object": "booking",
  "status": "confirmed"
}

POST /bookings/{id}/decline

Decline a pending request.

Scope: bookings:write. Success: 200.

  • id path, required The booking id.
  • Idempotency-Key header, required One to 255 printable characters. Send the same key again to retry the same request.
  • note body Optional text of up to 500 characters.
{
  "id": "9b1d7e44-2c0a-4f6b-8e3d-5a7f1c2e9d01",
  "object": "booking",
  "status": "declined"
}

POST /bookings/{id}/cancel

Cancel a pending or confirmed booking.

Scope: bookings:write. Success: 200.

  • id path, required The booking id.
  • Idempotency-Key header, required One to 255 printable characters. Send the same key again to retry the same request.
  • reason body Optional text of up to 500 characters.
{
  "id": "9b1d7e44-2c0a-4f6b-8e3d-5a7f1c2e9d01",
  "object": "booking",
  "status": "cancelled"
}

GET /members

The community’s member list, newest first.

Scope: members:read. Success: 200.

  • email query An exact address. This is the find-a-member search.
  • standing query active, suspended or inactive.
  • updated_since query RFC 3339 time.
  • include_archived query true to include archived members. They are left out by default.
  • limit query Page size from 1 to 100. The default is 50.
  • cursor query The next_cursor from the previous page.
{
  "data": [
    {
      "id": "5d2e8a90-1b3c-4d7e-a6f1-0c9b8a7d6e52",
      "object": "member",
      "name": "Dana Kim",
      "email": "dana@example.com",
      "address": "12 Robins Dr",
      "household_id": "b7e1c0a94d2f",
      "role": "owner",
      "standing": "active",
      "verified": true,
      "archived": false,
      "created_at": "2026-09-01T12:00:00Z"
    }
  ],
  "has_more": true,
  "next_cursor": "eyJ2IjoxLCJvIjoiY3JlYXRlZF9hdCIsImsiOlsiMjAyNi0xMC0yOFQxNTowMjoxMS4wMDAwMDBaIiwiOWIxZDdlNDQtMmMwYS00ZjZiLThlM2QtNWE3ZjFjMmU5ZDAxIl0sImEiOiI0YzFlOWYyYSJ9"
}

GET /members/{id}

One member.

Scope: members:read. Success: 200.

  • id path, required The member id.
{
  "id": "5d2e8a90-1b3c-4d7e-a6f1-0c9b8a7d6e52",
  "object": "member",
  "name": "Dana Kim",
  "email": "dana@example.com",
  "address": "12 Robins Dr",
  "household_id": "b7e1c0a94d2f",
  "role": "owner",
  "standing": "active",
  "verified": true,
  "archived": false,
  "created_at": "2026-09-01T12:00:00Z"
}

Webhook endpoints

These manage where events are sent. Adding one needs a plan that includes the API. A community can have 10 endpoints turned on, can add or roll 10 signing secrets a day, and each endpoint can be tested 5 times a minute.

GET /webhook_endpoints

The community’s webhook endpoints that are turned on. Only the host of each URL is shown.

Scope: webhooks:manage. Success: 200.

{
  "data": [
    {
      "id": "c41a7b30-5e92-4d08-b1f6-7a3e9d2c8f64",
      "object": "webhook_endpoint",
      "url_host": "hooks.example.com",
      "events": [
        "booking.created",
        "booking.approved"
      ],
      "status": "enabled",
      "disabled_reason": null,
      "source": "api",
      "created_at": "2026-10-28T15:00:00Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

POST /webhook_endpoints

Add an endpoint. Only the community’s main account can make the key that calls this. The response holds its signing secret once, and it cannot be fetched again.

Scope: webhooks:manage. Success: 201.

  • Idempotency-Key header, required One to 255 printable characters. Send the same key again to retry the same request.
  • url body, required An https address on a public host.
  • events body, required A list of event names.
{
  "id": "c41a7b30-5e92-4d08-b1f6-7a3e9d2c8f64",
  "object": "webhook_endpoint",
  "url_host": "hooks.example.com",
  "events": [
    "booking.created",
    "booking.approved"
  ],
  "status": "enabled",
  "disabled_reason": null,
  "source": "api",
  "created_at": "2026-10-28T15:00:00Z",
  "secret": "whsec_Zr4mQ9v2LxT7cNf0HkB5uWjE8sYdA1pG3oIeVn6RtXw"
}

DELETE /webhook_endpoints/{id}

Turn an endpoint off for good and drop what was queued for it.

Scope: webhooks:manage. Success: 200.

  • id path, required The endpoint id.
{
  "id": "c41a7b30-5e92-4d08-b1f6-7a3e9d2c8f64",
  "object": "webhook_endpoint",
  "deleted": true
}

POST /webhook_endpoints/{id}/test

Queue a ping event to the endpoint.

Scope: webhooks:manage. Success: 202.

  • id path, required The endpoint id.
  • Idempotency-Key header, required One to 255 printable characters. Send the same key again to retry the same request.
{
  "id": "c41a7b30-5e92-4d08-b1f6-7a3e9d2c8f64",
  "object": "webhook_endpoint",
  "test": "queued",
  "event_id": "evt_01JC8M4Q2R6T0V3X5Z7B9D1F3H"
}

An address is refused when it is not https on the default port, carries a username, names an IP address, points at one of our own domains, or resolves to a private network. The error code says which:

  • bad_url That is not a web address we can send to.
  • not_https The address must start with https://.
  • port Only the standard https port is allowed.
  • credentials The address cannot contain a user name or password.
  • ip_literal Use a host name, not an IP address.
  • own_domain That address belongs to Amenity Reserve or its partners, so we cannot send there.
  • internal_name That host name is not a public address.

Webhooks

A webhook is a signed POST to your endpoint when a booking changes. The events are:

EventSent when
booking.createdSent when a member asks for a space, or books one that needs no approval.
booking.approvedSent when an admin approves a request.
booking.declinedSent when an admin declines a request.
booking.cancelledSent when a member or an admin cancels, with who did it.
booking.changedSent when an admin moves a booking, with the old time.
pingSent only when you call the test endpoint. Its data is empty.

The body is the booking as it is at the moment of sending, in the same shape the API returns, with the member shown as a first name and a last initial. previous holds what changed. api_version is 2026-11-01.

{
  "id": "evt_01JC8M4Q2R6T0V3X5Z7B9D1F3H",
  "type": "booking.cancelled",
  "api_version": "2026-11-01",
  "created": "2026-10-29T14:05:09Z",
  "community_id": "0a7e3c11-8d24-4b6f-9c05-2e1f7a6b3d90",
  "data": {
    "object": {
      "id": "9b1d7e44-2c0a-4f6b-8e3d-5a7f1c2e9d01",
      "object": "booking",
      "amenity_id": "3f6c0d52-7a1b-4e0e-9a55-6d1c1b0f8a11",
      "status": "cancelled",
      "starts_at": "2026-11-02T18:00:00Z",
      "ends_at": "2026-11-02T22:00:00Z",
      "headcount": 40,
      "rules_version": 4,
      "answers": {
        "purpose": "Birthday",
        "alcohol": false
      },
      "payment_status": "none",
      "resident": {
        "id": "5d2e8a90-1b3c-4d7e-a6f1-0c9b8a7d6e52",
        "display_name": "Dana K."
      },
      "agreement": {
        "mode": "checkbox",
        "signed_at": "2026-10-28T15:02:11Z"
      },
      "decided_at": null,
      "cancelled_at": "2026-10-29T14:05:09Z",
      "cancelled_by": "admin",
      "time_changes": 0,
      "created_at": "2026-10-28T15:02:11Z",
      "updated_at": "2026-10-28T15:02:11Z"
    },
    "previous": {
      "status": "confirmed"
    }
  }
}

Each delivery carries these headers:

AR-Event-Id: evt_01JC8M4Q2R6T0V3X5Z7B9D1F3H
AR-Event-Type: booking.cancelled
AR-Signature: t=1793642400,v1=1d259a7ecdc4863822b1cbb5989862afca69eae1f11266c56c2343d5c7637f69
Content-Type: application/json
User-Agent: AmenityReserve-Webhooks/1

Delivery

  • Delivery is at least once. The same event can arrive more than once, so dedupe on the id in the signed body. The AR-Event-Id header is not covered by the signature and is for your logs.
  • Answer with any 2xx within 10 seconds. A redirect counts as a failure and is not followed. A 410 turns the endpoint off at once.
  • At most 2 deliveries to one endpoint are in flight at a time. Order is not promised.
  • Anything else is retried, up to 9 attempts over about three days. Each retry is signed again.
  • An endpoint is turned off after 50 failures in a row once the first of them is more than 3 days old, and the community’s admins are mailed. Any 2xx resets the count.
AttemptWhen
1Sent at once
21 minute after the attempt before
35 minutes after the attempt before
430 minutes after the attempt before
52 hours after the attempt before
66 hours after the attempt before
712 hours after the attempt before
824 hours after the attempt before
924 hours after the attempt before

Verifying signatures

The secret for an endpoint starts with whsec_ and is shown once, when the endpoint is added. The AR-Signature header holds a timestamp and one or more signatures: t=<unix seconds>,v1=<hex>. Each v1 is the hex HMAC-SHA256, keyed with the secret, of the timestamp, a dot, and the raw body. Rolling a secret sends two v1 values for 24 hours, so accept either one.

  1. Read the raw body before parsing it.
  2. Reject the request if the timestamp is more than 300 seconds from your clock.
  3. Compute the HMAC and compare it with every v1 in constant time.
  4. Parse the body, then dedupe on its id.
const crypto = require('node:crypto');

// rawBody: the request body exactly as received, before any JSON parsing.
// header: the AR-Signature header. secret: the whsec_ value for this endpoint.
function verifyArSignature(rawBody, header, secret, nowSeconds = Math.floor(Date.now() / 1000)) {
  const parts = String(header || '').split(',').map((p) => p.trim());
  const t = Number((parts.find((p) => p.startsWith('t=')) || '').slice(2));
  const sigs = parts.filter((p) => p.startsWith('v1=')).map((p) => p.slice(3));
  if (!Number.isInteger(t) || t <= 0 || sigs.length === 0) return false;
  if (Math.abs(nowSeconds - t) > 300) return false; // too old or too new
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`, 'utf8').digest();
  let ok = false;
  for (const s of sigs) {
    const given = Buffer.from(s, 'hex');
    if (given.length === expected.length && crypto.timingSafeEqual(given, expected)) ok = true;
  }
  return ok;
}

module.exports = { verifyArSignature };

Test vectors

Each signature below was made with openssl dgst -sha256 -hmac over t + "." + body. A correct verifier accepts each one with its own timestamp as the current time.

[
  {
    "secret": "whsec_test_secret_1",
    "t": 1793642400,
    "body": "{\"id\":\"evt_0001\",\"type\":\"ping\"}",
    "signature": "1d259a7ecdc4863822b1cbb5989862afca69eae1f11266c56c2343d5c7637f69"
  },
  {
    "secret": "whsec_test_secret_2",
    "t": 1793642400,
    "body": "{\"id\":\"evt_0001\",\"type\":\"ping\"}",
    "signature": "d370ab9a1a8dd991b16b335c1e930c34542bc2a560f7904aca16ee38492635e9"
  },
  {
    "secret": "whsec_test_secret_1",
    "t": 1793642400,
    "body": "{\"note\":\"café ✓\"}",
    "signature": "9cec184fc28de6cc06b7395fad06ff73f6f2c0141f7a34019122bef60e67320f"
  }
]

Privacy and data

An API key lets a tool your community connects read your community’s bookings and amenities. With the scopes your community grants, it can also read the member list and approve, decline or cancel bookings. By default it sees as little as the task needs, and some things are never sent.

  • With bookings:read alone, a booking shows its member as an id and a name such as “Dana K.”, a first name and a last initial.
  • With members:read as well, the member’s full name, email address, street address, role and standing can be read, and the member list opens. The key dialog says this before the owner ticks the scope.
  • Phone numbers and household notes are never sent.
  • Of a signed agreement, only the way it was signed and the time are sent. The typed name, initials, IP address and browser details are not.
  • Payment amounts, Stripe ids and deposit details are never sent. A booking shows only a short payment status.
  • Attendant email addresses and the text of the rules are never sent.
  • The answers on a booking request (purpose, alcohol, outside vendors) are sent, because they are the request.
  • The notes an admin writes on a decision or a cancellation are sent only to a key with bookings:write.
  • If a member’s data has been erased, the booking shows no member and resident_erased is true.
  • Webhooks carry the minimal member only, and cannot be expanded. Each is built at the time it is sent, so it reflects the booking and any erasure as they are then.

Your community chooses where a key’s data goes. If your community connects another tool with an API key, we send that tool the details its scopes open, including member details if your community grants members:read, on your community’s instruction. That tool is your community’s choice, and its own privacy terms apply. Your community is responsible for the keys it makes and for where it sends member data, under the Amenity Reserve API terms. Better Seasons staff can revoke a key that is leaked or abused. The API and its limits can change, with at least 30 days’ notice of a change that removes a field or an endpoint, except a change needed for security.

Amenity Reserve

See Amenity Reserve set up with your community’s amenities

Add your first space with the guided setup and open it for bookings. Prefer a walkthrough? Schedule a call.