{"openapi":"3.1.0","info":{"title":"Riverd API","version":"1.0.0","description":"Read your Riverd practice data, and make changes to it. A change applies at once and can be undone through the API for a while after; emails and texts it sends your clients wait until then, or until the session starts if sooner (client_messages_release_at says when). Authenticate with a personal API key in the Authorization header: 'Bearer rvd_pat_...'. A key in the web address is refused and turned off. Limits, per key and per practice: 60 requests a minute and 1000 an hour; 20 changes a minute and 300 a day (an Undo counts as a change, a dry_run as a request); 60 changes that can message a client an hour per practice (POST /bookings, /bookings/past, move, cancel, intake; /bookings/past counts although it sends nothing); 10 changes an hour and 30 a day per practice that confirm a client's email or phone (contact_confirmed true, or POST /clients/{clientId}/confirm-contact); 120 pages of session notes or intake answers an hour per practice; 30 AI drafts an hour per practice."},"servers":[{"url":"https://api.riverd.app/v1"}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"A personal API key from Settings, API keys."}},"schemas":{"Moment":{"type":"object","properties":{"date_time":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$","description":"RFC 3339 to the second, with the offset in effect in `timezone` at that moment, so it reads as that zone's clock: \"2026-10-09T14:00:00-05:00\". \"Z\" only when the zone is UTC."},"timezone":{"type":"string","minLength":1,"maxLength":64,"description":"An IANA time zone name, such as \"America/Chicago\". Never an abbreviation (\"EST\", \"IST\") or an offset (\"+02:00\")."}},"required":["date_time","timezone"],"additionalProperties":false,"description":"An exact moment, on the clock of `timezone` (the practice's unless the request named another)."},"Date":{"type":"object","properties":{"date":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$","description":"A calendar date, YYYY-MM-DD: \"2026-10-09\"."},"timezone":{"type":"string","minLength":1,"maxLength":64,"description":"An IANA time zone name, such as \"America/Chicago\". Never an abbreviation (\"EST\", \"IST\") or an offset (\"+02:00\")."}},"required":["date","timezone"],"additionalProperties":false,"description":"A calendar date in the practice's time zone."},"TimeOfDay":{"type":"object","properties":{"time":{"type":"string","pattern":"^(?:[01]\\d|2[0-3]):[0-5]\\d$","description":"A time of day, 24-hour HH:MM: \"09:00\"."},"timezone":{"type":"string","minLength":1,"maxLength":64,"description":"An IANA time zone name, such as \"America/Chicago\". Never an abbreviation (\"EST\", \"IST\") or an offset (\"+02:00\")."}},"required":["time","timezone"],"additionalProperties":false,"description":"A wall-clock time of day in the practice's time zone."},"EndTimeOfDay":{"type":"object","properties":{"time":{"type":"string","pattern":"^(?:(?:[01]\\d|2[0-3]):[0-5]\\d|24:00)$","description":"The end of a window, 24-hour HH:MM: \"17:00\". \"24:00\" means the end of the day (midnight at the end of that date)."},"timezone":{"type":"string","minLength":1,"maxLength":64,"description":"An IANA time zone name, such as \"America/Chicago\". Never an abbreviation (\"EST\", \"IST\") or an offset (\"+02:00\")."}},"required":["time","timezone"],"additionalProperties":false,"description":"The end of a wall-clock window in the practice's time zone."},"TimeZone":{"type":"string","description":"An IANA time zone name, such as \"America/Chicago\". Never an abbreviation (\"EST\", \"IST\") or an offset (\"+02:00\")."},"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","description":"Present on some errors, such as the clashes a booking would have."}}},"request_id":{"type":"string"}}},"OverlapError":{"type":"object","description":"The time overlaps something on the provider's calendar. Nothing was changed. Send the request again with \"allow_overlap\": true, after asking the provider, to go ahead.","properties":{"error":{"type":"object","properties":{"code":{"const":"overlap"},"message":{"type":"string"},"details":{"type":"object","properties":{"clashes":{"type":"array","items":{"type":"object","properties":{"kind":{"enum":["session","busy","time_off"],"description":"session: another booking. busy: a busy time on the provider's Google Calendar. time_off: the provider's time off (a whole day, or a window of it)."},"booking_id":{"type":"string","description":"The other booking, for a session."},"starts_at":{"$ref":"#/components/schemas/Moment"},"ends_at":{"$ref":"#/components/schemas/Moment"}},"required":["kind","starts_at","ends_at"]}}}}}},"request_id":{"type":"string"}},"example":{"error":{"code":"overlap","message":"This time overlaps another session and the provider's time off. Nothing was changed. To book it anyway, ask the provider first, then send the request again with \"allow_overlap\": true.","details":{"clashes":[{"kind":"session","booking_id":"5c0e3d2a-1b4f-4e6a-8d7c-9f0a1b2c3d4e","starts_at":{"date_time":"2026-10-09T13:30:00-05:00","timezone":"America/Chicago"},"ends_at":{"date_time":"2026-10-09T14:30:00-05:00","timezone":"America/Chicago"}},{"kind":"time_off","starts_at":{"date_time":"2026-10-09T14:00:00-05:00","timezone":"America/Chicago"},"ends_at":{"date_time":"2026-10-09T16:00:00-05:00","timezone":"America/Chicago"}}]}},"request_id":"0f1e2d3c-4b5a-4968-8776-655443322110"}},"DryRunResult":{"type":"object","properties":{"dry_run":{"const":true},"plan":{"type":"object","properties":{"summary":{"type":"string"},"changes":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"label":{"type":"string"},"before":{"anyOf":[{"type":"string"},{"$ref":"#/components/schemas/Moment"},{"type":"null"}]},"after":{"anyOf":[{"type":"string"},{"$ref":"#/components/schemas/Moment"},{"type":"null"}]}}}},"client_sees":{"type":"boolean"},"outside_sees":{"type":"boolean"}}},"data":{"type":"object","description":"On some changes: what the answer's data would hold, as far as it is known before the change, shaped like the change's own data (time off: the sessions inside the block)."},"read_as":{"type":"object"}}}}},"paths":{"/clients":{"get":{"operationId":"clients_list","summary":"List your clients by name, or find them by name, email or phone.","tags":["Read"],"security":[{"apiKey":[]}],"x-riverd-data-class":"D2","x-riverd-access":"any key","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{},"display_name":{},"first_name":{},"last_name":{},"email":{},"phone":{},"status":{},"connection_status":{},"source":{},"created_at":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}]}}}},"next_cursor":{"type":["string","null"]}}}}}},"400":{"description":"A parameter is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The plan or the key does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The practice has no valid time zone (code timezone_unset), so its times cannot be read or sent. Set it in Riverd under Settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The API is turned off, or could not answer right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["new","active","inactive","archived"]}},{"name":"search","in":"query","required":false,"schema":{"description":"Find clients: the text anywhere in the display, first or last name, the email or the phone, ignoring case. Every character but NUL matches itself: %, _ and * are not wildcards. A NUL is dropped, and a search of nothing but NULs finds no client. A search with digits in it also matches the phone by its digits alone.","type":"string","maxLength":100}},{"name":"include_archived","in":"query","required":false,"schema":{"description":"false leaves archived clients out. Default true.","type":"boolean"}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"next_cursor from the previous page."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}]},"post":{"operationId":"clients_add","summary":"Add a client.","tags":["Changes"],"security":[{"apiKey":[]}],"x-riverd-data-class":"D2","x-riverd-access":"read and write keys only","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"first_name":{"type":"string","minLength":1,"maxLength":100},"last_name":{"type":"string","minLength":1,"maxLength":100},"email":{"description":"Kept unconfirmed unless contact_confirmed is true. Give an email or a phone number.","type":"string","maxLength":254},"phone":{"description":"E.164, such as +13125550123. Kept unconfirmed unless contact_confirmed is true.","type":"string","maxLength":40},"contact_confirmed":{"type":"boolean","description":"Send true only after you have read the email and phone number in this request back to the provider and the provider said they are right. Riverd then keeps them as confirmed, as if the provider had typed them, and can email and text the client. Without it they are kept unconfirmed, and Riverd sends the client nothing until the provider confirms them. It confirms only the email and phone sent in this same request."},"dry_run":{"type":"boolean","description":"true: answer what would happen, and change nothing."}},"required":["first_name","last_name"]},"examples":{"unconfirmed":{"summary":"Contact details kept unconfirmed until the provider confirms them","value":{"first_name":"Maya","last_name":"Lin","email":"maya.lin@example.com"}},"confirmed":{"summary":"The provider confirmed the email and phone number","value":{"first_name":"Maya","last_name":"Lin","email":"maya.lin@example.com","phone":"+13125550123","contact_confirmed":true}}}}}},"description":"Applies at once. The answer carries a change id (change.id): POST /changes/{changeId}/undo reverses it while undo_until has not passed. Emails and texts the change sends a client are held until change.client_messages_release_at (undo_until, or the session start when that is sooner), so an Undo before then stops them. Send dry_run: true to see what would happen without changing anything.","responses":{"200":{"description":"Done (or, with dry_run, what would be done).","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"data":{"type":"object","properties":{"client_id":{},"email_unverified":{"type":"boolean","description":"true while the client's email is unconfirmed (see contact)."},"phone_unverified":{"type":"boolean","description":"true while the client's phone number is unconfirmed (see contact)."},"contact":{"type":"object","description":"Whether the client's email and phone number are confirmed after this change. To confirm one later, ask the provider, then send POST /clients/{clientId}/confirm-contact.","properties":{"email":{"anyOf":[{"enum":["confirmed","unconfirmed"]},{"type":"null"}],"description":"confirmed: Riverd can send to it. unconfirmed: Riverd sends the client nothing at it until the provider confirms it. null: none on file."},"phone":{"anyOf":[{"enum":["confirmed","unconfirmed"]},{"type":"null"}],"description":"confirmed: Riverd can send to it. unconfirmed: Riverd sends the client nothing at it until the provider confirms it. null: none on file."}}}},"description":"What the change made, such as the new client's id. Times are on the practice's clock."},"change":{"type":"object","properties":{"id":{"type":["string","null"],"description":"Send to POST /changes/{changeId}/undo to reverse the change."},"summary":{"type":"string"},"undo_until":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}],"description":"Until when POST /changes/{changeId}/undo can reverse it."},"client_messages_held":{"type":"integer","description":"Emails or texts to clients the change caused, waiting until client_messages_release_at. An upper bound: at release each is rebuilt from the practice as it is then, and some may not be sent (for example a contact the provider has not confirmed, a client who asked to stop emails, no intake form rule that applies, or the practice's client emails paused or at their daily limit)."},"client_messages_release_at":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}],"description":"When the held emails and texts go out. Usually undo_until; earlier when the session starts before undo_until. An Undo after this moment still reverses the change, but the messages have gone. Null when none are held."}}},"read_as":{"type":"object","description":"How Riverd read the times you sent, on the practice's clock."}}},{"$ref":"#/components/schemas/DryRunResult"}]}}}},"400":{"description":"The body, a field, dry_run or the Idempotency-Key header is not valid, or the body has a field this endpoint does not take. A time that cannot be read says why in its code: invalid_time, offset_zone_mismatch, nonexistent_local_time, ambiguous_local_time, invalid_timezone or timezone_mismatch; a booking at the second of two equal clock times (the clocks going back) is repeated_time.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The plan or the key does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Nothing with that id in your practice.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The practice changed since (the change was already undone, or a row was edited), or a request with the same Idempotency-Key is still running.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The change can't be made as asked (error.code says why), the practice has no time zone set (timezone_unset), or the Idempotency-Key was used for a different request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The API is turned off, or could not answer right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[\\x21-\\x7E]+$"},"description":"Optional. A unique value you choose for this request, like a UUID. Send the same value again and Riverd answers with the first result (header Idempotent-Replayed: true) instead of making the change twice. Honoured for 24 hours, per key (after that the same value starts a new request). The same value with a different request answers 422; while the first request is still running, 409."}]}},"/clients/{clientId}":{"get":{"operationId":"clients_get","summary":"One client.","tags":["Read"],"security":[{"apiKey":[]}],"x-riverd-data-class":"D2","x-riverd-access":"any key","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{},"display_name":{},"first_name":{},"last_name":{},"email":{},"phone":{},"status":{},"connection_status":{},"source":{},"created_at":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}]},"date_of_birth":{"anyOf":[{"$ref":"#/components/schemas/Date"},{"type":"null"}]},"tags":{},"email_unverified":{},"phone_unverified":{}}}}}}}},"400":{"description":"A parameter is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The plan or the key does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Nothing with that id in your practice.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The practice has no valid time zone (code timezone_unset), so its times cannot be read or sent. Set it in Riverd under Settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The API is turned off, or could not answer right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"clientId","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_-]+$"},"description":"The client's id."}]}},"/bookings":{"get":{"operationId":"bookings_list","summary":"List bookings by start time, optionally starting from one moment and before another, or for one client.","tags":["Read"],"security":[{"apiKey":[]}],"x-riverd-data-class":"D2","x-riverd-access":"any key","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{},"provider_client_id":{},"service_id":{},"starts_at":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}]},"ends_at":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}]},"status":{},"source":{}}}},"next_cursor":{"type":["string","null"]},"read_as":{"type":"object","description":"How each time you sent was read, on the clock it was read on. Present when the request had a time parameter.","properties":{"from":{"$ref":"#/components/schemas/Moment"},"to":{"$ref":"#/components/schemas/Moment"}}}}}}}},"400":{"description":"A parameter is not valid. A time that cannot be read says why in its code: invalid_time, offset_zone_mismatch, nonexistent_local_time, ambiguous_local_time, invalid_timezone or timezone_mismatch.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The plan or the key does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The practice has no valid time zone (code timezone_unset), so its times cannot be read or sent. Set it in Riverd under Settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The API is turned off, or could not answer right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"from","in":"query","required":false,"schema":{"type":"string","maxLength":100,"description":"A moment. A local time is read on the practice's clock (\"2026-10-09T14:00\"); an exact moment carries its offset (\"2026-10-09T14:00:00-05:00\" or \"...Z\"; in a URL, write \"+\" as %2B). The response repeats how it was read, in read_as."}},{"name":"to","in":"query","required":false,"schema":{"type":"string","maxLength":100,"description":"A moment. A local time is read on the practice's clock (\"2026-10-09T14:00\"); an exact moment carries its offset (\"2026-10-09T14:00:00-05:00\" or \"...Z\"; in a URL, write \"+\" as %2B). The response repeats how it was read, in read_as."}},{"name":"client_id","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_-]+$"}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"next_cursor from the previous page."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}]},"post":{"operationId":"bookings_create","summary":"Book a session for an existing client (client_id) or for someone new (client), starting at most 24 months ahead. Refused with 409 overlap when the time clashes with another session, a Google Calendar busy time or time off, unless allow_overlap is true.","tags":["Changes"],"security":[{"apiKey":[]}],"x-riverd-data-class":"D2","x-riverd-access":"read and write keys only","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"client_id":{"description":"An existing client's id. Send this or client, not both.","type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_-]+$"},"client":{"type":"object","properties":{"first_name":{"type":"string","minLength":1,"maxLength":100},"last_name":{"type":"string","minLength":1,"maxLength":100},"email":{"type":"string","maxLength":254},"phone":{"description":"E.164, such as +13125550123.","type":"string","maxLength":40}},"required":["first_name","last_name"],"description":"Someone who is not a client yet: added as a client and booked in one change, with one Undo. Give an email or a phone number. Contact details sent with an API key are kept unconfirmed, and Riverd sends nothing to them until the provider confirms them, unless contact_confirmed is true."},"service_id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_-]+$","description":"The service booked. Its length is the session's length unless end or duration_minutes says otherwise."},"start":{"anyOf":[{"type":"string","maxLength":100,"description":"A local time on the practice's clock (\"2026-10-09T14:00\"), an exact moment (\"2026-10-09T14:00:00-05:00\" or \"...Z\"), or an RFC 9557 string with the zone in brackets (\"2026-10-09T14:00:00-05:00[America/Chicago]\")."},{"type":"object","properties":{"date_time":{"type":"string","maxLength":100,"description":"A local time (\"2026-10-09T14:00\") or an exact moment with an offset."},"timezone":{"description":"The zone date_time is read in. Leave it out for the practice's zone. An IANA time zone name, such as \"America/Chicago\". Never an abbreviation (\"EST\", \"IST\") or an offset (\"+02:00\").","anyOf":[{"type":"string","minLength":1,"maxLength":64,"description":"An IANA time zone name, such as \"America/Chicago\". Never an abbreviation (\"EST\", \"IST\") or an offset (\"+02:00\")."},{"type":"null"}]}},"required":["date_time"],"additionalProperties":false,"description":"A moment in a named zone: {\"date_time\": \"2026-10-09T14:00\", \"timezone\": \"Asia/Tokyo\"}."}],"description":"When the session starts, at most 24 months ahead. A moment. Without a zone it is read on the practice's clock. An offset that does not match the zone at that moment is refused, and so is a local time that does not exist or happens twice on a clock-change day (add the offset to pick one)."},"end":{"anyOf":[{"type":"string","maxLength":100,"description":"A local time on the practice's clock (\"2026-10-09T14:00\"), an exact moment (\"2026-10-09T14:00:00-05:00\" or \"...Z\"), or an RFC 9557 string with the zone in brackets (\"2026-10-09T14:00:00-05:00[America/Chicago]\")."},{"type":"object","properties":{"date_time":{"type":"string","maxLength":100,"description":"A local time (\"2026-10-09T14:00\") or an exact moment with an offset."},"timezone":{"description":"The zone date_time is read in. Leave it out for the practice's zone. An IANA time zone name, such as \"America/Chicago\". Never an abbreviation (\"EST\", \"IST\") or an offset (\"+02:00\").","anyOf":[{"type":"string","minLength":1,"maxLength":64,"description":"An IANA time zone name, such as \"America/Chicago\". Never an abbreviation (\"EST\", \"IST\") or an offset (\"+02:00\")."},{"type":"null"}]}},"required":["date_time"],"additionalProperties":false,"description":"A moment in a named zone: {\"date_time\": \"2026-10-09T14:00\", \"timezone\": \"Asia/Tokyo\"}."}],"description":"When the session ends, on the same day. Send this or duration_minutes, or neither to use the service's length (for a move: the session's current length). A moment. Without a zone it is read on the practice's clock. An offset that does not match the zone at that moment is refused, and so is a local time that does not exist or happens twice on a clock-change day (add the offset to pick one)."},"duration_minutes":{"type":"integer","minimum":1,"maximum":1439,"description":"How long the session is, in whole minutes. Send this or end, not both."},"notes":{"description":"Session notes. Private to the practice: the client never sees them.","type":"string","maxLength":20000},"allow_overlap":{"type":"boolean","description":"true makes the change even when the time overlaps another session, a busy time on the provider's Google Calendar, or the provider's time off. Ask the provider first. Without it, an overlap answers 409 overlap with the list of clashes, and nothing is changed."},"contact_confirmed":{"type":"boolean","description":"With \"client\" only. Send true only after you have read the email and phone number in this request back to the provider and the provider said they are right. Riverd then keeps them as confirmed, as if the provider had typed them, and can email and text the client. Without it they are kept unconfirmed, and Riverd sends the client nothing until the provider confirms them. It confirms only the email and phone sent in this same request. To confirm an existing client's details, use POST /clients/{clientId}/confirm-contact."},"dry_run":{"type":"boolean","description":"true: answer what would happen, and change nothing."}},"required":["service_id","start"]},"examples":{"existing_client":{"summary":"Book an existing client at 2pm on the practice's clock, for the service's length","value":{"client_id":"3f1c2a9e-7d4b-4c1e-9a8f-2b6d5e4c3a10","service_id":"8a7b6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d","start":"2026-10-09T14:00"}},"new_client":{"summary":"Add someone new and book them for 90 minutes","value":{"client":{"first_name":"Maya","last_name":"Lin","email":"maya@example.com"},"service_id":"8a7b6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d","start":{"date_time":"2026-10-09T14:00","timezone":"America/Chicago"},"duration_minutes":90}},"new_client_confirmed":{"summary":"Add someone new whose email the provider confirmed, and book them","value":{"client":{"first_name":"Maya","last_name":"Lin","email":"maya@example.com"},"contact_confirmed":true,"service_id":"8a7b6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d","start":"2026-10-09T14:00"}},"overlap_allowed":{"summary":"Book over a clash on purpose, after asking the provider","value":{"client_id":"3f1c2a9e-7d4b-4c1e-9a8f-2b6d5e4c3a10","service_id":"8a7b6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d","start":"2026-10-09T14:00:00-05:00","end":"2026-10-09T15:00:00-05:00","allow_overlap":true}}}}}},"description":"Applies at once. The answer carries a change id (change.id): POST /changes/{changeId}/undo reverses it while undo_until has not passed. Emails and texts the change sends a client are held until change.client_messages_release_at (undo_until, or the session start when that is sooner), so an Undo before then stops them. Send dry_run: true to see what would happen without changing anything.","responses":{"200":{"description":"Done (or, with dry_run, what would be done).","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"data":{"type":"object","properties":{"booking_id":{},"backdated":{},"starts_at":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}]},"ends_at":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}]},"client_id":{},"email_unverified":{"type":"boolean","description":"true while the client's email is unconfirmed (see contact)."},"phone_unverified":{"type":"boolean","description":"true while the client's phone number is unconfirmed (see contact)."},"contact":{"type":"object","description":"Whether the client's email and phone number are confirmed after this change. To confirm one later, ask the provider, then send POST /clients/{clientId}/confirm-contact.","properties":{"email":{"anyOf":[{"enum":["confirmed","unconfirmed"]},{"type":"null"}],"description":"confirmed: Riverd can send to it. unconfirmed: Riverd sends the client nothing at it until the provider confirms it. null: none on file."},"phone":{"anyOf":[{"enum":["confirmed","unconfirmed"]},{"type":"null"}],"description":"confirmed: Riverd can send to it. unconfirmed: Riverd sends the client nothing at it until the provider confirms it. null: none on file."}}}},"description":"What the change made, such as the new client's id. Times are on the practice's clock."},"change":{"type":"object","properties":{"id":{"type":["string","null"],"description":"Send to POST /changes/{changeId}/undo to reverse the change."},"summary":{"type":"string"},"undo_until":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}],"description":"Until when POST /changes/{changeId}/undo can reverse it."},"client_messages_held":{"type":"integer","description":"Emails or texts to clients the change caused, waiting until client_messages_release_at. An upper bound: at release each is rebuilt from the practice as it is then, and some may not be sent (for example a contact the provider has not confirmed, a client who asked to stop emails, no intake form rule that applies, or the practice's client emails paused or at their daily limit)."},"client_messages_release_at":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}],"description":"When the held emails and texts go out. Usually undo_until; earlier when the session starts before undo_until. An Undo after this moment still reverses the change, but the messages have gone. Null when none are held."}}},"read_as":{"type":"object","description":"How Riverd read the times you sent, on the practice's clock. Present when the body had a time.","properties":{"start":{"$ref":"#/components/schemas/Moment"},"end":{"$ref":"#/components/schemas/Moment"}}}}},{"allOf":[{"$ref":"#/components/schemas/DryRunResult"},{"type":"object","properties":{"read_as":{"type":"object","description":"How Riverd read the times you sent, on the practice's clock. Present when the body had a time.","properties":{"start":{"$ref":"#/components/schemas/Moment"},"end":{"$ref":"#/components/schemas/Moment"}}}}}]}]}}}},"400":{"description":"The body, a field, dry_run or the Idempotency-Key header is not valid, or the body has a field this endpoint does not take. A time that cannot be read says why in its code: invalid_time, offset_zone_mismatch, nonexistent_local_time, ambiguous_local_time, invalid_timezone or timezone_mismatch; a booking at the second of two equal clock times (the clocks going back) is repeated_time.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The plan or the key does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Nothing with that id in your practice.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The time overlaps another session, a busy time on the provider's Google Calendar or time off (code overlap), and nothing was changed. error.details.clashes lists each clash. Ask the provider, then send the request again with \"allow_overlap\": true to go ahead. Also 409 for a change that is already undone, or an Idempotency-Key still running.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OverlapError"}}}},"422":{"description":"The change can't be made as asked (error.code says why), the practice has no time zone set (timezone_unset), or the Idempotency-Key was used for a different request. A start more than 24 months ahead on the practice's clock is start_too_far: through the API a session starts at most 24 months ahead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The API is turned off, or could not answer right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[\\x21-\\x7E]+$"},"description":"Optional. A unique value you choose for this request, like a UUID. Send the same value again and Riverd answers with the first result (header Idempotent-Replayed: true) instead of making the change twice. Honoured for 24 hours, per key (after that the same value starts a new request). The same value with a different request answers 422; while the first request is still running, 409."}]}},"/bookings/{bookingId}":{"get":{"operationId":"bookings_get","summary":"One booking.","tags":["Read"],"security":[{"apiKey":[]}],"x-riverd-data-class":"D2","x-riverd-access":"any key","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{},"provider_client_id":{},"service_id":{},"starts_at":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}]},"ends_at":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}]},"status":{},"source":{}}}}}}}},"400":{"description":"A parameter is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The plan or the key does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Nothing with that id in your practice.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The practice has no valid time zone (code timezone_unset), so its times cannot be read or sent. Set it in Riverd under Settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The API is turned off, or could not answer right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"bookingId","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_-]+$"},"description":"The session's id (a booking's id)."}]}},"/schedule":{"get":{"operationId":"schedule_list","summary":"Your sessions starting from one moment and before another, with client and service names.","tags":["Read"],"security":[{"apiKey":[]}],"x-riverd-data-class":"D2","x-riverd-access":"any key","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{},"starts_at":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}]},"ends_at":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}]},"status":{},"client_id":{},"client_name":{},"service_id":{},"service_name":{}}}},"next_cursor":{"type":["string","null"]},"read_as":{"type":"object","description":"How each time you sent was read, on the clock it was read on. Present when the request had a time parameter.","properties":{"from":{"$ref":"#/components/schemas/Moment"},"to":{"$ref":"#/components/schemas/Moment"}}}}}}}},"400":{"description":"A parameter is not valid. A time that cannot be read says why in its code: invalid_time, offset_zone_mismatch, nonexistent_local_time, ambiguous_local_time, invalid_timezone or timezone_mismatch.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The plan or the key does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The practice has no valid time zone (code timezone_unset), so its times cannot be read or sent. Set it in Riverd under Settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The API is turned off, or could not answer right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"from","in":"query","required":true,"schema":{"type":"string","maxLength":100,"description":"A moment. A local time is read on the practice's clock (\"2026-10-09T14:00\"); an exact moment carries its offset (\"2026-10-09T14:00:00-05:00\" or \"...Z\"; in a URL, write \"+\" as %2B). The response repeats how it was read, in read_as."}},{"name":"to","in":"query","required":true,"schema":{"type":"string","maxLength":100,"description":"A moment. A local time is read on the practice's clock (\"2026-10-09T14:00\"); an exact moment carries its offset (\"2026-10-09T14:00:00-05:00\" or \"...Z\"; in a URL, write \"+\" as %2B). The response repeats how it was read, in read_as."}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"next_cursor from the previous page."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}]}},"/services":{"get":{"operationId":"services_list","summary":"Your services. Inactive ones only when asked.","tags":["Read"],"security":[{"apiKey":[]}],"x-riverd-data-class":"D1","x-riverd-access":"any key","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{},"name":{},"duration_minutes":{},"price_cents":{},"currency":{},"is_active":{}}}}}}}}},"400":{"description":"A parameter is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The plan or the key does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The practice has no valid time zone (code timezone_unset), so its times cannot be read or sent. Set it in Riverd under Settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The API is turned off, or could not answer right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"include_inactive","in":"query","required":false,"schema":{"type":"boolean"}}]}},"/availability":{"get":{"operationId":"availability_list","summary":"Your weekly booking hours.","tags":["Read"],"security":[{"apiKey":[]}],"x-riverd-data-class":"D0","x-riverd-access":"any key","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{},"day":{},"start_time":{"anyOf":[{"$ref":"#/components/schemas/TimeOfDay"},{"type":"null"}]},"end_time":{"anyOf":[{"$ref":"#/components/schemas/EndTimeOfDay"},{"type":"null"}]}}}}}}}}},"400":{"description":"A parameter is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The plan or the key does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The practice has no valid time zone (code timezone_unset), so its times cannot be read or sent. Set it in Riverd under Settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The API is turned off, or could not answer right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/time-off":{"get":{"operationId":"availability_time_off_list","summary":"Days and times you blocked off, optionally ending on or after a date.","tags":["Read"],"security":[{"apiKey":[]}],"x-riverd-data-class":"D1","x-riverd-access":"any key","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{},"start_date":{"anyOf":[{"$ref":"#/components/schemas/Date"},{"type":"null"}]},"end_date":{"anyOf":[{"$ref":"#/components/schemas/Date"},{"type":"null"}]},"is_all_day":{},"start_time":{"anyOf":[{"$ref":"#/components/schemas/TimeOfDay"},{"type":"null"}]},"end_time":{"anyOf":[{"$ref":"#/components/schemas/EndTimeOfDay"},{"type":"null"}]},"reason":{}}}},"next_cursor":{"type":["string","null"]},"read_as":{"type":"object","description":"How each time you sent was read, on the clock it was read on. Present when the request had a time parameter.","properties":{"from":{"$ref":"#/components/schemas/Date"}}}}}}}},"400":{"description":"A parameter is not valid. A time that cannot be read says why in its code: invalid_time, offset_zone_mismatch, nonexistent_local_time, ambiguous_local_time, invalid_timezone or timezone_mismatch.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The plan or the key does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The practice has no valid time zone (code timezone_unset), so its times cannot be read or sent. Set it in Riverd under Settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The API is turned off, or could not answer right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"from","in":"query","required":false,"schema":{"type":"string","maxLength":100,"description":"A date, YYYY-MM-DD, in the practice's time zone: \"2026-10-09\". The response repeats how it was read, in read_as."}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"next_cursor from the previous page."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}]},"post":{"operationId":"time_off_create","summary":"Block time off: whole days, or the same hours on each of several days. Like the Schedule page, it cancels nothing; the answer lists the sessions inside the blocked time so you can ask about moving them.","tags":["Changes"],"security":[{"apiKey":[]}],"x-riverd-data-class":"D2","x-riverd-access":"read and write keys only","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"start_date":{"anyOf":[{"type":"string","maxLength":100,"description":"A calendar date, YYYY-MM-DD: \"2026-10-09\"."},{"type":"object","properties":{"date":{"type":"string","maxLength":100,"description":"A calendar date, YYYY-MM-DD: \"2026-10-09\"."},"timezone":{"description":"Optional. If sent, it must be the practice's time zone.","anyOf":[{"type":"string","minLength":1,"maxLength":64,"description":"An IANA time zone name, such as \"America/Chicago\". Never an abbreviation (\"EST\", \"IST\") or an offset (\"+02:00\")."},{"type":"null"}]}},"required":["date"],"additionalProperties":false,"description":"{\"date\": \"2026-10-09\", \"timezone\": \"America/Chicago\"}"}],"description":"The first day off: \"2026-10-09\". Always on the practice's clock."},"end_date":{"description":"The last day off, included. Leave it out for one day.","anyOf":[{"anyOf":[{"type":"string","maxLength":100,"description":"A calendar date, YYYY-MM-DD: \"2026-10-09\"."},{"type":"object","properties":{"date":{"type":"string","maxLength":100,"description":"A calendar date, YYYY-MM-DD: \"2026-10-09\"."},"timezone":{"description":"Optional. If sent, it must be the practice's time zone.","anyOf":[{"type":"string","minLength":1,"maxLength":64,"description":"An IANA time zone name, such as \"America/Chicago\". Never an abbreviation (\"EST\", \"IST\") or an offset (\"+02:00\")."},{"type":"null"}]}},"required":["date"],"additionalProperties":false,"description":"{\"date\": \"2026-10-09\", \"timezone\": \"America/Chicago\"}"}],"description":"A date, always read in the practice's time zone."},{"type":"null"}]},"start_time":{"description":"Block only part of each day, from this time: \"09:00\". Send it with end_time; leave both out to block whole days.","anyOf":[{"anyOf":[{"type":"string","maxLength":100,"description":"A time of day, 24-hour HH:MM: \"09:00\"."},{"type":"object","properties":{"time":{"type":"string","maxLength":100,"description":"A time of day, 24-hour HH:MM: \"09:00\"."},"timezone":{"description":"Optional. If sent, it must be the practice's time zone.","anyOf":[{"type":"string","minLength":1,"maxLength":64,"description":"An IANA time zone name, such as \"America/Chicago\". Never an abbreviation (\"EST\", \"IST\") or an offset (\"+02:00\")."},{"type":"null"}]}},"required":["time"],"additionalProperties":false,"description":"{\"time\": \"09:00\", \"timezone\": \"America/Chicago\"}"}],"description":"A time of day, 00:00 to 23:59, always read in the practice's time zone."},{"type":"null"}]},"end_time":{"description":"The end of the window on each day: \"12:00\". After start_time, on the same day.","anyOf":[{"anyOf":[{"type":"string","maxLength":100,"description":"A time of day, 24-hour HH:MM: \"09:00\"."},{"type":"object","properties":{"time":{"type":"string","maxLength":100,"description":"A time of day, 24-hour HH:MM: \"09:00\"."},"timezone":{"description":"Optional. If sent, it must be the practice's time zone.","anyOf":[{"type":"string","minLength":1,"maxLength":64,"description":"An IANA time zone name, such as \"America/Chicago\". Never an abbreviation (\"EST\", \"IST\") or an offset (\"+02:00\")."},{"type":"null"}]}},"required":["time"],"additionalProperties":false,"description":"{\"time\": \"09:00\", \"timezone\": \"America/Chicago\"}"}],"description":"A time of day, 00:00 to 23:59, always read in the practice's time zone."},{"type":"null"}]},"reason":{"description":"A note for yourself, such as \"Vacation\". Saved with the block like on the Schedule page, and never repeated in an answer.","anyOf":[{"type":"string","maxLength":500},{"type":"null"}]},"dry_run":{"type":"boolean","description":"true: answer what would happen, and change nothing."}},"additionalProperties":false,"required":["start_date"]},"examples":{"whole_days":{"summary":"Whole days off","value":{"start_date":"2026-12-24","end_date":"2026-12-26","reason":"Holidays"}},"part_of_each_day":{"summary":"The same hours on each of several days","value":{"start_date":"2026-10-12","end_date":"2026-10-16","start_time":"12:00","end_time":"13:00"}}}}}},"description":"Applies at once. The answer carries a change id (change.id): POST /changes/{changeId}/undo reverses it while undo_until has not passed. Emails and texts the change sends a client are held until change.client_messages_release_at (undo_until, or the session start when that is sooner), so an Undo before then stops them. Send dry_run: true to see what would happen without changing anything.","responses":{"200":{"description":"Done (or, with dry_run, what would be done).","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"data":{"type":"object","properties":{"time_off_id":{},"start_date":{"anyOf":[{"$ref":"#/components/schemas/Date"},{"type":"null"}]},"end_date":{"anyOf":[{"$ref":"#/components/schemas/Date"},{"type":"null"}]},"is_all_day":{},"start_time":{"anyOf":[{"$ref":"#/components/schemas/TimeOfDay"},{"type":"null"}]},"end_time":{"anyOf":[{"$ref":"#/components/schemas/EndTimeOfDay"},{"type":"null"}]},"sessions_inside":{"type":"array","items":{"type":"object","properties":{"starts_at":{"$ref":"#/components/schemas/Moment"},"ends_at":{"$ref":"#/components/schemas/Moment"}}}},"sessions_inside_truncated":{}},"description":"What the change made, such as the new client's id. Times are on the practice's clock."},"change":{"type":"object","properties":{"id":{"type":["string","null"],"description":"Send to POST /changes/{changeId}/undo to reverse the change."},"summary":{"type":"string"},"undo_until":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}],"description":"Until when POST /changes/{changeId}/undo can reverse it."},"client_messages_held":{"type":"integer","description":"Emails or texts to clients the change caused, waiting until client_messages_release_at. An upper bound: at release each is rebuilt from the practice as it is then, and some may not be sent (for example a contact the provider has not confirmed, a client who asked to stop emails, no intake form rule that applies, or the practice's client emails paused or at their daily limit)."},"client_messages_release_at":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}],"description":"When the held emails and texts go out. Usually undo_until; earlier when the session starts before undo_until. An Undo after this moment still reverses the change, but the messages have gone. Null when none are held."}}},"read_as":{"type":"object","description":"How Riverd read the times you sent, on the practice's clock. Present when the body had a time.","properties":{"start_date":{"$ref":"#/components/schemas/Date"},"end_date":{"$ref":"#/components/schemas/Date"},"start_time":{"$ref":"#/components/schemas/TimeOfDay"},"end_time":{"$ref":"#/components/schemas/TimeOfDay"}}}}},{"allOf":[{"$ref":"#/components/schemas/DryRunResult"},{"type":"object","properties":{"read_as":{"type":"object","description":"How Riverd read the times you sent, on the practice's clock. Present when the body had a time.","properties":{"start_date":{"$ref":"#/components/schemas/Date"},"end_date":{"$ref":"#/components/schemas/Date"},"start_time":{"$ref":"#/components/schemas/TimeOfDay"},"end_time":{"$ref":"#/components/schemas/TimeOfDay"}}}}}]}]}}}},"400":{"description":"The body, a field, dry_run or the Idempotency-Key header is not valid, or the body has a field this endpoint does not take. A time that cannot be read says why in its code: invalid_time, offset_zone_mismatch, nonexistent_local_time, ambiguous_local_time, invalid_timezone or timezone_mismatch; a booking at the second of two equal clock times (the clocks going back) is repeated_time.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The plan or the key does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Nothing with that id in your practice.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The practice changed since (the change was already undone, or a row was edited), or a request with the same Idempotency-Key is still running.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The change can't be made as asked (error.code says why), the practice has no time zone set (timezone_unset), or the Idempotency-Key was used for a different request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The API is turned off, or could not answer right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[\\x21-\\x7E]+$"},"description":"Optional. A unique value you choose for this request, like a UUID. Send the same value again and Riverd answers with the first result (header Idempotent-Replayed: true) instead of making the change twice. Honoured for 24 hours, per key (after that the same value starts a new request). The same value with a different request answers 422; while the first request is still running, 409."}]}},"/bookings/{bookingId}/notes":{"get":{"operationId":"bookings_notes_get","summary":"The session notes for one booking.","tags":["Read"],"security":[{"apiKey":[]}],"x-riverd-data-class":"D3","x-riverd-access":"any key","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"booking_id":{},"notes":{},"previous_notes":{},"updated_at":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}]}}}}}}}},"400":{"description":"A parameter is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The plan or the key does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Nothing with that id in your practice.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The practice has no valid time zone (code timezone_unset), so its times cannot be read or sent. Set it in Riverd under Settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The API is turned off, or could not answer right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"bookingId","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_-]+$"},"description":"The session's id (a booking's id)."}]},"put":{"operationId":"bookings_notes_write","summary":"Write a session's notes. Adds your text after the notes already there, unless replace is true. Nothing is sent to the client, and no AI credits are used.","tags":["Changes"],"security":[{"apiKey":[]}],"x-riverd-data-class":"D2","x-riverd-access":"read and write keys only","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"notes":{"type":"string","maxLength":20000,"description":"The text. Added after the notes already there, with a blank line between, unless replace is true. The notes can be at most 20,000 characters in all."},"replace":{"description":"true: replace the notes with this text (\"\" clears them). Leave it out to add to them.","type":"boolean"},"dry_run":{"type":"boolean","description":"true: answer what would happen, and change nothing."}},"additionalProperties":false,"required":["notes"]},"examples":{"add":{"summary":"Add to the notes","value":{"notes":"Follow-up: shoulder mobility improved, keep the home stretches."}},"replace":{"summary":"Replace the notes","value":{"notes":"S: Lower back tight after travel.\nO: Limited flexion.\nA: Improving.\nP: Rebook in two weeks.","replace":true}}}}}},"description":"Applies at once. The answer carries a change id (change.id): POST /changes/{changeId}/undo reverses it while undo_until has not passed. Emails and texts the change sends a client are held until change.client_messages_release_at (undo_until, or the session start when that is sooner), so an Undo before then stops them. Send dry_run: true to see what would happen without changing anything.","responses":{"200":{"description":"Done (or, with dry_run, what would be done).","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"data":{"type":"object","properties":{"booking_id":{},"mode":{},"length":{},"updated_at":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}]}},"description":"What the change made, such as the new client's id. Times are on the practice's clock."},"change":{"type":"object","properties":{"id":{"type":["string","null"],"description":"Send to POST /changes/{changeId}/undo to reverse the change."},"summary":{"type":"string"},"undo_until":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}],"description":"Until when POST /changes/{changeId}/undo can reverse it."},"client_messages_held":{"type":"integer","description":"Emails or texts to clients the change caused, waiting until client_messages_release_at. An upper bound: at release each is rebuilt from the practice as it is then, and some may not be sent (for example a contact the provider has not confirmed, a client who asked to stop emails, no intake form rule that applies, or the practice's client emails paused or at their daily limit)."},"client_messages_release_at":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}],"description":"When the held emails and texts go out. Usually undo_until; earlier when the session starts before undo_until. An Undo after this moment still reverses the change, but the messages have gone. Null when none are held."}}},"read_as":{"type":"object","description":"How Riverd read the times you sent, on the practice's clock."}}},{"$ref":"#/components/schemas/DryRunResult"}]}}}},"400":{"description":"The body, a field, dry_run or the Idempotency-Key header is not valid, or the body has a field this endpoint does not take. A time that cannot be read says why in its code: invalid_time, offset_zone_mismatch, nonexistent_local_time, ambiguous_local_time, invalid_timezone or timezone_mismatch; a booking at the second of two equal clock times (the clocks going back) is repeated_time.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The plan or the key does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Nothing with that id in your practice.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The practice changed since (the change was already undone, or a row was edited), or a request with the same Idempotency-Key is still running.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The change can't be made as asked (error.code says why), the practice has no time zone set (timezone_unset), or the Idempotency-Key was used for a different request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The API is turned off, or could not answer right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"bookingId","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_-]+$"},"description":"The session's id (a booking's id)."},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[\\x21-\\x7E]+$"},"description":"Optional. A unique value you choose for this request, like a UUID. Send the same value again and Riverd answers with the first result (header Idempotent-Replayed: true) instead of making the change twice. Honoured for 24 hours, per key (after that the same value starts a new request). The same value with a different request answers 422; while the first request is still running, 409."}]}},"/session-notes":{"get":{"operationId":"bookings_notes_list","summary":"Session notes, oldest change first, optionally changed at or after a moment.","tags":["Read"],"security":[{"apiKey":[]}],"x-riverd-data-class":"D3","x-riverd-access":"any key","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"booking_id":{},"notes":{},"previous_notes":{},"updated_at":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}]}}}},"next_cursor":{"type":["string","null"]},"read_as":{"type":"object","description":"How each time you sent was read, on the clock it was read on. Present when the request had a time parameter.","properties":{"updated_since":{"$ref":"#/components/schemas/Moment"}}}}}}}},"400":{"description":"A parameter is not valid. A time that cannot be read says why in its code: invalid_time, offset_zone_mismatch, nonexistent_local_time, ambiguous_local_time, invalid_timezone or timezone_mismatch.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The plan or the key does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The practice has no valid time zone (code timezone_unset), so its times cannot be read or sent. Set it in Riverd under Settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The API is turned off, or could not answer right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"updated_since","in":"query","required":false,"schema":{"type":"string","maxLength":100,"description":"A moment. A local time is read on the practice's clock (\"2026-10-09T14:00\"); an exact moment carries its offset (\"2026-10-09T14:00:00-05:00\" or \"...Z\"; in a URL, write \"+\" as %2B). The response repeats how it was read, in read_as."}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"next_cursor from the previous page."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}]}},"/intake-answers":{"get":{"operationId":"intake_submissions_list","summary":"Filled intake forms, oldest first, optionally filled at or after a moment. The questions are on each answer's own page.","tags":["Read"],"security":[{"apiKey":[]}],"x-riverd-data-class":"D3","x-riverd-access":"any key","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{},"provider_client_id":{},"booking_id":{},"form_version_id":{},"answers":{},"signed_name":{},"attested":{},"signed_at":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}]},"created_at":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}]}}}},"next_cursor":{"type":["string","null"]},"read_as":{"type":"object","description":"How each time you sent was read, on the clock it was read on. Present when the request had a time parameter.","properties":{"since":{"$ref":"#/components/schemas/Moment"}}}}}}}},"400":{"description":"A parameter is not valid. A time that cannot be read says why in its code: invalid_time, offset_zone_mismatch, nonexistent_local_time, ambiguous_local_time, invalid_timezone or timezone_mismatch.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The plan or the key does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The practice has no valid time zone (code timezone_unset), so its times cannot be read or sent. Set it in Riverd under Settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The API is turned off, or could not answer right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"client_id","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_-]+$"}},{"name":"since","in":"query","required":false,"schema":{"type":"string","maxLength":100,"description":"A moment. A local time is read on the practice's clock (\"2026-10-09T14:00\"); an exact moment carries its offset (\"2026-10-09T14:00:00-05:00\" or \"...Z\"; in a URL, write \"+\" as %2B). The response repeats how it was read, in read_as."}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"next_cursor from the previous page."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}]}},"/intake-answers/{submissionId}":{"get":{"operationId":"intake_submissions_get","summary":"One filled intake form, with the questions it was answered against.","tags":["Read"],"security":[{"apiKey":[]}],"x-riverd-data-class":"D3","x-riverd-access":"any key","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{},"provider_client_id":{},"booking_id":{},"form_version_id":{},"answers":{},"signed_name":{},"attested":{},"signed_at":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}]},"created_at":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}]},"schema_snapshot":{}}}}}}}},"400":{"description":"A parameter is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The plan or the key does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Nothing with that id in your practice.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The practice has no valid time zone (code timezone_unset), so its times cannot be read or sent. Set it in Riverd under Settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The API is turned off, or could not answer right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"submissionId","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_-]+$"},"description":"The filled intake form's id."}]}},"/open-times":{"get":{"operationId":"open_times_find","summary":"When you are free: on one date, or over up to 14 dates with from and to. Without service_id, the free stretches of each day (your weekly hours minus sessions, Google Calendar busy times and time off). With service_id, the start times a client can book that service at on your booking page.","tags":["Read"],"security":[{"apiKey":[]}],"x-riverd-data-class":"D1","x-riverd-access":"any key","description":"Send date (one day), or from and to (up to 14 days, both included). Dates are on your practice's clock. Each row is one date. Without service_id it has stretches: each {start, end} is a stretch of your weekly hours with no session (pending, confirmed or in progress), Google Calendar busy time or time off in it. All-day time off closes the date; part-day time off closes its window. With service_id (an active service) it has start_times instead: exactly the start times a client sees for that service on your booking page that day, on your slot interval, with no clash, and none in a month where your plan's monthly session limit is reached. Nothing at or before now is offered: today's stretches start at the next whole minute, and past dates come back empty. On a clock-change day the stretches follow the clock, so the day is 23 or 25 hours long.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"date":{"anyOf":[{"$ref":"#/components/schemas/Date"},{"type":"null"}]},"stretches":{"type":"array","items":{"type":"object","properties":{"start":{"$ref":"#/components/schemas/Moment"},"end":{"$ref":"#/components/schemas/Moment"}}}},"start_times":{"type":"array","items":{"$ref":"#/components/schemas/Moment"}}}}},"read_as":{"type":"object","description":"How each time you sent was read, on the clock it was read on. Present when the request had a time parameter.","properties":{"date":{"$ref":"#/components/schemas/Date"},"from":{"$ref":"#/components/schemas/Date"},"to":{"$ref":"#/components/schemas/Date"}}}}}}}},"400":{"description":"A parameter is not valid. A time that cannot be read says why in its code: invalid_time, offset_zone_mismatch, nonexistent_local_time, ambiguous_local_time, invalid_timezone or timezone_mismatch.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The plan or the key does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The practice has no valid time zone (code timezone_unset), so its times cannot be read or sent. Set it in Riverd under Settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The API is turned off, or could not answer right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"date","in":"query","required":false,"schema":{"type":"string","maxLength":100,"description":"A date, YYYY-MM-DD, in the practice's time zone: \"2026-10-09\". The response repeats how it was read, in read_as."}},{"name":"from","in":"query","required":false,"schema":{"type":"string","maxLength":100,"description":"A date, YYYY-MM-DD, in the practice's time zone: \"2026-10-09\". The response repeats how it was read, in read_as."}},{"name":"to","in":"query","required":false,"schema":{"type":"string","maxLength":100,"description":"A date, YYYY-MM-DD, in the practice's time zone: \"2026-10-09\". The response repeats how it was read, in read_as."}},{"name":"service_id","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_-]+$"}}]}},"/bookings/past":{"post":{"operationId":"bookings_log_past","summary":"Log a session that already happened, for an existing client. Its start must be in the past; it is not checked for overlaps. Riverd sends the client nothing about it: no notice and no intake form.","tags":["Changes"],"security":[{"apiKey":[]}],"x-riverd-data-class":"D2","x-riverd-access":"read and write keys only","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"client_id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_-]+$","description":"The client's id. To log a past session for someone new, add them first with POST /clients."},"service_id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_-]+$","description":"The service. Its length is the session's length unless end or duration_minutes says otherwise."},"start":{"anyOf":[{"type":"string","maxLength":100,"description":"A local time on the practice's clock (\"2026-10-09T14:00\"), an exact moment (\"2026-10-09T14:00:00-05:00\" or \"...Z\"), or an RFC 9557 string with the zone in brackets (\"2026-10-09T14:00:00-05:00[America/Chicago]\")."},{"type":"object","properties":{"date_time":{"type":"string","maxLength":100,"description":"A local time (\"2026-10-09T14:00\") or an exact moment with an offset."},"timezone":{"description":"The zone date_time is read in. Leave it out for the practice's zone. An IANA time zone name, such as \"America/Chicago\". Never an abbreviation (\"EST\", \"IST\") or an offset (\"+02:00\").","anyOf":[{"type":"string","minLength":1,"maxLength":64,"description":"An IANA time zone name, such as \"America/Chicago\". Never an abbreviation (\"EST\", \"IST\") or an offset (\"+02:00\")."},{"type":"null"}]}},"required":["date_time"],"additionalProperties":false,"description":"A moment in a named zone: {\"date_time\": \"2026-10-09T14:00\", \"timezone\": \"Asia/Tokyo\"}."}],"description":"When the session started. It must be in the past: book a session to come with POST /bookings. A moment. Without a zone it is read on the practice's clock. An offset that does not match the zone at that moment is refused, and so is a local time that does not exist or happens twice on a clock-change day (add the offset to pick one)."},"end":{"anyOf":[{"type":"string","maxLength":100,"description":"A local time on the practice's clock (\"2026-10-09T14:00\"), an exact moment (\"2026-10-09T14:00:00-05:00\" or \"...Z\"), or an RFC 9557 string with the zone in brackets (\"2026-10-09T14:00:00-05:00[America/Chicago]\")."},{"type":"object","properties":{"date_time":{"type":"string","maxLength":100,"description":"A local time (\"2026-10-09T14:00\") or an exact moment with an offset."},"timezone":{"description":"The zone date_time is read in. Leave it out for the practice's zone. An IANA time zone name, such as \"America/Chicago\". Never an abbreviation (\"EST\", \"IST\") or an offset (\"+02:00\").","anyOf":[{"type":"string","minLength":1,"maxLength":64,"description":"An IANA time zone name, such as \"America/Chicago\". Never an abbreviation (\"EST\", \"IST\") or an offset (\"+02:00\")."},{"type":"null"}]}},"required":["date_time"],"additionalProperties":false,"description":"A moment in a named zone: {\"date_time\": \"2026-10-09T14:00\", \"timezone\": \"Asia/Tokyo\"}."}],"description":"When the session ends, on the same day. Send this or duration_minutes, or neither to use the service's length (for a move: the session's current length). A moment. Without a zone it is read on the practice's clock. An offset that does not match the zone at that moment is refused, and so is a local time that does not exist or happens twice on a clock-change day (add the offset to pick one)."},"duration_minutes":{"type":"integer","minimum":1,"maximum":1439,"description":"How long the session is, in whole minutes. Send this or end, not both."},"notes":{"description":"Session notes. Private to the practice: the client never sees them.","type":"string","maxLength":20000},"dry_run":{"type":"boolean","description":"true: answer what would happen, and change nothing."}},"required":["client_id","service_id","start"]},"examples":{"past":{"summary":"Log a session that happened yesterday at 10am","value":{"client_id":"3f1c2a9e-7d4b-4c1e-9a8f-2b6d5e4c3a10","service_id":"8a7b6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d","start":"2026-10-08T10:00","duration_minutes":60}}}}}},"description":"Applies at once. The answer carries a change id (change.id): POST /changes/{changeId}/undo reverses it while undo_until has not passed. Emails and texts the change sends a client are held until change.client_messages_release_at (undo_until, or the session start when that is sooner), so an Undo before then stops them. Send dry_run: true to see what would happen without changing anything.","responses":{"200":{"description":"Done (or, with dry_run, what would be done).","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"data":{"type":"object","properties":{"booking_id":{},"backdated":{},"starts_at":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}]},"ends_at":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}]}},"description":"What the change made, such as the new client's id. Times are on the practice's clock."},"change":{"type":"object","properties":{"id":{"type":["string","null"],"description":"Send to POST /changes/{changeId}/undo to reverse the change."},"summary":{"type":"string"},"undo_until":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}],"description":"Until when POST /changes/{changeId}/undo can reverse it."},"client_messages_held":{"type":"integer","description":"Emails or texts to clients the change caused, waiting until client_messages_release_at. An upper bound: at release each is rebuilt from the practice as it is then, and some may not be sent (for example a contact the provider has not confirmed, a client who asked to stop emails, no intake form rule that applies, or the practice's client emails paused or at their daily limit)."},"client_messages_release_at":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}],"description":"When the held emails and texts go out. Usually undo_until; earlier when the session starts before undo_until. An Undo after this moment still reverses the change, but the messages have gone. Null when none are held."}}},"read_as":{"type":"object","description":"How Riverd read the times you sent, on the practice's clock. Present when the body had a time.","properties":{"start":{"$ref":"#/components/schemas/Moment"},"end":{"$ref":"#/components/schemas/Moment"}}}}},{"allOf":[{"$ref":"#/components/schemas/DryRunResult"},{"type":"object","properties":{"read_as":{"type":"object","description":"How Riverd read the times you sent, on the practice's clock. Present when the body had a time.","properties":{"start":{"$ref":"#/components/schemas/Moment"},"end":{"$ref":"#/components/schemas/Moment"}}}}}]}]}}}},"400":{"description":"The body, a field, dry_run or the Idempotency-Key header is not valid, or the body has a field this endpoint does not take. A time that cannot be read says why in its code: invalid_time, offset_zone_mismatch, nonexistent_local_time, ambiguous_local_time, invalid_timezone or timezone_mismatch; a booking at the second of two equal clock times (the clocks going back) is repeated_time.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The plan or the key does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Nothing with that id in your practice.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The practice changed since (the change was already undone, or a row was edited), or a request with the same Idempotency-Key is still running.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The change can't be made as asked (error.code says why), the practice has no time zone set (timezone_unset), or the Idempotency-Key was used for a different request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The API is turned off, or could not answer right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[\\x21-\\x7E]+$"},"description":"Optional. A unique value you choose for this request, like a UUID. Send the same value again and Riverd answers with the first result (header Idempotent-Replayed: true) instead of making the change twice. Honoured for 24 hours, per key (after that the same value starts a new request). The same value with a different request answers 422; while the first request is still running, 409."}]}},"/bookings/{bookingId}/move":{"post":{"operationId":"bookings_move","summary":"Move a session to a new time, at most 24 months ahead. Without end or duration_minutes it keeps its length. Refused with 409 overlap when the new time clashes with another session, a Google Calendar busy time or time off, unless allow_overlap is true. The client gets the usual notice.","tags":["Changes"],"security":[{"apiKey":[]}],"x-riverd-data-class":"D2","x-riverd-access":"read and write keys only","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"start":{"anyOf":[{"type":"string","maxLength":100,"description":"A local time on the practice's clock (\"2026-10-09T14:00\"), an exact moment (\"2026-10-09T14:00:00-05:00\" or \"...Z\"), or an RFC 9557 string with the zone in brackets (\"2026-10-09T14:00:00-05:00[America/Chicago]\")."},{"type":"object","properties":{"date_time":{"type":"string","maxLength":100,"description":"A local time (\"2026-10-09T14:00\") or an exact moment with an offset."},"timezone":{"description":"The zone date_time is read in. Leave it out for the practice's zone. An IANA time zone name, such as \"America/Chicago\". Never an abbreviation (\"EST\", \"IST\") or an offset (\"+02:00\").","anyOf":[{"type":"string","minLength":1,"maxLength":64,"description":"An IANA time zone name, such as \"America/Chicago\". Never an abbreviation (\"EST\", \"IST\") or an offset (\"+02:00\")."},{"type":"null"}]}},"required":["date_time"],"additionalProperties":false,"description":"A moment in a named zone: {\"date_time\": \"2026-10-09T14:00\", \"timezone\": \"Asia/Tokyo\"}."}],"description":"The new start, at most 24 months ahead. A moment. Without a zone it is read on the practice's clock. An offset that does not match the zone at that moment is refused, and so is a local time that does not exist or happens twice on a clock-change day (add the offset to pick one)."},"end":{"anyOf":[{"type":"string","maxLength":100,"description":"A local time on the practice's clock (\"2026-10-09T14:00\"), an exact moment (\"2026-10-09T14:00:00-05:00\" or \"...Z\"), or an RFC 9557 string with the zone in brackets (\"2026-10-09T14:00:00-05:00[America/Chicago]\")."},{"type":"object","properties":{"date_time":{"type":"string","maxLength":100,"description":"A local time (\"2026-10-09T14:00\") or an exact moment with an offset."},"timezone":{"description":"The zone date_time is read in. Leave it out for the practice's zone. An IANA time zone name, such as \"America/Chicago\". Never an abbreviation (\"EST\", \"IST\") or an offset (\"+02:00\").","anyOf":[{"type":"string","minLength":1,"maxLength":64,"description":"An IANA time zone name, such as \"America/Chicago\". Never an abbreviation (\"EST\", \"IST\") or an offset (\"+02:00\")."},{"type":"null"}]}},"required":["date_time"],"additionalProperties":false,"description":"A moment in a named zone: {\"date_time\": \"2026-10-09T14:00\", \"timezone\": \"Asia/Tokyo\"}."}],"description":"When the session ends, on the same day. Send this or duration_minutes, or neither to use the service's length (for a move: the session's current length). A moment. Without a zone it is read on the practice's clock. An offset that does not match the zone at that moment is refused, and so is a local time that does not exist or happens twice on a clock-change day (add the offset to pick one)."},"duration_minutes":{"type":"integer","minimum":1,"maximum":1439,"description":"How long the session is, in whole minutes. Send this or end, not both."},"allow_overlap":{"type":"boolean","description":"true makes the change even when the time overlaps another session, a busy time on the provider's Google Calendar, or the provider's time off. Ask the provider first. Without it, an overlap answers 409 overlap with the list of clashes, and nothing is changed."},"dry_run":{"type":"boolean","description":"true: answer what would happen, and change nothing."}},"required":["start"]},"examples":{"keep_length":{"summary":"Move to Friday at 9am, keeping the session's length","value":{"start":"2026-10-09T09:00"}},"new_end":{"summary":"Move and set a new end, as exact moments","value":{"start":"2026-10-09T09:00:00-05:00","end":"2026-10-09T10:30:00-05:00"}}}}}},"description":"Applies at once. The answer carries a change id (change.id): POST /changes/{changeId}/undo reverses it while undo_until has not passed. Emails and texts the change sends a client are held until change.client_messages_release_at (undo_until, or the session start when that is sooner), so an Undo before then stops them. Send dry_run: true to see what would happen without changing anything.","responses":{"200":{"description":"Done (or, with dry_run, what would be done).","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"data":{"type":"object","properties":{"booking_id":{},"starts_at":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}]},"ends_at":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}]}},"description":"What the change made, such as the new client's id. Times are on the practice's clock."},"change":{"type":"object","properties":{"id":{"type":["string","null"],"description":"Send to POST /changes/{changeId}/undo to reverse the change."},"summary":{"type":"string"},"undo_until":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}],"description":"Until when POST /changes/{changeId}/undo can reverse it."},"client_messages_held":{"type":"integer","description":"Emails or texts to clients the change caused, waiting until client_messages_release_at. An upper bound: at release each is rebuilt from the practice as it is then, and some may not be sent (for example a contact the provider has not confirmed, a client who asked to stop emails, no intake form rule that applies, or the practice's client emails paused or at their daily limit)."},"client_messages_release_at":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}],"description":"When the held emails and texts go out. Usually undo_until; earlier when the session starts before undo_until. An Undo after this moment still reverses the change, but the messages have gone. Null when none are held."}}},"read_as":{"type":"object","description":"How Riverd read the times you sent, on the practice's clock. Present when the body had a time.","properties":{"start":{"$ref":"#/components/schemas/Moment"},"end":{"$ref":"#/components/schemas/Moment"}}}}},{"allOf":[{"$ref":"#/components/schemas/DryRunResult"},{"type":"object","properties":{"read_as":{"type":"object","description":"How Riverd read the times you sent, on the practice's clock. Present when the body had a time.","properties":{"start":{"$ref":"#/components/schemas/Moment"},"end":{"$ref":"#/components/schemas/Moment"}}}}}]}]}}}},"400":{"description":"The body, a field, dry_run or the Idempotency-Key header is not valid, or the body has a field this endpoint does not take. A time that cannot be read says why in its code: invalid_time, offset_zone_mismatch, nonexistent_local_time, ambiguous_local_time, invalid_timezone or timezone_mismatch; a booking at the second of two equal clock times (the clocks going back) is repeated_time.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The plan or the key does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Nothing with that id in your practice.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The time overlaps another session, a busy time on the provider's Google Calendar or time off (code overlap), and nothing was changed. error.details.clashes lists each clash. Ask the provider, then send the request again with \"allow_overlap\": true to go ahead. Also 409 for a change that is already undone, or an Idempotency-Key still running.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OverlapError"}}}},"422":{"description":"The change can't be made as asked (error.code says why), the practice has no time zone set (timezone_unset), or the Idempotency-Key was used for a different request. A start more than 24 months ahead on the practice's clock is start_too_far: through the API a session starts at most 24 months ahead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The API is turned off, or could not answer right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"bookingId","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_-]+$"},"description":"The session's id (a booking's id)."},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[\\x21-\\x7E]+$"},"description":"Optional. A unique value you choose for this request, like a UUID. Send the same value again and Riverd answers with the first result (header Idempotent-Replayed: true) instead of making the change twice. Honoured for 24 hours, per key (after that the same value starts a new request). The same value with a different request answers 422; while the first request is still running, 409."}]}},"/bookings/{bookingId}/cancel":{"post":{"operationId":"bookings_cancel","summary":"Cancel a session, or decline a client's request that is still waiting (the client is told, with the reason). A session that has started, or that has money collected, is refused. The client gets the usual notice.","tags":["Changes"],"security":[{"apiKey":[]}],"x-riverd-data-class":"D2","x-riverd-access":"read and write keys only","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"description":"Why. For a client's own request that is still waiting (a request the provider has not accepted), it is declined and the client is shown this reason.","type":"string","maxLength":1000},"dry_run":{"type":"boolean","description":"true: answer what would happen, and change nothing."}}}}}},"description":"Applies at once. The answer carries a change id (change.id): POST /changes/{changeId}/undo reverses it while undo_until has not passed. Emails and texts the change sends a client are held until change.client_messages_release_at (undo_until, or the session start when that is sooner), so an Undo before then stops them. Send dry_run: true to see what would happen without changing anything.","responses":{"200":{"description":"Done (or, with dry_run, what would be done).","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"data":{"type":"object","properties":{"booking_id":{},"status":{}},"description":"What the change made, such as the new client's id. Times are on the practice's clock."},"change":{"type":"object","properties":{"id":{"type":["string","null"],"description":"Send to POST /changes/{changeId}/undo to reverse the change."},"summary":{"type":"string"},"undo_until":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}],"description":"Until when POST /changes/{changeId}/undo can reverse it."},"client_messages_held":{"type":"integer","description":"Emails or texts to clients the change caused, waiting until client_messages_release_at. An upper bound: at release each is rebuilt from the practice as it is then, and some may not be sent (for example a contact the provider has not confirmed, a client who asked to stop emails, no intake form rule that applies, or the practice's client emails paused or at their daily limit)."},"client_messages_release_at":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}],"description":"When the held emails and texts go out. Usually undo_until; earlier when the session starts before undo_until. An Undo after this moment still reverses the change, but the messages have gone. Null when none are held."}}},"read_as":{"type":"object","description":"How Riverd read the times you sent, on the practice's clock."}}},{"$ref":"#/components/schemas/DryRunResult"}]}}}},"400":{"description":"The body, a field, dry_run or the Idempotency-Key header is not valid, or the body has a field this endpoint does not take. A time that cannot be read says why in its code: invalid_time, offset_zone_mismatch, nonexistent_local_time, ambiguous_local_time, invalid_timezone or timezone_mismatch; a booking at the second of two equal clock times (the clocks going back) is repeated_time.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The plan or the key does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Nothing with that id in your practice.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The practice changed since (the change was already undone, or a row was edited), or a request with the same Idempotency-Key is still running.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The change can't be made as asked (error.code says why), the practice has no time zone set (timezone_unset), or the Idempotency-Key was used for a different request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The API is turned off, or could not answer right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"bookingId","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_-]+$"},"description":"The session's id (a booking's id)."},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[\\x21-\\x7E]+$"},"description":"Optional. A unique value you choose for this request, like a UUID. Send the same value again and Riverd answers with the first result (header Idempotent-Replayed: true) instead of making the change twice. Honoured for 24 hours, per key (after that the same value starts a new request). The same value with a different request answers 422; while the first request is still running, 409."}]}},"/clients/{clientId}/confirm-contact":{"post":{"operationId":"clients_confirm_contact_api","summary":"Confirm a client's email or phone number on file, after the provider says it is right. Until then Riverd sends that client nothing at a detail added through the API. Undo puts it back to unconfirmed.","tags":["Changes"],"security":[{"apiKey":[]}],"x-riverd-data-class":"D2","x-riverd-access":"read and write keys only","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"description":"true confirms the client's email on file. Read it back to the provider first (GET /clients/{clientId}), and send this only if the provider says it is right.","type":"boolean"},"phone":{"description":"true confirms the client's phone number on file. Read it back to the provider first, and send this only if the provider says it is right.","type":"boolean"},"dry_run":{"type":"boolean","description":"true: answer what would happen, and change nothing."}},"additionalProperties":false},"examples":{"email":{"summary":"The provider said the email on file is right","value":{"email":true}},"both":{"summary":"The provider said the email and the phone number are right","value":{"email":true,"phone":true}}}}}},"description":"Applies at once. The answer carries a change id (change.id): POST /changes/{changeId}/undo reverses it while undo_until has not passed. Emails and texts the change sends a client are held until change.client_messages_release_at (undo_until, or the session start when that is sooner), so an Undo before then stops them. Send dry_run: true to see what would happen without changing anything.","responses":{"200":{"description":"Done (or, with dry_run, what would be done).","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"data":{"type":"object","properties":{"client_id":{},"confirmed":{"type":"array","items":{"enum":["email","phone"]},"description":"The details this request confirmed."},"contact":{"type":"object","description":"Whether the client's email and phone number are confirmed after this change. To confirm one later, ask the provider, then send POST /clients/{clientId}/confirm-contact.","properties":{"email":{"anyOf":[{"enum":["confirmed","unconfirmed"]},{"type":"null"}],"description":"confirmed: Riverd can send to it. unconfirmed: Riverd sends the client nothing at it until the provider confirms it. null: none on file."},"phone":{"anyOf":[{"enum":["confirmed","unconfirmed"]},{"type":"null"}],"description":"confirmed: Riverd can send to it. unconfirmed: Riverd sends the client nothing at it until the provider confirms it. null: none on file."}}}},"description":"What the change made, such as the new client's id. Times are on the practice's clock."},"change":{"type":"object","properties":{"id":{"type":["string","null"],"description":"Send to POST /changes/{changeId}/undo to reverse the change."},"summary":{"type":"string"},"undo_until":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}],"description":"Until when POST /changes/{changeId}/undo can reverse it."},"client_messages_held":{"type":"integer","description":"Emails or texts to clients the change caused, waiting until client_messages_release_at. An upper bound: at release each is rebuilt from the practice as it is then, and some may not be sent (for example a contact the provider has not confirmed, a client who asked to stop emails, no intake form rule that applies, or the practice's client emails paused or at their daily limit)."},"client_messages_release_at":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}],"description":"When the held emails and texts go out. Usually undo_until; earlier when the session starts before undo_until. An Undo after this moment still reverses the change, but the messages have gone. Null when none are held."}}},"read_as":{"type":"object","description":"How Riverd read the times you sent, on the practice's clock."}}},{"$ref":"#/components/schemas/DryRunResult"}]}}}},"400":{"description":"The body, a field, dry_run or the Idempotency-Key header is not valid, or the body has a field this endpoint does not take. A time that cannot be read says why in its code: invalid_time, offset_zone_mismatch, nonexistent_local_time, ambiguous_local_time, invalid_timezone or timezone_mismatch; a booking at the second of two equal clock times (the clocks going back) is repeated_time.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The plan or the key does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Nothing with that id in your practice.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The practice changed since (the change was already undone, or a row was edited), or a request with the same Idempotency-Key is still running.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The change can't be made as asked (error.code says why), the practice has no time zone set (timezone_unset), or the Idempotency-Key was used for a different request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The API is turned off, or could not answer right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"clientId","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_-]+$"},"description":"The client's id."},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[\\x21-\\x7E]+$"},"description":"Optional. A unique value you choose for this request, like a UUID. Send the same value again and Riverd answers with the first result (header Idempotent-Replayed: true) instead of making the change twice. Honoured for 24 hours, per key (after that the same value starts a new request). The same value with a different request answers 422; while the first request is still running, 409."}]}},"/intake-forms":{"get":{"operationId":"intake_forms_list","summary":"Your intake forms that can be sent, oldest first, each with its id and name only. Archived forms are left out. A form's id is the form_id that POST /bookings/{bookingId}/intake takes.","tags":["Read"],"security":[{"apiKey":[]}],"x-riverd-data-class":"D1","x-riverd-access":"any key","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{},"name":{}}}},"next_cursor":{"type":["string","null"]}}}}}},"400":{"description":"A parameter is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The plan or the key does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The practice has no valid time zone (code timezone_unset), so its times cannot be read or sent. Set it in Riverd under Settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The API is turned off, or could not answer right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"next_cursor from the previous page."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}]}},"/bookings/{bookingId}/intake":{"post":{"operationId":"intake_send_held","summary":"Send an intake form to the session's client. The email with the form's link waits until the change can no longer be undone, so an Undo stops it.","tags":["Changes"],"security":[{"apiKey":[]}],"x-riverd-data-class":"D2","x-riverd-access":"read and write keys only","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"form_id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_-]+$","description":"The intake form to send: a form's id from GET /intake-forms, which lists exactly the forms that can be sent. The client is the session's client."},"dry_run":{"type":"boolean","description":"true: answer what would happen, and change nothing."}},"required":["form_id"]}}}},"description":"Applies at once. The answer carries a change id (change.id): POST /changes/{changeId}/undo reverses it while undo_until has not passed. Emails and texts the change sends a client are held until change.client_messages_release_at (undo_until, or the session start when that is sooner), so an Undo before then stops them. Send dry_run: true to see what would happen without changing anything.","responses":{"200":{"description":"Done (or, with dry_run, what would be done).","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"data":{"type":"object","properties":{"intake_send_id":{},"booking_id":{},"form_name":{}},"description":"What the change made, such as the new client's id. Times are on the practice's clock."},"change":{"type":"object","properties":{"id":{"type":["string","null"],"description":"Send to POST /changes/{changeId}/undo to reverse the change."},"summary":{"type":"string"},"undo_until":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}],"description":"Until when POST /changes/{changeId}/undo can reverse it."},"client_messages_held":{"type":"integer","description":"Emails or texts to clients the change caused, waiting until client_messages_release_at. An upper bound: at release each is rebuilt from the practice as it is then, and some may not be sent (for example a contact the provider has not confirmed, a client who asked to stop emails, no intake form rule that applies, or the practice's client emails paused or at their daily limit)."},"client_messages_release_at":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}],"description":"When the held emails and texts go out. Usually undo_until; earlier when the session starts before undo_until. An Undo after this moment still reverses the change, but the messages have gone. Null when none are held."}}},"read_as":{"type":"object","description":"How Riverd read the times you sent, on the practice's clock."}}},{"$ref":"#/components/schemas/DryRunResult"}]}}}},"400":{"description":"The body, a field, dry_run or the Idempotency-Key header is not valid, or the body has a field this endpoint does not take. A time that cannot be read says why in its code: invalid_time, offset_zone_mismatch, nonexistent_local_time, ambiguous_local_time, invalid_timezone or timezone_mismatch; a booking at the second of two equal clock times (the clocks going back) is repeated_time.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The plan or the key does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Nothing with that id in your practice.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The practice changed since (the change was already undone, or a row was edited), or a request with the same Idempotency-Key is still running.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The change can't be made as asked (error.code says why), the practice has no time zone set (timezone_unset), or the Idempotency-Key was used for a different request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The API is turned off, or could not answer right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"bookingId","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_-]+$"},"description":"The session's id (a booking's id)."},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[\\x21-\\x7E]+$"},"description":"Optional. A unique value you choose for this request, like a UUID. Send the same value again and Riverd answers with the first result (header Idempotent-Replayed: true) instead of making the change twice. Honoured for 24 hours, per key (after that the same value starts a new request). The same value with a different request answers 422; while the first request is still running, 409."}]}},"/time-off/{timeOffId}":{"delete":{"operationId":"time_off_remove","summary":"Remove a block of time off, by the id GET /time-off gives. Those times open again on your booking page.","tags":["Changes"],"security":[{"apiKey":[]}],"x-riverd-data-class":"D2","x-riverd-access":"read and write keys only","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"dry_run":{"type":"boolean","description":"true: answer what would happen, and change nothing."}},"additionalProperties":false}}}},"description":"Applies at once. The answer carries a change id (change.id): POST /changes/{changeId}/undo reverses it while undo_until has not passed. Emails and texts the change sends a client are held until change.client_messages_release_at (undo_until, or the session start when that is sooner), so an Undo before then stops them. Send dry_run: true to see what would happen without changing anything.","responses":{"200":{"description":"Done (or, with dry_run, what would be done).","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"data":{"type":"object","properties":{"time_off_id":{},"start_date":{"anyOf":[{"$ref":"#/components/schemas/Date"},{"type":"null"}]},"end_date":{"anyOf":[{"$ref":"#/components/schemas/Date"},{"type":"null"}]},"is_all_day":{},"start_time":{"anyOf":[{"$ref":"#/components/schemas/TimeOfDay"},{"type":"null"}]},"end_time":{"anyOf":[{"$ref":"#/components/schemas/EndTimeOfDay"},{"type":"null"}]}},"description":"What the change made, such as the new client's id. Times are on the practice's clock."},"change":{"type":"object","properties":{"id":{"type":["string","null"],"description":"Send to POST /changes/{changeId}/undo to reverse the change."},"summary":{"type":"string"},"undo_until":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}],"description":"Until when POST /changes/{changeId}/undo can reverse it."},"client_messages_held":{"type":"integer","description":"Emails or texts to clients the change caused, waiting until client_messages_release_at. An upper bound: at release each is rebuilt from the practice as it is then, and some may not be sent (for example a contact the provider has not confirmed, a client who asked to stop emails, no intake form rule that applies, or the practice's client emails paused or at their daily limit)."},"client_messages_release_at":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}],"description":"When the held emails and texts go out. Usually undo_until; earlier when the session starts before undo_until. An Undo after this moment still reverses the change, but the messages have gone. Null when none are held."}}},"read_as":{"type":"object","description":"How Riverd read the times you sent, on the practice's clock."}}},{"$ref":"#/components/schemas/DryRunResult"}]}}}},"400":{"description":"The body, a field, dry_run or the Idempotency-Key header is not valid, or the body has a field this endpoint does not take. A time that cannot be read says why in its code: invalid_time, offset_zone_mismatch, nonexistent_local_time, ambiguous_local_time, invalid_timezone or timezone_mismatch; a booking at the second of two equal clock times (the clocks going back) is repeated_time.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The plan or the key does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Nothing with that id in your practice.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The practice changed since (the change was already undone, or a row was edited), or a request with the same Idempotency-Key is still running.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The change can't be made as asked (error.code says why), the practice has no time zone set (timezone_unset), or the Idempotency-Key was used for a different request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The API is turned off, or could not answer right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"timeOffId","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_-]+$"},"description":"The id of the block of time off."},{"name":"dry_run","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]},"description":"true: answer what would happen, and change nothing. The same as \"dry_run\": true in a JSON body, which stays the main way; sent both ways, the two must agree."},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[\\x21-\\x7E]+$"},"description":"Optional. A unique value you choose for this request, like a UUID. Send the same value again and Riverd answers with the first result (header Idempotent-Replayed: true) instead of making the change twice. Honoured for 24 hours, per key (after that the same value starts a new request). The same value with a different request answers 422; while the first request is still running, 409."}]}},"/key":{"get":{"operationId":"key_get","summary":"The key you are calling with (its name, access level, expiry, or none for a key that never expires, and whether it may start AI on notes), your practice's time zone, and today's date in it. Every time the API reads or sends is on that clock.","tags":["Key"],"security":[{"apiKey":[]}],"x-riverd-data-class":"D1","x-riverd-access":"any key","description":"timezone is the practice's IANA time zone: every time the API sends is on that clock, and a time you send without a zone or offset is read on it. today is the practice's date now. expires_at is null for a key that never expires (the owner chose Never when making it).","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"name":{},"access_level":{},"ai_on_notes":{},"created_at":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}]},"expires_at":{"anyOf":[{"$ref":"#/components/schemas/Moment"},{"type":"null"}],"description":"When the key stops working, or null for a key that never expires."},"timezone":{"$ref":"#/components/schemas/TimeZone"},"today":{"anyOf":[{"$ref":"#/components/schemas/Date"},{"type":"null"}]}}}}}}}},"401":{"description":"Missing, invalid, expired or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The plan or the key does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The practice has no valid time zone (code timezone_unset), so its times cannot be read or sent. Set it in Riverd under Settings.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The API is turned off, or could not answer right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/changes/{changeId}/undo":{"post":{"operationId":"changes_undo","summary":"Undo a change made through the API, using the change id its response gave. Works while the change can still be undone; client messages it caused that have not gone out are stopped.","tags":["Changes"],"security":[{"apiKey":[]}],"x-riverd-data-class":"D1","x-riverd-access":"read and write keys only","description":"Reverses a change made through the API (any read-write key of the practice may undo it), while its undo_until has not passed. A change made in Riverd itself is never undone through the API. Client messages the change caused that have not gone out are stopped.","responses":{"200":{"description":"Undone.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"change_id":{"type":"string"},"undone":{"const":true},"client_messages_stopped":{"type":"integer"},"client_messages_already_sent":{"type":"integer"}}}}}}}},"400":{"description":"The body, a field, dry_run or the Idempotency-Key header is not valid, or the body has a field this endpoint does not take. A time that cannot be read says why in its code: invalid_time, offset_zone_mismatch, nonexistent_local_time, ambiguous_local_time, invalid_timezone or timezone_mismatch; a booking at the second of two equal clock times (the clocks going back) is repeated_time.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, expired or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The plan or the key does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Nothing with that id in your practice.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The practice changed since (the change was already undone, or a row was edited), or a request with the same Idempotency-Key is still running.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"It is too late to undo this change (code undo_expired).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The change can't be made as asked (error.code says why), the practice has no time zone set (timezone_unset), or the Idempotency-Key was used for a different request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The API is turned off, or could not answer right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"changeId","in":"path","required":true,"schema":{"type":"string","minLength":1,"maxLength":64},"description":"The change id from the answer of the change to undo (change.id)."},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[\\x21-\\x7E]+$"},"description":"Optional. A unique value you choose for this request, like a UUID. Send the same value again and Riverd answers with the first result (header Idempotent-Replayed: true) instead of making the change twice. Honoured for 24 hours, per key (after that the same value starts a new request). The same value with a different request answers 422; while the first request is still running, 409."}]}},"/ai/soap-draft":{"post":{"operationId":"ai_soap_draft","summary":"Draft a SOAP note from one session's notes with Reed · AI. Only for a key made with 'Can start Reed · AI on notes' switched on. Uses AI credits. Nothing is saved.","tags":["Reed · AI"],"security":[{"apiKey":[]}],"x-riverd-data-class":"D3","x-riverd-access":"any key","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"booking_id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_-]+$"}},"required":["booking_id"],"additionalProperties":false}}}},"responses":{"200":{"description":"The draft. Nothing is saved in Riverd.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"booking_id":{"type":"string"},"soap_note":{"type":"string","description":"The draft as plain text: four sections, each under its own heading line (Subjective:, Objective:, Assessment:, Plan:), the same text the SOAP button in Riverd puts in the notes box."}}}}}}}},"401":{"description":"Missing, invalid, expired or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The practice has used this month's AI credits (code ai_credits_exhausted). A draft uses AI credits like the SOAP button in Riverd.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The plan or the key does not allow this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"That session has no notes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The session's notes are longer than 10,000 characters, the most one draft takes (code notes_too_long, refused before Reed runs), or are not session notes (code not_session_notes). Nothing was drafted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The API is turned off, or could not answer right now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}