{"id":"34a8564d6d80a780149c67a8870833e23db0702a09c8fa4d0386e06ff9d5e20a","pubkey":"da19f1cd34beca44be74da4b306d9d1dd86b6343cef94ce22c49c6f59816e5bd","created_at":1774618279,"kind":30817,"tags":[["title","NIP-BOOKING: Calendar Availability & Booking"],["k","30582"],["k","30583"],["k","30584"],["d","nip-booking"]],"content":"NIP-BOOKING\n===========\n\nCalendar Availability & Booking\n---------------------------------\n\n`draft` `optional`\n\nThree addressable event kinds for calendar-based scheduling on Nostr; a provider publishes available time slots (including recurring patterns), a requester books a specific slot, and either party can cancel.\n\n> **Design principle:** Booking events coordinate scheduling. They communicate availability and record reservations. They do not enforce exclusivity at the relay level; the consuming application is responsible for preventing double-bookings and handling cancellation policies.\n\n> **Standalone.** This NIP works independently on any Nostr application.\n\n## Motivation\n\nNostr has NIP-52 for calendar events and NIP-53 for live activities, but no standard mechanism for **advertising availability and accepting bookings**. Many workflows require structured scheduling:\n\n- **Appointment booking** - a professional publishes available slots, clients book sessions\n- **Recurring services** - weekly or monthly time slots with availability management\n- **Venue & resource reservation** - rooms, equipment, or shared spaces with time-slot allocation\n- **Event ticketing** - time-limited entry slots with capacity management\n- **Tutoring & consulting** - one-on-one sessions with calendar-based scheduling\n\nWithout a standard, each scheduling application invents its own availability and booking scheme. NIP-BOOKING provides three minimal, composable primitives that any Nostr application can adopt for time-based coordination. Applications MAY compose with NIP-APPROVAL for multi-party confirmation and NIP-VARIATION for scope changes, but NIP-BOOKING works independently.\n\n## Relationship to Existing NIPs\n\n- **NIP-52 (Calendar Events):** Calendar events are confirmed, scheduled happenings (\"I'm hosting a workshop on Friday\"). Availability Calendars advertise when a provider is free; Booking Slots reserve time. The two are complementary: a provider publishes NIP-BOOKING availability, a requester books a slot, and the confirmed booking may produce a NIP-52 calendar event.\n- **NIP-53 (Live Activities):** Live activities represent ongoing events with real-time participation. Booking Slots are future reservations. A booked session might become a live activity when it starts, but the scheduling and the activity are distinct concerns.\n- **NIP-APPROVAL (kinds 30570-30571):** Booking confirmation uses NIP-APPROVAL. The provider creates an Approval Gate (kind 30570) referencing the Booking Slot, then responds with an Approval Response (kind 30571). This enables confirmation, rejection, or revision requests. See [Composing with NIP-APPROVAL](#composing-with-nip-approval) below.\n- **NIP-VARIATION (kind 30579):** Rescheduling uses NIP-VARIATION. Either party publishes a Variation Request with `variation_type: schedule_change` and proposed new times. The other party approves via NIP-APPROVAL. See [Composing with NIP-VARIATION](#composing-with-nip-variation) below.\n- **RFC 5545 (iCalendar):** Recurrence patterns align with iCalendar RRULE concepts but use simpler tag-based encoding for lightweight calendars, or full RRULE strings for complex recurring patterns.\n\n## Kinds\n\n| kind  | description           |\n| ----- | --------------------- |\n| 30582 | Availability Calendar |\n| 30583 | Booking Slot          |\n| 30584 | Booking Cancellation  |\n\nAll three kinds are addressable events (NIP-01). The `d` tag format ensures each event occupies a unique slot, allowing updates via republication.\n\n---\n\n## Availability Calendar (`kind:30582`)\n\nPublished by a provider to advertise their available time slots. Addressable; the provider can update their calendar by republishing with the same `d` tag, typically to mark slots as booked or blocked.\n\nA calendar MAY declare a recurring pattern using optional recurrence tags. When recurrence tags are present, the calendar acts as both an explicit slot list and a recurring availability declaration. Clients expand recurrence patterns into concrete slots and merge them with any explicit `slot` tags on the same event.\n\n### Explicit Slots Example\n\nA calendar with individually listed time slots for a specific period:\n\n```json\n{\n    \"kind\": 30582,\n    \"pubkey\": \"<provider-hex-pubkey>\",\n    \"created_at\": 1698774000,\n    \"tags\": [\n        [\"d\", \"<provider-hex-pubkey>:calendar:2026-W09\"],\n        [\"t\", \"availability-calendar\"],\n        [\"g\", \"gcpuuz\"],\n        [\"slot\", \"1698822000\", \"1698832800\", \"available\"],\n        [\"slot\", \"1698832800\", \"1698843600\", \"available\"],\n        [\"slot\", \"1698908400\", \"1698919200\", \"available\"],\n        [\"slot\", \"1698919200\", \"1698930000\", \"blocked\"],\n        [\"slot_duration_minutes\", \"180\"],\n        [\"max_bookings_per_slot\", \"1\"],\n        [\"alt\", \"Availability calendar with 4 slots\"]\n    ],\n    \"content\": \"\",\n    \"id\": \"<32-bytes lowercase hex>\",\n    \"sig\": \"<64-bytes lowercase hex>\"\n}\n```\n\n### Recurring Pattern Example\n\nA calendar with recurrence tags declares a repeating availability pattern. Clients expand the RRULE into concrete slots. A single recurring calendar can express complex schedules like \"every Monday and Thursday 10:00-11:00, except bank holidays, until December 2026\":\n\n```json\n{\n    \"kind\": 30582,\n    \"pubkey\": \"<provider-hex-pubkey>\",\n    \"created_at\": 1698780000,\n    \"tags\": [\n        [\"d\", \"<provider-hex-pubkey>:calendar:appointments\"],\n        [\"t\", \"availability-calendar\"],\n        [\"recurrence\", \"FREQ=WEEKLY;BYDAY=MO,TH\"],\n        [\"recurrence_start\", \"10:00\"],\n        [\"recurrence_start\", \"14:00\"],\n        [\"slot_duration_minutes\", \"60\"],\n        [\"g\", \"gcpuuz\"],\n        [\"max_bookings_per_slot\", \"1\"],\n        [\"pricing\", \"7500\"],\n        [\"currency\", \"GBP\"],\n        [\"timezone\", \"Europe/London\"],\n        [\"expiration\", \"1735689600\"],\n        [\"exclude_date\", \"2026-03-17\"],\n        [\"exclude_date\", \"2026-04-06\"],\n        [\"cancellation_window_hours\", \"48\"],\n        [\"cancellation_fee\", \"3750\"],\n        [\"cancellation_fee_currency\", \"GBP\"],\n        [\"alt\", \"Recurring availability: Mon/Thu, 60 min slots, 7500 GBP\"]\n    ],\n    \"content\": \"\",\n    \"id\": \"<32-bytes lowercase hex>\",\n    \"sig\": \"<64-bytes lowercase hex>\"\n}\n```\n\nThis expands to four slots per week: Monday 10:00, Monday 14:00, Thursday 10:00, Thursday 14:00.\n\n### Mixed Example\n\nA calendar MAY combine explicit slots with recurrence tags. Explicit `slot` tags override the recurring pattern for specific times:\n\n```json\n{\n    \"kind\": 30582,\n    \"pubkey\": \"<provider-hex-pubkey>\",\n    \"created_at\": 1698780000,\n    \"tags\": [\n        [\"d\", \"<provider-hex-pubkey>:calendar:tutoring:2026-W12\"],\n        [\"t\", \"availability-calendar\"],\n        [\"recurrence\", \"FREQ=WEEKLY;BYDAY=MO,WE,FR\"],\n        [\"recurrence_start\", \"09:00\"],\n        [\"slot_duration_minutes\", \"60\"],\n        [\"timezone\", \"Europe/London\"],\n        [\"slot\", \"1699254000\", \"1699257600\", \"blocked\"],\n        [\"g\", \"gcpuuz\"],\n        [\"alt\", \"Recurring availability: Mon/Wed/Fri 09:00, 60 min slots\"]\n    ],\n    \"content\": \"\",\n    \"id\": \"<32-bytes lowercase hex>\",\n    \"sig\": \"<64-bytes lowercase hex>\"\n}\n```\n\nHere the recurring pattern generates Monday/Wednesday/Friday 09:00 slots, but the explicit `slot` tag blocks one specific time window.\n\n### Tags\n\n* `d` (REQUIRED): Addressable event identifier. RECOMMENDED format: `<provider_pubkey>:calendar:<period>` for period-based calendars, or `<provider_pubkey>:calendar:<category>` for recurring calendars. Applications MAY use any d-tag format that ensures uniqueness.\n* `t` (REQUIRED): Protocol family marker. MUST be `\"availability-calendar\"`.\n* `slot` (RECOMMENDED, multiple): Explicit time slots. Format: `[\"slot\", \"<start_unix>\", \"<end_unix>\", \"<status>\"]` where `status` is `\"available\"`, `\"booked\"`, or `\"blocked\"`. A calendar event MAY contain multiple `slot` tags.\n* `g` (RECOMMENDED): Geohash of the service area.\n* `slot_duration_minutes` (RECOMMENDED): Default slot duration in minutes. REQUIRED when `recurrence` is present.\n* `max_bookings_per_slot` (OPTIONAL): Maximum concurrent bookings per slot (defaults to 1 if omitted).\n* `expiration` (OPTIONAL): Unix timestamp for calendar validity period. Clients SHOULD use NIP-40 `expiration` for relay-level enforcement.\n\n**Recurrence tags** (all OPTIONAL; when `recurrence` is present, `recurrence_start` and `slot_duration_minutes` become REQUIRED):\n\n* `recurrence` (OPTIONAL): RRULE string conforming to [RFC 5545, Section 3.3.10](https://datatracker.ietf.org/doc/html/rfc5545#section-3.3.10). See [RRULE Format](#rrule-format) below.\n* `recurrence_start` (OPTIONAL, multiple): Start time of each recurring slot in `HH:MM` format (24-hour, UTC unless `timezone` is specified). Multiple tags are allowed for multi-slot days.\n* `timezone` (OPTIONAL): Provider's local timezone in IANA format (e.g. `Europe/London`). If omitted, clients MUST default to UTC.\n* `exclude_date` (OPTIONAL, multiple): ISO 8601 date to exclude from the pattern (e.g. bank holidays).\n* `pricing` (OPTIONAL): Price per slot in smallest currency unit (pence for GBP, cents for USD, satoshis for SAT).\n* `currency` (OPTIONAL): Currency code (ISO 4217).\n* `cancellation_window_hours` (OPTIONAL): Minimum notice hours for penalty-free cancellation.\n* `cancellation_fee` (OPTIONAL): Fee in smallest currency unit when cancelled outside the window.\n* `cancellation_fee_currency` (OPTIONAL): Currency code for the cancellation fee.\n* `p` (OPTIONAL, multiple): Additional parties to notify of availability changes.\n* `ref` (OPTIONAL): External reference (e.g. practice management system ID).\n\nApplications MAY define additional constraint tags (e.g. environmental, seasonal, or capacity constraints) beyond those listed here.\n\n**Content:** Empty string or NIP-44 encrypted JSON with additional scheduling metadata (booking policies, cancellation terms, pricing per slot, service descriptions, preparation instructions).\n\n### Slot Tag Format\n\nEach explicit slot is encoded as a `slot` tag with structured positional values:\n\n```\n[\"slot\", \"<start_unix>\", \"<end_unix>\", \"<status>\"]\n```\n\n- `start_unix`: Unix timestamp for slot start time\n- `end_unix`: Unix timestamp for slot end time\n- `status`: One of `\"available\"`, `\"booked\"`, or `\"blocked\"`\n\nProviders update slot statuses by republishing the entire calendar event with revised `slot` tags.\n\n### RRULE Format\n\nWhen a calendar includes recurrence tags, the `recurrence` tag contains an RRULE string using a subset of the iCalendar specification (RFC 5545, Section 3.3.10). Supported RRULE properties:\n\n| Property   | Description                  | Example                                   |\n|------------|------------------------------|-------------------------------------------|\n| `FREQ`     | Recurrence frequency         | `DAILY`, `WEEKLY`, `MONTHLY`, `YEARLY`    |\n| `BYDAY`    | Days of the week             | `MO`, `TU`, `WE`, `TH`, `FR`, `SA`, `SU` |\n| `BYHOUR`   | Hours of the day (0-23)      | `10`, `14`                                |\n| `INTERVAL` | Interval between recurrences | `1` (every week), `2` (every other week)  |\n| `COUNT`    | Number of occurrences        | `52` (52 weeks)                           |\n| `UNTIL`    | End date (ISO 8601)          | `20261231T235959Z`                        |\n| `WKST`     | Week start day               | `MO` (Monday)                             |\n\n**Example RRULE patterns:**\n\n| Schedule                           | RRULE                                         |\n|------------------------------------|-----------------------------------------------|\n| Every Monday                       | `FREQ=WEEKLY;BYDAY=MO`                        |\n| Monday and Thursday                | `FREQ=WEEKLY;BYDAY=MO,TH`                     |\n| Every weekday                      | `FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR`           |\n| Every other Saturday               | `FREQ=WEEKLY;INTERVAL=2;BYDAY=SA`             |\n| First Monday of each month         | `FREQ=MONTHLY;BYDAY=1MO`                      |\n| 52 weekly sessions                 | `FREQ=WEEKLY;BYDAY=WE;COUNT=52`               |\n\nClients MUST support at minimum `FREQ`, `BYDAY`, `INTERVAL`, `COUNT`, and `UNTIL`. Clients SHOULD support `BYHOUR` and `WKST`. Unsupported RRULE properties MUST be silently ignored; clients SHOULD NOT reject events containing unsupported properties.\n\nLibraries such as `rrule.js` (JavaScript) or `dateutil.rrule` (Python) provide RFC 5545 RRULE parsing.\n\n### RRULE Resolution\n\nClients resolve recurring calendars into concrete bookable slots by:\n\n1. Parsing the `recurrence` tag as an RFC 5545 RRULE\n2. Generating occurrence dates within the requested time window\n3. Applying `exclude_date` exclusions\n4. Combining each occurrence date with the `recurrence_start` time(s) and `slot_duration_minutes` to produce concrete slots\n5. Merging with any explicit `slot` tags (explicit slots take precedence over generated ones for the same time window)\n6. Checking each slot against existing Kind 30583 bookings to determine remaining capacity\n7. Presenting available slots to the requester\n\n### Tag Reference\n\n| Tag                         | Required          | Multiple | Description                                       |\n|-----------------------------|-------------------|----------|---------------------------------------------------|\n| `d`                         | MUST              | No       | Addressable event identifier                      |\n| `t`                         | MUST              | No       | Protocol family marker                            |\n| `slot`                      | SHOULD            | Yes      | Explicit time slot with status                    |\n| `g`                         | SHOULD            | No       | Service area geohash                              |\n| `slot_duration_minutes`     | SHOULD*           | No       | Default slot duration (* MUST when recurring)     |\n| `max_bookings_per_slot`     | MAY               | No       | Max concurrent bookings (default 1)               |\n| `expiration`                | MAY               | No       | Calendar validity period (NIP-40)                 |\n| `recurrence`                | MAY               | No       | RRULE string (RFC 5545)                           |\n| `recurrence_start`          | MAY*              | Yes      | Start time per recurring slot (* MUST when recurring) |\n| `timezone`                  | MAY               | No       | Provider's timezone (IANA format)                 |\n| `exclude_date`              | MAY               | Yes      | Excluded dates (ISO 8601)                         |\n| `pricing`                   | MAY               | No       | Price per slot (smallest currency unit)           |\n| `currency`                  | MAY               | No       | Currency code                                     |\n| `cancellation_window_hours` | MAY               | No       | Penalty-free cancellation window                  |\n| `cancellation_fee`          | MAY               | No       | Fee for late cancellation                         |\n| `cancellation_fee_currency` | MAY               | No       | Currency of the cancellation fee                  |\n| `p`                         | MAY               | Yes      | Additional notification targets                   |\n| `ref`                       | MAY               | No       | External reference                                |\n\n---\n\n## Booking Slot (`kind:30583`)\n\nPublished by a requester to book a specific slot from a provider's calendar.\n\n```json\n{\n    \"kind\": 30583,\n    \"pubkey\": \"<requester-hex-pubkey>\",\n    \"created_at\": 1698775000,\n    \"tags\": [\n        [\"d\", \"<provider-hex-pubkey>:calendar:2026-W09:slot:1698822000:booking:<requester-hex-pubkey>\"],\n        [\"t\", \"booking-slot\"],\n        [\"e\", \"<calendar-event-id>\", \"wss://relay.example.com\"],\n        [\"p\", \"<provider-hex-pubkey>\"],\n        [\"slot_start\", \"1698822000\"],\n        [\"slot_end\", \"1698832800\"],\n        [\"amount\", \"5000\"],\n        [\"currency\", \"SAT\"],\n        [\"alt\", \"Booking slot for 5000 SAT\"]\n    ],\n    \"content\": \"<NIP-44 encrypted JSON: {\\\"address\\\":\\\"42 Oak Lane, London SE1 2AB\\\",\\\"notes\\\":\\\"Please arrive 10 minutes early.\\\"}>\",\n    \"id\": \"<32-bytes lowercase hex>\",\n    \"sig\": \"<64-bytes lowercase hex>\"\n}\n```\n\nTags:\n\n* `d` (REQUIRED): Addressable event identifier. RECOMMENDED format: `<calendar_d_tag>:slot:<slot_start>:booking:<requester_pubkey>`. The `slot_start` timestamp participates in the identifier to ensure one booking per requester per slot; without it, a second booking from the same requester on the same calendar would replace the first. Applications MAY use any d-tag format that ensures uniqueness.\n* `t` (REQUIRED): Protocol family marker. MUST be `\"booking-slot\"`.\n* `e` (REQUIRED): Event ID of the Kind 30582 availability calendar.\n* `p` (REQUIRED): Provider's hex pubkey.\n* `slot_start` (REQUIRED): Unix timestamp for the start time of the booked slot.\n* `slot_end` (REQUIRED): Unix timestamp for the end time of the booked slot.\n* `amount` (RECOMMENDED): Agreed price in smallest currency unit (pence for GBP, cents for USD, satoshis for SAT).\n* `currency` (RECOMMENDED): Currency code.\n* `ref` (OPTIONAL): External reference (booking confirmation number).\n* `notes` (OPTIONAL): Booking notes or special requests.\n\n**Content:** Empty string or NIP-44 encrypted JSON with booking details (address, special requirements, access instructions).\n\n### Tag Reference\n\n| Tag          | Required | Multiple | Description                                |\n|--------------|----------|----------|--------------------------------------------|\n| `d`          | MUST     | No       | Addressable event identifier               |\n| `t`          | MUST     | No       | Protocol family marker                     |\n| `e`          | MUST     | No       | Reference to Kind 30582 calendar event     |\n| `p`          | MUST     | No       | Provider's pubkey                          |\n| `slot_start` | MUST     | No       | Booked slot start time                     |\n| `slot_end`   | MUST     | No       | Booked slot end time                       |\n| `amount`     | SHOULD   | No       | Price (smallest currency unit)             |\n| `currency`   | SHOULD   | No       | Currency code                              |\n| `ref`        | MAY      | No       | External reference                         |\n| `notes`      | MAY      | No       | Booking notes                              |\n\n---\n\n## Booking Cancellation (`kind:30584`)\n\nPublished by either party to cancel a booking. The `d` tag format creates one cancellation per booking. A dedicated cancellation kind is used instead of NIP-09 deletion because: (a) either party may cancel, but only the booking author can issue a NIP-09 deletion for their own event; (b) cancellations carry structured metadata (reason codes, refund amounts, penalty amounts) that deletion requests cannot express; (c) cancellations create an auditable record that persists independently of relay deletion compliance.\n\n```json\n{\n    \"kind\": 30584,\n    \"pubkey\": \"<requester-hex-pubkey>\",\n    \"created_at\": 1698776000,\n    \"tags\": [\n        [\"d\", \"<provider-hex-pubkey>:calendar:2026-W09:slot:1698822000:booking:<requester-hex-pubkey>:cancellation\"],\n        [\"t\", \"booking-cancellation\"],\n        [\"e\", \"<booking-event-id>\", \"wss://relay.example.com\"],\n        [\"cancel_reason\", \"requester_initiated\"],\n        [\"p\", \"<provider-hex-pubkey>\"],\n        [\"refund_amount\", \"5000\"],\n        [\"refund_currency\", \"SAT\"],\n        [\"alt\", \"Booking cancellation: requester initiated, 5000 SAT refund\"]\n    ],\n    \"content\": \"Need to reschedule due to an unexpected commitment. Apologies for the short notice.\",\n    \"id\": \"<32-bytes lowercase hex>\",\n    \"sig\": \"<64-bytes lowercase hex>\"\n}\n```\n\nTags:\n\n* `d` (REQUIRED): Addressable event identifier. RECOMMENDED format: `<booking_d_tag>:cancellation`. One cancellation per booking. Applications MAY use any d-tag format that ensures uniqueness.\n* `t` (REQUIRED): Protocol family marker. MUST be `\"booking-cancellation\"`.\n* `e` (REQUIRED): Event ID of the Kind 30583 booking being cancelled.\n* `cancel_reason` (REQUIRED): Reason code. One of `\"requester_initiated\"`, `\"provider_initiated\"`, `\"schedule_conflict\"`, `\"no_show\"`, `\"force_majeure\"`.\n* `p` (RECOMMENDED): Other party's hex pubkey (for notification).\n* `refund_amount` (OPTIONAL): Refund amount in smallest currency unit.\n* `refund_currency` (OPTIONAL): Currency of the refund.\n* `penalty_amount` (OPTIONAL): Cancellation penalty in smallest currency unit.\n* `penalty_currency` (OPTIONAL): Currency of the penalty.\n\n**Content:** Plain text with cancellation details or reason.\n\n### Tag Reference\n\n| Tag               | Required | Multiple | Description                                |\n|-------------------|----------|----------|--------------------------------------------|\n| `d`               | MUST     | No       | Addressable event identifier               |\n| `t`               | MUST     | No       | Protocol family marker                     |\n| `e`               | MUST     | No       | Reference to Kind 30583 booking event      |\n| `cancel_reason`   | MUST     | No       | Reason code for cancellation               |\n| `p`               | SHOULD   | No       | Other party's pubkey for notification      |\n| `refund_amount`   | MAY      | No       | Refund amount (smallest currency unit)     |\n| `refund_currency` | MAY      | No       | Currency of the refund                     |\n| `penalty_amount`  | MAY      | No       | Penalty amount (smallest currency unit)    |\n| `penalty_currency`| MAY      | No       | Currency of the penalty                    |\n\n---\n\n## Composing with NIP-APPROVAL (OPTIONAL)\n\nThis section describes OPTIONAL composition with NIP-APPROVAL. NIP-BOOKING works independently without NIP-APPROVAL.\n\nBooking confirmation is handled by [NIP-APPROVAL](./NIP-APPROVAL.md) (kinds 30570-30571). In workflows where bookings require provider approval, a Kind 30583 booking is treated as a _request_ rather than a confirmed reservation. The provider creates an Approval Gate referencing the booking, then publishes an Approval Response with their decision.\n\nIn workflows where bookings are auto-confirmed, this composition step is not needed; the Kind 30583 booking itself constitutes confirmation.\n\n### Confirmation Flow\n\n**Step 1: Requester books a slot** (Kind 30583, as above).\n\n**Step 2: Provider creates an Approval Gate** (Kind 30570) referencing the booking:\n\n```json\n{\n    \"kind\": 30570,\n    \"pubkey\": \"<provider-hex-pubkey>\",\n    \"created_at\": 1698781000,\n    \"tags\": [\n        [\"d\", \"<provider-hex-pubkey>:calendar:2026-W09:slot:1698822000:booking:<requester-hex-pubkey>:gate:confirmation\"],\n        [\"t\", \"approval-gate\"],\n        [\"gate_type\", \"approval\"],\n        [\"gate_authority\", \"<provider-hex-pubkey>\"],\n        [\"gate_status\", \"pending\"],\n        [\"e\", \"<booking-event-id>\", \"wss://relay.example.com\"],\n        [\"p\", \"<requester-hex-pubkey>\"],\n        [\"expiration\", \"1698868400\"],\n        [\"alt\", \"Approval gate for booking confirmation\"]\n    ],\n    \"content\": \"Booking confirmation required for Monday 09:00-12:00 session.\",\n    \"id\": \"<32-bytes lowercase hex>\",\n    \"sig\": \"<64-bytes lowercase hex>\"\n}\n```\n\n**Step 3a: Provider approves** (Kind 30571):\n\n```json\n{\n    \"kind\": 30571,\n    \"pubkey\": \"<provider-hex-pubkey>\",\n    \"created_at\": 1698782000,\n    \"tags\": [\n        [\"d\", \"<provider-hex-pubkey>:calendar:2026-W09:slot:1698822000:booking:<requester-hex-pubkey>:gate:confirmation:response:<provider-hex-pubkey>\"],\n        [\"t\", \"approval-response\"],\n        [\"e\", \"<gate-event-id>\", \"wss://relay.example.com\"],\n        [\"decision\", \"approved\"],\n        [\"p\", \"<requester-hex-pubkey>\"],\n        [\"slot_start\", \"1698822000\"],\n        [\"slot_end\", \"1698832800\"],\n        [\"amount\", \"7500\"],\n        [\"currency\", \"GBP\"],\n        [\"alt\", \"Booking approved: 7500 GBP\"]\n    ],\n    \"content\": \"<NIP-44 encrypted JSON: {\\\"location\\\":\\\"Suite 4B, 12 Harley Street, London W1G 9PF\\\",\\\"preparation\\\":\\\"Please arrive 10 minutes early for your first session.\\\"}>\",\n    \"id\": \"<32-bytes lowercase hex>\",\n    \"sig\": \"<64-bytes lowercase hex>\"\n}\n```\n\nThe `slot_start`, `slot_end`, `amount`, and `currency` tags on the Approval Response confirm the booking details. The NIP-44 encrypted content carries private location or preparation information.\n\n**Step 3b: Provider declines** (Kind 30571):\n\n```json\n{\n    \"kind\": 30571,\n    \"pubkey\": \"<provider-hex-pubkey>\",\n    \"created_at\": 1698782000,\n    \"tags\": [\n        [\"d\", \"<provider-hex-pubkey>:calendar:2026-W09:slot:1698822000:booking:<requester-hex-pubkey>:gate:confirmation:response:<provider-hex-pubkey>\"],\n        [\"t\", \"approval-response\"],\n        [\"e\", \"<gate-event-id>\", \"wss://relay.example.com\"],\n        [\"decision\", \"rejected\"],\n        [\"p\", \"<requester-hex-pubkey>\"],\n        [\"decline_reason\", \"schedule_conflict\"],\n        [\"alt\", \"Booking rejected: schedule conflict\"]\n    ],\n    \"content\": \"Unfortunately I have a scheduling conflict for this slot. Please check my availability for Wednesday afternoon instead.\",\n    \"id\": \"<32-bytes lowercase hex>\",\n    \"sig\": \"<64-bytes lowercase hex>\"\n}\n```\n\n### REQ Filter for Booking Confirmations\n\nTo fetch all approval responses for a specific booking:\n\n```json\n{\n    \"kinds\": [30571],\n    \"authors\": [\"<provider-hex-pubkey>\"],\n    \"#e\": [\"<gate-event-id>\"]\n}\n```\n\n### Validation Rules\n\n| Rule     | Description                                                                                              |\n|----------|----------------------------------------------------------------------------------------------------------|\n| V-BK-05  | The Approval Gate (30570) MUST reference a valid Kind 30583 Booking Slot via `e` tag                     |\n| V-BK-06  | The Approval Response (30571) `decision` tag MUST be `approved`, `rejected`, or `revise`     |\n| V-BK-07  | The Approval Response MUST be published by the provider referenced in the Kind 30583 booking's `p` tag   |\n\n---\n\n## Composing with NIP-VARIATION (OPTIONAL)\n\nThis section describes OPTIONAL composition with NIP-VARIATION. NIP-BOOKING works independently without NIP-VARIATION. Applications that do not need rescheduling support can use cancellation-and-rebook instead.\n\nRescheduling is handled by [NIP-VARIATION](./NIP-VARIATION.md) (kind 30579). A reschedule is a scope change: the proposer creates a Variation Request with `variation_type: schedule_change` and new `slot_start`/`slot_end` tags. The other party responds via NIP-APPROVAL.\n\nRescheduling preserves the booking history and relationship between the original request and the new time, unlike cancellation-and-rebook which creates two independent events.\n\n### Reschedule Flow\n\n**Step 1: Requester requests a reschedule** (Kind 30579):\n\n```json\n{\n    \"kind\": 30579,\n    \"pubkey\": \"<requester-hex-pubkey>\",\n    \"created_at\": 1698782000,\n    \"tags\": [\n        [\"d\", \"<provider-hex-pubkey>:calendar:2026-W10:slot:1699254000:booking:<requester-hex-pubkey>:variation:001\"],\n        [\"t\", \"variation-request\"],\n        [\"variation_type\", \"schedule_change\"],\n        [\"e\", \"<booking-event-id>\", \"wss://relay.example.com\"],\n        [\"p\", \"<provider-hex-pubkey>\"],\n        [\"slot_start\", \"1699340400\"],\n        [\"slot_end\", \"1699344000\"],\n        [\"original_slot_start\", \"1699254000\"],\n        [\"original_slot_end\", \"1699257600\"],\n        [\"reschedule_reason\", \"requester_conflict\"],\n        [\"cancellation_window_hours\", \"48\"],\n        [\"penalty_waived\", \"true\"],\n        [\"alt\", \"Reschedule request: requester conflict\"]\n    ],\n    \"content\": \"I have a work commitment that has come up on Monday. Could we move to Tuesday afternoon instead?\",\n    \"id\": \"<32-bytes lowercase hex>\",\n    \"sig\": \"<64-bytes lowercase hex>\"\n}\n```\n\nThe `variation_type: schedule_change` signals that this variation is a reschedule. The `slot_start` and `slot_end` tags carry the proposed new times, while `original_slot_start` and `original_slot_end` record the original booking times for reference.\n\n**Step 2: Provider approves the reschedule** via NIP-APPROVAL (Kind 30571):\n\n```json\n{\n    \"kind\": 30571,\n    \"pubkey\": \"<provider-hex-pubkey>\",\n    \"created_at\": 1698783000,\n    \"tags\": [\n        [\"d\", \"<provider-hex-pubkey>:calendar:2026-W10:slot:1699254000:booking:<requester-hex-pubkey>:variation:001:response:<provider-hex-pubkey>\"],\n        [\"t\", \"approval-response\"],\n        [\"e\", \"<variation-event-id>\", \"wss://relay.example.com\"],\n        [\"decision\", \"approved\"],\n        [\"p\", \"<requester-hex-pubkey>\"],\n        [\"slot_start\", \"1699340400\"],\n        [\"slot_end\", \"1699344000\"],\n        [\"alt\", \"Reschedule approved\"]\n    ],\n    \"content\": \"Tuesday afternoon works well. See you then.\",\n    \"id\": \"<32-bytes lowercase hex>\",\n    \"sig\": \"<64-bytes lowercase hex>\"\n}\n```\n\nThis preserves the full event chain: booking (30583) -> variation request (30579) -> approval response (30571).\n\n**Provider-initiated reschedule** follows the same pattern, with the provider publishing the Variation Request and the requester responding:\n\n```json\n{\n    \"kind\": 30579,\n    \"pubkey\": \"<provider-hex-pubkey>\",\n    \"created_at\": 1698782000,\n    \"tags\": [\n        [\"d\", \"<provider-hex-pubkey>:calendar:2026-W10:slot:1699254000:booking:<requester-hex-pubkey>:variation:002\"],\n        [\"t\", \"variation-request\"],\n        [\"variation_type\", \"schedule_change\"],\n        [\"e\", \"<booking-event-id>\", \"wss://relay.example.com\"],\n        [\"p\", \"<requester-hex-pubkey>\"],\n        [\"slot_start\", \"1699340400\"],\n        [\"slot_end\", \"1699344000\"],\n        [\"original_slot_start\", \"1699254000\"],\n        [\"original_slot_end\", \"1699257600\"],\n        [\"reschedule_reason\", \"illness\"],\n        [\"penalty_waived\", \"true\"],\n        [\"expiration\", \"1698868400\"],\n        [\"alt\", \"Reschedule request: provider illness\"]\n    ],\n    \"content\": \"I'm unwell today and need to reschedule our session. Tuesday same time would work if that suits you.\",\n    \"id\": \"<32-bytes lowercase hex>\",\n    \"sig\": \"<64-bytes lowercase hex>\"\n}\n```\n\n### Cancellation Policy on Reschedules\n\nCancellation policies are declared on the Availability Calendar (Kind 30582) via `cancellation_window_hours` and `cancellation_fee` tags. The Variation Request echoes the applicable policy for transparency.\n\nWhen a reschedule is requested:\n\n1. The requesting party checks the applicable cancellation window\n2. If the reschedule is requested within the cancellation window (i.e. less than `cancellation_window_hours` before the original slot), the `cancellation_fee` MAY apply\n3. The `penalty_waived` tag allows the requesting party to signal that they accept the penalty, or the other party to waive it when responding\n\n**Provider-initiated reschedules** SHOULD NOT incur penalties on the requester.\n\n### Variation Request Tags for Rescheduling\n\nWhen composing NIP-VARIATION for rescheduling, the following tags are used on the Kind 30579 Variation Request:\n\n| Tag                         | Required | Description                                         |\n|-----------------------------|----------|-----------------------------------------------------|\n| `variation_type`            | MUST     | MUST be `\"schedule_change\"` for reschedules         |\n| `e`                         | MUST     | Reference to Kind 30583 booking event               |\n| `slot_start`                | MUST     | Proposed new start time (Unix timestamp)            |\n| `slot_end`                  | MUST     | Proposed new end time (Unix timestamp)              |\n| `p`                         | SHOULD   | Other party's pubkey for notification               |\n| `original_slot_start`       | SHOULD   | Original booking start time                         |\n| `original_slot_end`         | SHOULD   | Original booking end time                           |\n| `reschedule_reason`         | SHOULD   | Reason code: `requester_conflict`, `provider_conflict`, `weather`, `illness`, `other` |\n| `cancellation_window_hours` | MAY      | Echoed from calendar policy                         |\n| `cancellation_fee`          | MAY      | Applicable fee if outside window                    |\n| `cancellation_fee_currency` | MAY      | Currency of the cancellation fee                    |\n| `penalty_waived`            | MAY      | Whether the penalty is waived (`\"true\"`)            |\n| `amount`                    | MAY      | Price for rescheduled slot (if different)           |\n| `currency`                  | MAY      | Currency code                                       |\n| `expiration`                | MAY      | Response deadline (NIP-40)                          |\n\n---\n\n## Protocol Flow\n\n\n![Booking Lifecycle](https://raw.githubusercontent.com/forgesworn/nip-drafts/main/images/booking-1.png)\n\n```mermaid\nsequenceDiagram\n    participant P as Provider\n    participant R as Relay\n    participant Q as Requester\n\n    P->>R: kind:30582 Availability Calendar\n    Note over P,R: slots: available\n\n    Q->>R: REQ filter (kind:30582)\n    R-->>Q: Calendar with available slots\n\n    Q->>R: kind:30583 Booking Slot\n    Note over Q,R: slot: Mon 09:00-12:00\n\n    R-->>P: Notification\n\n    alt Approval required\n        P->>R: kind:30570 Approval Gate\n        P->>R: kind:30571 Approval Response (approved)\n        R-->>Q: Booking confirmed\n    end\n\n    P->>R: kind:30582 Calendar (updated)\n    Note over P,R: Mon slot: booked\n\n    alt Reschedule\n        Q->>R: kind:30579 Variation Request (schedule_change)\n        R-->>P: Reschedule proposal\n        P->>R: kind:30571 Approval Response (approved)\n        R-->>Q: Reschedule confirmed\n    end\n\n    alt Cancellation\n        Q->>R: kind:30584 Booking Cancellation\n        R-->>P: Notification\n        P->>R: kind:30582 Calendar (updated)\n        Note over P,R: slot: available again\n    end\n```\n\n### Step-by-Step\n\n1. **Publish calendar:** Provider publishes `kind:30582` with available time slots. If the calendar includes recurrence tags, clients expand the pattern into concrete slots.\n2. **Discover availability:** Requester queries for `kind:30582` calendars filtered by geohash, provider pubkey, or time range.\n3. **Book slot:** Requester publishes `kind:30583` to book a specific slot.\n4. **Confirm booking (optional):** Provider creates an Approval Gate (`kind:30570`) and publishes an Approval Response (`kind:30571`) to confirm or decline. If the workflow does not require confirmation, the booking is auto-confirmed.\n5. **Update calendar:** Provider republishes `kind:30582` with the booked slot's status changed to `\"booked\"`.\n6. **Service delivery:** The consuming application handles what happens during the booked slot (task creation, payment, etc.).\n7. **Reschedule (optional):** Either party publishes a Variation Request (`kind:30579`) with `variation_type: schedule_change`. The other party responds via Approval Response (`kind:30571`).\n8. **Cancellation (optional):** Either party publishes `kind:30584` to cancel. The provider restores the slot to `\"available\"` in their next calendar update.\n9. **Next period:** Provider publishes a new or updated `kind:30582` for the next scheduling period.\n\n## REQ Filters\n\n### Discover Provider Availability\n\n```json\n{\n    \"kinds\": [30582],\n    \"#g\": [\"gcpuuz\"],\n    \"#t\": [\"availability-calendar\"]\n}\n```\n\n### Fetch Bookings for a Calendar\n\n```json\n{\n    \"kinds\": [30583],\n    \"#e\": [\"<calendar-event-id>\"]\n}\n```\n\n### Fetch Cancellations for a Booking\n\n```json\n{\n    \"kinds\": [30584],\n    \"#e\": [\"<booking-event-id>\"]\n}\n```\n\n### Fetch Reschedule Requests for a Booking\n\n```json\n{\n    \"kinds\": [30579],\n    \"#e\": [\"<booking-event-id>\"],\n    \"#t\": [\"variation-request\"]\n}\n```\n\n---\n\n## Validation Rules\n\n| Rule     | Event Kind(s)                | Requirement                                                                                     |\n|----------|------------------------------|-------------------------------------------------------------------------------------------------|\n| V-BK-01  | 30582 (Availability Calendar)| When `recurrence` is present, MUST include a valid RRULE string conforming to RFC 5545          |\n| V-BK-02  | 30582 (Availability Calendar)| When `recurrence` is present, MUST include at least one `recurrence_start` tag in `HH:MM` format and a `slot_duration_minutes` tag |\n| V-BK-03  | 30582 (Availability Calendar)| `max_bookings_per_slot` value MUST be a positive integer when present (minimum `1`)             |\n| V-BK-04  | 30582 (Availability Calendar)| `exclude_date` values MUST be valid ISO 8601 dates                                              |\n| V-BK-05  | 30570/30571 (Approval)       | Approval Gate MUST reference a valid Kind 30583 Booking Slot via `e` tag                        |\n| V-BK-06  | 30571 (Approval Response)    | `decision` tag MUST be `approved`, `rejected`, or `revise`                          |\n| V-BK-07  | 30571 (Approval Response)    | MUST be published by the provider referenced in the Kind 30583 booking's `p` tag                |\n| V-BK-08  | 30579 (Variation Request)    | When used for rescheduling, MUST reference a valid Kind 30583 Booking Slot via `e` tag          |\n| V-BK-09  | 30579 (Variation Request)    | `slot_start` MUST be a valid future Unix timestamp                                              |\n| V-BK-10  | 30579 (Variation Request)    | `slot_end` MUST be greater than `slot_start`                                                    |\n| V-BK-11  | 30579 (Variation Request)    | MUST be published by either the requester or provider of the referenced booking                 |\n| V-BK-12  | 30582 (Availability Calendar)| `pricing` and `cancellation_fee` values MUST be non-negative integers when present              |\n\n## Relay Recommendations\n\n| Event Kind                    | Recommended Retention                   | Rationale                   |\n|-------------------------------|-----------------------------------------|-----------------------------|\n| 30582 (Availability Calendar) | 90 days post-expiration or post-update  | Scheduling infrastructure   |\n| 30583 (Booking Slot)          | 90 days post-slot-end                   | Booking record              |\n| 30584 (Booking Cancellation)  | 90 days post-slot-end                   | Cancellation audit trail    |\n\n## Security Considerations\n\n* **Double-booking prevention.** Relays do not enforce slot exclusivity. The provider is responsible for updating their calendar (`kind:30582`) after each booking. Applications SHOULD check slot status before confirming bookings and SHOULD handle race conditions gracefully (e.g. by notifying the requester if a slot was booked by someone else).\n* **Cancellation policies.** The `penalty_amount` and `refund_amount` tags communicate cancellation terms but do not enforce payment. Applications SHOULD integrate with payment protocols for automated penalty collection.\n* **Content encryption.** Booking details (addresses, personal information, session notes) SHOULD be NIP-44 encrypted in the `content` field. Public `slot` tags on calendars reveal when a provider is busy but not who booked or why.\n* **Calendar authenticity.** Clients MUST verify that `kind:30582` calendar events are signed by the provider's pubkey. Forged calendars could lead to bookings with non-existent providers.\n* **Slot time validation.** Clients SHOULD verify that `slot_start` and `slot_end` on booking events (`kind:30583`) match an `available` slot on the referenced calendar (`kind:30582`). Bookings referencing non-existent or `blocked` slots SHOULD be rejected.\n* **Timestamp manipulation.** Providers could manipulate slot timestamps to create artificial scarcity or conflict. Clients SHOULD cross-reference `created_at` timestamps with slot times and flag anomalies.\n* **Booking spam.** Providers SHOULD implement rate limiting on incoming Kind 30583 bookings. A requester who repeatedly books and cancels slots MAY be flagged or blocked at the application level.\n* **Cancellation policy manipulation.** The cancellation policy tags on Kind 30582 are informational; they declare the policy but do not enforce it cryptographically. Enforcement depends on the consuming application and payment integration.\n* **Slot overbooking.** When `max_bookings_per_slot` is greater than 1, multiple requesters may book the same slot. Providers MUST track the number of active bookings per slot and decline (via NIP-APPROVAL) or update their calendar when capacity is reached.\n* **Time zone handling.** Calendars with recurrence tags include a `timezone` tag to avoid ambiguity in recurring patterns. Clients MUST resolve RRULE patterns in the provider's declared timezone. Daylight saving time transitions MUST be handled correctly; a recurring slot at \"10:00 Europe/London\" remains at 10:00 local time regardless of GMT/BST transitions.\n* **Confirmation authenticity.** When NIP-APPROVAL is used for booking confirmation, clients MUST verify that Approval Response (30571) events are signed by the provider referenced in the original Kind 30583 booking's `p` tag. Forged confirmations could mislead requesters.\n\n## Privacy\n\n* **Availability exposure.** Publishing a Kind 30582 calendar reveals when a provider is free and when they are busy. Providers who require privacy SHOULD limit the time horizon of published calendars and use NIP-44 encryption for the `content` field.\n* **Booking metadata.** Kind 30583 `slot_start`/`slot_end` tags are public, revealing that a booking exists at a specific time. Sensitive details (addresses, personal notes) MUST be placed in NIP-44 encrypted `content`, not in public tags.\n* **Geohash precision.** The `g` tag exposes approximate location. Providers SHOULD use a geohash precision appropriate for their privacy needs (e.g. 5-6 characters for neighbourhood-level, not 8+ characters for building-level).\n* **Cancellation visibility.** Kind 30584 cancellation events are public. The `cancel_reason` tag reveals why a booking was cancelled. Applications handling sensitive contexts (e.g. medical appointments) SHOULD use generic reason codes.\n\n## Test Vectors\n\n### Availability Calendar (Explicit Slots)\n\n```json\n{\n    \"kind\": 30582,\n    \"pubkey\": \"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2\",\n    \"created_at\": 1698774000,\n    \"tags\": [\n        [\"d\", \"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2:calendar:2026-W09\"],\n        [\"t\", \"availability-calendar\"],\n        [\"g\", \"gcpuuz\"],\n        [\"slot\", \"1698822000\", \"1698832800\", \"available\"],\n        [\"slot\", \"1698832800\", \"1698843600\", \"available\"],\n        [\"slot_duration_minutes\", \"180\"],\n        [\"max_bookings_per_slot\", \"1\"],\n        [\"alt\", \"Availability calendar with 2 slots\"]\n    ],\n    \"content\": \"\",\n    \"id\": \"<32-bytes lowercase hex>\",\n    \"sig\": \"<64-bytes lowercase hex>\"\n}\n```\n\n### Availability Calendar (Recurring)\n\n```json\n{\n    \"kind\": 30582,\n    \"pubkey\": \"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2\",\n    \"created_at\": 1698780000,\n    \"tags\": [\n        [\"d\", \"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2:calendar:appointments\"],\n        [\"t\", \"availability-calendar\"],\n        [\"recurrence\", \"FREQ=WEEKLY;BYDAY=MO,TH\"],\n        [\"recurrence_start\", \"10:00\"],\n        [\"slot_duration_minutes\", \"60\"],\n        [\"timezone\", \"Europe/London\"],\n        [\"g\", \"gcpuuz\"],\n        [\"max_bookings_per_slot\", \"1\"],\n        [\"pricing\", \"7500\"],\n        [\"currency\", \"GBP\"],\n        [\"expiration\", \"1735689600\"],\n        [\"exclude_date\", \"2026-03-17\"],\n        [\"alt\", \"Recurring availability: Mon/Thu, 60 min slots, 7500 GBP\"]\n    ],\n    \"content\": \"\",\n    \"id\": \"<32-bytes lowercase hex>\",\n    \"sig\": \"<64-bytes lowercase hex>\"\n}\n```\n\n### Booking Slot\n\n```json\n{\n    \"kind\": 30583,\n    \"pubkey\": \"f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5\",\n    \"created_at\": 1698775000,\n    \"tags\": [\n        [\"d\", \"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2:calendar:2026-W09:slot:1698822000:booking:f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5\"],\n        [\"t\", \"booking-slot\"],\n        [\"e\", \"abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789\", \"wss://relay.example.com\"],\n        [\"p\", \"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2\"],\n        [\"slot_start\", \"1698822000\"],\n        [\"slot_end\", \"1698832800\"],\n        [\"amount\", \"7500\"],\n        [\"currency\", \"GBP\"],\n        [\"alt\", \"Booking slot for 7500 GBP\"]\n    ],\n    \"content\": \"\",\n    \"id\": \"<32-bytes lowercase hex>\",\n    \"sig\": \"<64-bytes lowercase hex>\"\n}\n```\n\n### Booking Cancellation\n\n```json\n{\n    \"kind\": 30584,\n    \"pubkey\": \"f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5\",\n    \"created_at\": 1698776000,\n    \"tags\": [\n        [\"d\", \"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2:calendar:2026-W09:slot:1698822000:booking:f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5:cancellation\"],\n        [\"t\", \"booking-cancellation\"],\n        [\"e\", \"1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef\", \"wss://relay.example.com\"],\n        [\"cancel_reason\", \"requester_initiated\"],\n        [\"p\", \"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2\"],\n        [\"refund_amount\", \"7500\"],\n        [\"refund_currency\", \"GBP\"],\n        [\"alt\", \"Booking cancellation: requester initiated, 7500 GBP refund\"]\n    ],\n    \"content\": \"Unable to attend due to a scheduling conflict.\",\n    \"id\": \"<32-bytes lowercase hex>\",\n    \"sig\": \"<64-bytes lowercase hex>\"\n}\n```\n\n---\n\n## Use Cases\n\n### Professional Appointment Booking\n\nTherapists, consultants, tutors, and other professionals can publish their availability calendar on Nostr. Clients browse available slots and book sessions directly. The NIP-44 encrypted content on booking events protects client details (address, session notes). Cancellation events with penalty tags enable automated cancellation policy enforcement.\n\n### Venue & Resource Reservation\n\nCo-working spaces, meeting rooms, sports facilities, and shared equipment can use availability calendars for reservation management. The `max_bookings_per_slot` tag supports both exclusive-use resources (set to 1) and shared resources (set to capacity). The `g` tag enables location-based discovery.\n\n### Recurring Event Registration\n\nCommunity organisers can publish recurring event slots (weekly meetups, monthly workshops, regular classes) using recurrence tags on Kind 30582. Attendees book specific sessions via `kind:30583`. The recurrence tags communicate the schedule pattern, while individual bookings track attendance per session.\n\n### Service Provider Scheduling\n\nAny service provider who works by appointment (plumbers, electricians, dog walkers, personal trainers) can publish their availability and accept bookings through Nostr. The calendar model handles the common pattern of time-slotted availability with location-based discovery.\n\n---\n\n## Dependencies\n\n* [NIP-01](https://github.com/nostr-protocol/nips/blob/master/01.md): Basic protocol flow, addressable events\n* [NIP-40](https://github.com/nostr-protocol/nips/blob/master/40.md): Expiration timestamps (calendar validity)\n* [NIP-44](https://github.com/nostr-protocol/nips/blob/master/44.md): Versioned encrypted payloads (private booking details)\n* [NIP-52](https://github.com/nostr-protocol/nips/blob/master/52.md): Calendar Events (complementary; NIP-52 defines events, NIP-BOOKING defines availability and reservations)\n* [NIP-APPROVAL](./NIP-APPROVAL.md): Multi-party approval gates (booking confirmation)\n* [NIP-VARIATION](./NIP-VARIATION.md): Scope and price change management (rescheduling)\n* [RFC 5545](https://datatracker.ietf.org/doc/html/rfc5545): Internet Calendaring and Scheduling (iCalendar) for RRULE format\n\n## Reference Implementation\n\nNo public reference implementation exists yet. Implementors SHOULD refer to the kind definitions above.\n\nA minimal implementation requires:\n\n1. A Nostr client that supports addressable event publishing.\n2. Calendar rendering logic: parsing `slot` tags and `recurrence`/RRULE patterns to display provider availability.\n3. Booking state management: tracking slot statuses and preventing double-bookings at the application level.\n4. Cancellation handling with refund/penalty computation per the provider's published policies.\n\nFor full-featured implementations, additionally:\n\n5. Confirmation handling via NIP-APPROVAL: creating Approval Gates for incoming bookings and processing Approval Responses.\n6. Reschedule handling via NIP-VARIATION: publishing Variation Requests with `variation_type: schedule_change` and processing approval responses with updated slot times.","sig":"4847bb669e69b0ab6f4a49db0a34e5e8ba1a0a40256d3b76e80c4ab119a89ec86238d34fba399eee447bc42fe2d8f90838ed3d5696933243b98e03b392328b2c"}