Skip to content

Capabilities

Capabilities let a Resource do more than store records.

They turn a Resource into something Diagonal can treat in a special way: users who can log in, people who can receive notifications, invoice-like records, payment customers, calendar events, and more.

Capabilities are configured from the Capabilities area of a Resource. Each capability has settings that usually point to Fields on that same Resource.

Diagonal validates the selected Field types and shows configuration issues beside an incomplete Capability. Some referenced Fields are system-managed; their values are written by Capability hooks rather than ordinary forms or workflows.

Data Protection and Authorization

Lockable

Lockable prevents selected Fields from changing after an existing Record matches a filter. Add one or more Lock rules, choose When records match, and select at least one Locked field. The filter is evaluated against the existing Record, so a client can't unlock protected values by changing the same request payload.

Role and Permission

Each Workspace has one Role Resource and one Permission Resource. These Capabilities make their Records the workspace authorization catalog. Diagonal manages canonical slugs, system-role flags, permission assignments, caches, and deletion restrictions.

Don't enable a second Role or Permission Capability in the same Workspace. System Roles and Permissions can't be repurposed or deleted, assigned Roles must be removed from users before deletion, and assigned Permissions must be removed from non-system Roles first.

Identity and Communication

Use these capabilities when records represent people or destinations your app needs to recognize, authenticate, or contact.

Authenticatable

Authenticatable Resources represent people who can log in.

Use this for user-type Resources like Customers, Students, Employees, Vendors, Members, or Partners. Once enabled and configured, Records in that Resource can sign in with email/password. Builders can also enable emailed Magic Links. When two-factor authentication is enabled, every password, Magic Link, or password-reset sign-in also asks for a code sent by SMS. SMS is not a standalone login method. Before the first code goes to a phone number, the person must agree to receive codes by text message. The consent step shows the carrier rates notice and links to the SMS Terms and the Privacy Policy. Initial setup verifies a dedicated MFA number and generates 10 single-use backup codes before sign-in completes. A backup code replaces the SMS code when the person cannot receive a text message. The backup codes page is at /auth/resource/{resourceId}/two-factor/recovery-codes.

Setting Field type Required Purpose
Email field Email Yes Identifies the user during login.
Password field Password Yes Stores the user's password credential. Individual Records may remain empty until the user sets a password.
Roles field Relation No System-managed Role assignments for Records in this Resource.
Authentication Enabled field Boolean No Turns sign-in off for a Record when the field is No. The Record cannot sign in by any method, and its open sessions and API tokens stop working on their next request. Records sign in when the field is empty or not mapped.
Enable Two-Factor Authentication Boolean No Requires an SMS code after the password or Magic Link before the person is signed in. A person without a verified MFA number must complete setup before sign-in finishes. Off by default.
Enable Magic Link Login Boolean No Offers Email me a sign-in link. Off by default.
Login Page Page No Custom Page containing the login form. The built-in form is used when empty.
Login Redirect Page No Page opened after successful sign-in.

Multiple user types

A workspace can have more than one authenticatable Resource. For example, a school portal might have Students and Teachers as separate Resources with different pages, permissions, and policies.

When Enable Two-Factor Authentication is on, each user sets up a separate SMS number at their next sign-in. The number is not a Record field. To change the MFA number, the user must verify their existing factor or use a backup code, then verify the new number. The backup codes page links to this change flow.

Notifiable

Notifiable Resources can receive messages.

Use this when records represent people or destinations that should receive email, SMS, or other notifications from workflows.

Setting Field type Required Purpose
Email field Email Yes Email address used for notifications.
Phone field Phone or text No Phone number used for SMS or phone-based notifications.

Good examples include Customers receiving invoice reminders, Employees receiving task updates, or Students receiving course announcements.

Payments and Commerce

Use these capabilities together when your app needs invoices, orders, line items, payment records, refunds, or customer payment profiles.

Most payment setups use several Resources together:

Resource Capability Role in the payment model
Customer or Account Payer The party responsible for paying.
Invoice or Order Payable The thing money is owed for.
Invoice Line Item or Order Item Itemizable A billable item connected to the payable.
Discount, Fee, or Adjustment Payable Adjustment A credit or charge connected to the payable.
Payment or Charge Payment A transaction against the payable.
Refund Refund Money returned against a payment.

Payer

Payer Resources represent payment customers.

Use this when a Resource should act as the party paying for something. Diagonal can manage payment-customer identity for these records when the payment integration is available.

Setting Field type Required Purpose
Email field Email No Email used for payment customer records.
Name field Text No Display name used for payment customer records.

Common Payer Resources include Customers, Accounts, Organizations, or Members.

Payable

Payable Resources behave like invoices or bills.

Use this when records need to track totals, amounts paid, outstanding balances, currency, and payment status.

Setting Field type Required Purpose
Status field Select Yes Tracks states like draft, open, paid, partially paid, overpaid, void, or written off.
Amount total field Money Yes Total amount due.
Amount paid field Money Yes Amount collected so far.
Currency field Text Yes Currency for the payable record.
Amount subtotal field Money No Total before payable adjustments are applied.
Amount adjustment field Money No Net adjustment amount after credits and charges.
Amount credit adjustment field Money No Total active credit adjustments, such as discounts.
Amount charge adjustment field Money No Total active charge adjustments, such as fees.
Amount written off field Money No Total active write-offs.
Amount outstanding field Money No Remaining collectible balance after payments and write-offs.
Amount overpaid field Money No Amount paid above the total.
Paid at field Datetime No Timestamp for when the payable became paid.
Payer field Relation No Links the payable to the payer responsible for it.
Accounting entity field Wallet accounting entity No The accounting entity that owns the invoice. Set it when you create the invoice, or leave it empty to use the default entity. It cannot change after that.

Payable records often work with Itemizable, Payable Adjustment, Payment, and Refund Resources. For example, an Invoice can have many Invoice Line Items, many Discounts or Fees, many Payments, and many Refunds through the payment records.

Itemizable

Itemizable Resources represent line items attached to a Payable Resource.

Use this for invoice line items, order items, subscription items, or billable services.

Setting Field type Required Purpose
Parent relation field Relation Yes Connects the line item to its parent payable record.
Description field Text Yes Describes the item or service.
Unit price field Money Yes Price for one unit.
Total field Money Yes Total for the line item.
Quantity field Number No Quantity multiplied by unit price.
Price field Relation No Links the line item to the Price it came from. Must point to a Resource that has the Price capability enabled.

When quantity is available, Diagonal can calculate line item totals and keep the parent Payable record in sync.

When a line item references a Price and its unit price or description are left blank, Diagonal snapshots them from the Price on save: the unit price is copied from the Price's amount, and the description from the name of the product the Price belongs to. The line item only needs the Price relation — the product is reached through it. Snapshotting happens at save time, so editing the Price later never changes line items that were already created.

Payable Adjustment

Payable Adjustment Resources store credits and charges against a Payable Resource.

Use this for invoice discounts, coupons, write-offs, processing fees, late fees, surcharges, or other invoice-level adjustments. Each adjustment belongs to a payable record. Diagonal recalculates the payable when active adjustments are created, updated, archived, or restored.

Setting Field type Required Purpose
Payable field Relation Yes Links the adjustment to the payable record being adjusted.
Kind field Select Yes Whether the adjustment is a credit, charge, or write_off.
Status field Select Yes Adjustment state. Supported values are active and void.
Calculation type field Select Yes How the amount is calculated. Supported values are fixed_amount and percentage.
Amount field Money Yes Applied adjustment amount. Diagonal overwrites this field with the final calculated amount (see order of operations below).
Fixed amount field Money No Amount to apply when the calculation type is fixed_amount.
Percent field Number No Percentage to apply when the calculation type is percentage.
Description field Text Yes Human-readable explanation shown in invoice or internal views. Used as the adjustment's display value.
Effective at field Date or Datetime No Accounting date for the adjustment. If it is empty or not mapped, use the record creation date.

Order of operations

Diagonal applies adjustments in a fixed order, regardless of the order the adjustment records were created:

  1. Subtotal — Diagonal sums the line items into the payable subtotal.
  2. Credits — All active credits are applied against the subtotal. Percentage credits are calculated from the subtotal, and fixed-amount credits use their fixed value. Credits are capped so they cannot reduce the subtotal below zero.
  3. Charges — All active charges are then applied against the post-credit basis (subtotal minus credits). Percentage charges are calculated from that reduced basis, and fixed-amount charges use their fixed value.
  4. Write-offs — Active fixed adjustments with kind write_off reduce the collectible balance after payments. They do not change the payable total or paid amount.

Because the order is fixed in this sequence, the order in which adjustment records are created does not change the result, and reordering adjustments of the same kind does not change the total.

Diagonal overwrites each adjustment's Amount field with its final calculated amount. For a percentage charge this is the post-credit amount, not the gross amount, so the payable total is always the clean sum of every adjustment's Amount field.

Worked example. On a $100 subtotal with a 20% credit and a 10% charge:

  • Credit: 20% × $100 = $20, leaving a post-credit basis of $80.
  • Charge: 10% × $80 = $8 (not $10, because the charge is calculated after the credit). The charge's Amount field is stored as $8.
  • Payable total: $100 − $20 + $8 = $88.

Adjustment records are normal Records. In practice, a page or workflow creates an adjustment with the standard data creation flow, using fields like payable, kind, status, calculation type, fixed amount, or percent. Because this goes through normal record creation, capability hooks, validation, relations, and policies stay in one path.

Use the Payable: Write Off workflow task for write-offs. Leave the amount empty to write off the full outstanding balance. A partial write-off keeps the invoice open or partially_paid. A full write-off sets the invoice to written_off and does not set Paid at. Set the adjustment status to void to cancel a write-off and restore its balance while keeping the audit record.

You can also create a write-off through the Record API for the Payable Adjustment Resource. Use the configured field keys. Do not send the system-managed amount field.

{
  "invoice": { "id": "<payable-record-id>" },
  "kind": "write_off",
  "status": "active",
  "calculation_type": "fixed_amount",
  "fixed_amount": { "amount": 2500, "currency": "usd" },
  "description": "Bad debt balance",
  "effective_at": "2026-09-25"
}

The Payable Adjustment capability hook validates this payload. It requires a positive fixed amount in the payable currency. It permits active write-offs only for open or partially paid payables. It rejects a write-off while a payment is pending or processing. Payable recalculation rejects active write-offs above the remaining collectible balance. The Record API operation rolls back when any of these checks fail.

Diagonal also stores internal adjustment totals and allocation details in payable metadata. This metadata is useful for internal tracking, reporting, and future tax behavior without requiring app builders to expose every calculated field to end users.

Payment

Payment Resources store individual payment transactions.

Use this when you need records for charges, manual payments, checkout payments, failed payments, or payment history against a payable record.

Setting Field type Required Purpose
Payable field Relation Yes Links the payment to the payable record.
Amount field Money Yes Amount of the payment.
Currency field Text Yes Currency for the payment.
Method field Text Yes Payment method, such as manual or checkout.
Status field Text Yes Payment state, such as succeeded, failed, refunded, or partially refunded.
Payer field Relation No Links the payment to the payer.
Failure reason field Text No Explains why a payment failed.
Refunded amount field Money No Tracks how much has been refunded.
Diagonal fee field Money Yes The Diagonal fee Stripe collected on the payment. Set by the system.
Diagonal fee refunded field Money Yes The part of the Diagonal fee returned to you. Set by the system.
Net Diagonal fee field Money Yes The Diagonal fee minus the refunded fee. Set by the system.
Notes field Text No Internal payment notes.
Paid at field Datetime No Timestamp for successful payment.

Payment records can update the connected Payable record so totals, balances, and statuses stay current.

Map the three Diagonal fee settings to stored Money Fields. You can query, filter, total, and export them like other Money Fields. Only Diagonal writes them:

  • For each successful Stripe payment, Diagonal reads the actual fee from Stripe, usually within a minute of the payment.
  • An empty value means the fee is not known yet or the payment is not a Stripe payment. Manual payments have no Diagonal fee.
  • A refund to your customer does not change the Diagonal fee. The refunded fee Field changes only when Stripe returns part of the fee to you.
  • The Diagonal fee is not deducted from the payment amount or the invoice. It is separate from the card processing fee your customers can be charged at checkout.

Payer Balance Entry

Payer Balance Entry Resources hold immutable credits and debits. The Payer's Balance field shows the total. Checkout offers only the share held by the invoice's accounting entity. Credit cannot pay an invoice of another entity.

Setting Field type Required Purpose
Payer field Relation Yes Links the entry to its payer.
Direction field Select Yes Credit or debit.
Amount field Money Yes Positive amount to add or subtract.
Accounting entity field Wallet accounting entity No Entity whose balance the entry changes. Set at creation. A linked payment or change invoice supplies its entity. Otherwise, the entry uses the selected entity or the default entity.
Effective at field Date or Datetime No Date the entry takes effect. Defaults to creation time.
Payment field Relation No System-managed payment link for checkout debits and refund credits.
Source payable field Relation No System-managed change invoice link.

A manual credit requires an active entity. A manual debit can clear credit held by an inactive entity. A debit cannot exceed that entity's available balance. Refunds return credit to the original invoice's entity. With one entity or no entity, checkout uses the total as before. A balance imported directly on the payer belongs to the default entity.

Payment Method

Payment Method Resources store saved Stripe payment methods for a Payer. The Payer relation, type, and display name are required; optional system-managed Fields can hold card brand, last four digits, expiration month/year, or bank name. Apps display these safe summary values—never raw card or bank credentials.

Refund

Refund Resources store refunds against Payment records.

Use this when you need to track partial refunds, full refunds, pending refunds, failed refunds, and refund reasons.

Setting Field type Required Purpose
Payment field Relation Yes Links the refund to the payment being refunded.
Amount field Money Yes Amount being refunded.
Currency field Text Yes Currency for the refund.
Status field Text Yes Refund state, such as succeeded, pending, or failed.
Failure reason field Text No Stores the reason when a refund fails.
Refunded at field Datetime Yes Timestamp for the refund.
Reason field Text No Reason for the refund.

Successful refund records can update the related Payment record's refunded amount and status.

Catalog and Pricing

Use these capabilities when your app sells things and needs a catalog of products with their own pricing.

Product and Price work as a pair: a Product is the sellable item, and a Price is a specific amount and billing cadence for that product. One product can have many prices, such as a monthly and a yearly option.

Resource Capability Role in the catalog model
Product, Plan, or Service Product The sellable catalog item.
Price, Rate, or Plan Price Price An amount and billing cadence for a product.

Product

Product Resources represent sellable catalog items that prices and subscriptions can reference.

Use this for products, plans, services, packages, or any sellable item that needs its own catalog entry.

Setting Field type Required Purpose
Name field Text Yes Display name of the product.
Description field Text No Longer description of the product.

Price

Price Resources represent the pricing records for a product, each with an amount and a billing cadence.

Use this when a product needs one or more prices, such as a one-time charge, a monthly subscription, or an annual plan. A single product can have many prices.

Setting Field type Required Purpose
Product field Relation Yes Links the price to its product. Must point to a Resource that has the Product capability enabled.
Amount field Money Yes The price amount.
Recurrence field Select Yes Billing cadence. Supported values are one_time, monthly, quarterly, semi_annual, and yearly.

When you connect a Price to its product, Diagonal checks that the related Resource actually has the Product capability enabled, so catalog relationships stay valid.

After a Subscription Item uses a Price, you cannot change the recurrence of that Price. This includes canceled and archived items. To change a recurrence, create a new Price and move subscribers to it. You can still change the amount and other fields.

Subscriptions

Use these capabilities when your app sells recurring agreements that bill on a schedule and generate invoices each period.

Subscription and Subscription Item work as a pair: a Subscription is the recurring agreement on one billing cadence, and each Subscription Item is one priced line on that agreement. The catalog (Product and Price) supplies what is being billed.

Resource Capability Role in the subscription model
Subscription, Membership, or Plan Subscription The recurring agreement that bills each period.
Subscription Item, Line, or Add-on Subscription Item One priced line on the subscription.

Subscription

Subscription Resources represent a recurring agreement that generates a payable invoice each billing period.

Setting Field type Required Purpose
Payer field Relation Yes Links the subscription to the payer responsible for it.
Status field Select Yes Lifecycle state, managed by the system — see below. Supported values are draft, active, past_due, lapsed, paused, and canceled.
Collection method field Select Yes How payment is collected. Supported values are send_invoice and charge_automatically.
Interval field Select No The subscription's billing cadence. Supported values are monthly, quarterly, semi_annual, and yearly. The system freezes this from the first recurring item — see below.
Started at field Datetime Yes When the subscription began. System-managed.
Current period start field Datetime Yes Start of the active billing period. System-managed.
Current period end field Datetime Yes End of the active billing period. System-managed.
Next invoice at field Datetime Yes When the next invoice is due to be generated; drives the billing cycle. System-managed.
Canceled at field Datetime No When the subscription was canceled. System-managed.
Cancel at period end field Boolean No Whether the subscription cancels at the end of the current period rather than immediately. Set through the Subscription: Cancel task.
Latest invoice field Relation No Points to the most recently generated payable invoice. System-managed.
Grace period days field Number No How many days past an invoice's due date this subscription stays billable before lapsing. Falls back to the subscription_grace_period_days workspace field, then to no grace.
Accounting entity field Wallet accounting entity No The accounting entity that owns the subscription and each invoice that it generates. Set it when you create the subscription, or leave it empty to use the default entity. It cannot change after that.

The status is machine-managed

The subscription's status is never edited by hand — like a Payable's totals, the system owns it, and it changes through exactly two channels:

  1. Derivation from invoices. The billable statuses recompute automatically from the latest invoice on every billing run and every payment: active while the invoice is paid (or open but not yet past its due day), past_due once it is unpaid past the due day but within the grace period, and lapsed once the grace period is exhausted. Because payments re-run this derivation immediately, a subscription can never look paid while its invoice is not, and an unpaid renewal always surfaces in past-due reporting. A lapsed subscription keeps billing each period until it is explicitly paused or canceled.
  2. Lifecycle tasks. The remaining states — draft (created but not started), paused, and canceled — are only ever set by the subscription workflow tasks:
Task What it does
Subscription: Create The one-step signup: creates the subscription record for a payer, one active item per selected price, activates it, and by default generates the first invoice immediately.
Subscription: Activate Starts a draft subscription so the billing runner generates its first invoice.
Subscription: Generate Invoice Generates the payable invoice for the current billing period. At most one invoice ever exists per period, even if run repeatedly.
Subscription: Cancel Cancels immediately, or raises the cancel-at-period-end flag so the current paid period runs out first.
Subscription: Pause Suspends billing; nothing unpauses it automatically.
Subscription: Resume Returns a paused subscription to billing and re-derives its status from the latest invoice.

If your app needs its own member-facing state — an active/inactive toggle, a display badge, an approval flag — add your own field alongside the status rather than repurposing it. The status always reflects billing truth; your fields can mean whatever your business needs.

Once the Subscription and its catalog capabilities are enabled, the platform billing runner picks the subscription up automatically: it generates each period's invoice when it comes due, charges the payer's saved payment method when the collection method is charge_automatically, and keeps the status current. No workflow or page setup is required for billing to run.

One cadence per subscription

A subscription bills on a single interval. The first recurring item added to a subscription freezes its Interval field, and every later recurring item must share that interval. A recurring item whose price recurs on a different cadence is rejected — those belong on a separate subscription. This keeps the billing cycle unambiguous: one subscription, one clock, one invoice per period covering every item.

One-time items are the exception: a price with one_time recurrence carries no cadence, so it can be added to any subscription and is billed once on the next invoice without affecting the frozen interval.

Subscription Item

Subscription Item Resources represent a recurring line on a subscription that renews each billing period.

Setting Field type Required Purpose
Subscription field Relation Yes Links the item to its subscription.
Price field Relation Yes Links the item to the price it bills at.
Product field Relation No The product being billed. Derived from the price when left empty.
Quantity field Number No Quantity billed.
Price snapshot field Money Yes The price amount agreed at subscription time. Snapshotted from the price when left empty.
Description snapshot field Text Yes The product name agreed at subscription time. Snapshotted from the product when left empty.
Starts at field Datetime No When the item begins billing.
Ends at field Datetime No When the item stops billing.
Status field Select Yes Item state. Supported values are active and canceled.

When an item is created from its price alone, Diagonal snapshots the price amount and the product name onto the item, and denormalizes the product relation from the price. These snapshots are taken once, on save: editing the source price or product later never changes existing items, so each item keeps the terms agreed at subscription time. Any value you set explicitly is never overwritten.

Scheduling and Calendars

Use these capabilities when Resources need to appear on calendars or participate in scheduling.

Eventable is about the event itself. Schedulable is about the person, place, or thing that can be assigned to an event, how much of it there is, and when it is open. Availability records change those open hours on some dates. Booking is about a record that takes that capacity.

Resource Capability Example
Appointment Eventable A scheduled customer appointment with a start and end time.
Class Session Eventable A recurring lesson or session on a calendar.
Staff Member Schedulable A person who can be booked.
Room Schedulable A room that can be reserved.
Equipment Schedulable Equipment that can be assigned to a booking.
Ticket Order Booking An order that takes places from an event and a ticket type.
Time Off Availability A tutor's holiday, or an extra evening a location opens.

Eventable

Eventable Resources represent calendar events.

Use this for appointments, bookings, classes, sessions, meetings, site visits, or anything with start and end times.

Setting Field type Required Purpose
Event schedule field Event Schedule Yes Required start, end, and all-day values, plus optional recurrence rule and exceptions. The end must not be earlier than the start. An equal end is allowed.
Label field Text No Display name for internal calendar views.
Location field Location No Location used in calendar links.
Video conference link field Text No Event address fallback when no location is set.
Description template Text No Calendar description; can include Record fields and fields of related Records.
Write events to staff calendars (calendar_sync) Boolean No Add each Record to the Google calendar of its staff member. A repeating Record becomes a Google recurring event. Needs the Calendar sync workspace flag.
Staff member (calendar_owner_relation) Relation With calendar sync The belongs-to relation that names the staff member, such as Tutor. It must point to a Schedulable Resource. The event goes to that Record's calendar that receives events.
Event title (calendar_title_template) Text No One-line title template. Empty uses the label field, then the singular name of the Resource.
Event description (calendar_description_template) Text No Description template. Empty uses the description template. Attendees see it.
Room field (location_select_field) Select No Its option label is the event location when the Location field is empty.
Attendees (calendar_attendee_paths) List of text No Email fields to invite, stored as dotted paths of up to 3 relations that end at an Email field, such as student.family.parents.email. Has-many steps are allowed. Each email gets a Google invitation. Empty by default.
Status field (calendar_status_field) Select or Text No The status that Status meanings read.
Status meanings (calendar_status_meanings) List of {value, meaning} No meaning is cancelled (the event is removed) or tentative. Each Select value must be an option of the status field. Any other value is a confirmed event.

Eventable works well with Schedulable when you need to book people, rooms, equipment, or other availability-bearing resources.

Recurring event timezones

A new repeating event takes the timezone in the resource's Timezone Field when it holds a valid zone, and the workspace timezone otherwise. Local wall-clock times stay stable across daylight saving time changes, such as 9:00 AM every week in America/New_York.

API and workflow updates can include $operation.eventable.scope to choose how a recurring event update applies. Supported values are this, this_and_future, and all. When scope is omitted, an update applies to that occurrence only (this).

The response of a this_and_future or all save carries meta.series. counts and items cover only the part of the change that ran before the save returned. items lists each occurrence that the change could not apply, with a code such as series_kept_override: a person changed the date or time of that occurrence and the change had no date to move it to, so it stayed where it is. When a person had moved it to another day, its date in the new pattern also got a new occurrence. queued is null when the change ran whole before the save returned. Otherwise it says what still runs: from, the first date that still waits, and past, true while the past occurrences of an all change still wait. A weekday change moves an occurrence whose time a person changed to the new day at that time.

Calendar sync writes and changes each event about 10 seconds after the Record changes. A change of staff member deletes the event from the old calendar and creates it on the new one. Archiving a Record deletes its event and restoring it writes the event again. A Record that ended before the day sync starts is never written. A nightly run writes what Record events do not report, such as imports, quiet writes, and changes to related Records, and undoes edits made in Google. With attendees, Google emails them for a new event, a cancellation, a time change, an attendee change, and any change that a person or a workflow made. A change that the system or the repeat engine made emails nobody. Diagonal never creates Google Meet links: the video conference link goes into the location or the description.

A repeating Record appears from today as one Google recurring event for each part of the series, on the calendar of the staff member that part names. Generating more dates makes no Google change. A date changed with "This occurrence" becomes a changed instance, and a cancelled or skipped date, a workspace closure, or a date with another staff member becomes a cancelled instance. A substitute's date also appears on the substitute's calendar. A "This and future" change ends the recurring event the day before and starts a new one. An "All occurrences" title change emails attendees only when a person or a workflow made it. With keep, a recurring event with no end date ends at the last generated date.

A save that turns calendar sync or Eventable off can send calendar_events with PATCH /api/resources/{resource}/capabilities/eventable: keep leaves the events in staff calendars, and remove deletes the events from today on. Omitted means keep. Any other save that sends calendar_events gets 422.

To list each repeating event once, send "$collapse": "series" with a record search. The search applies every filter, the search term, and the read policies first. Then it keeps one record for each repeating event: its first date under $sort, or under the Event Schedule start when the search has no sort. A one-time record is its own entry, and pagination.total counts events, not dates. To list the dates of one series, filter on __meta.capabilities.eventable.series.series_id. Both work only while Eventable is on; otherwise the search returns a validation error.

Schedulable

Schedulable Resources can participate in scheduling.

Use this for anything that has availability or can be assigned to events: staff members, service providers, rooms, equipment, vehicles, or locations.

Setting Field type Required Purpose
Color field Color No Display color for calendars and schedules.
Name field Text No Display name used in scheduling views.
Capacity field Number No How many places a record has. Empty means no limit: bookings of the record can overlap. Bookings take places from it. Give a person or a room that takes one booking at a time a capacity of 1.
Seats left field Number No Places left. The platform keeps this field; nobody else can write it.
Timezone field Text No The zone of regular hours and of per-day, per-week, and per-month limits. The workspace zone applies when it is empty.
Regular hours field Regular Hours No The record's usual weekly hours. See Open hours.
Hours within relation Relation No Related Schedulable records, such as a location, whose open time this record stays inside.
Booking limits List No At most a number of bookings or minutes per day, week, or month, such as two classes per member each week.

A Resource can be both Authenticatable and Schedulable. For example, an Employee can log in and also be booked for appointments.

A capacity cannot be lowered below the places already taken. The count includes firm places, live holds, and the most places that overlapping future bookings take at one time. The save is refused with an error on the capacity field.

Open hours

A Schedulable record's open time is computed when it is read, so there is no horizon and a change of hours is one record write:

  1. Base hours. The record's own Regular Hours value. A record without a value, whose Hours Within relation names records, takes the open time of those records. Otherwise a record with an hours field has no open time, and a record without an hours field is open all the time. To open a record only on the dates that exceptions give, map an hours field and leave it empty.
  2. Exceptions. A replace exception removes the base hours of every date it touches, in the record's zone. open and replace exceptions add their time. Every closed exception, every closure, and busy time on the record's blocking Google calendars removes time. Busy time is read up to the furthest date that the record's Booking resources let people book (90 days when nothing books the record, at most 180), and later busy time shows as open. A booking in busy time is refused only when the Booking resource requires open hours.
  3. Limits. The result stays inside the open time of the records in Hours Within.

Open time follows the reader's policies. Hours and exceptions a reader cannot read add nothing, also when a record takes them from its location, and a record whose own hours are hidden does not take its location's hours instead. A closed exception and a limit apply whatever the reader can read. A cycle of Hours Within relations, a chain of more than four, or a stored hours value that cannot be read gives the record no open time.

Existing bookings are not checked again when hours change.

Availability

Availability records are exceptions to regular hours: time off, a holiday, an extra evening, or other hours on some dates. One record can apply to many Schedulable records, such as a whole team. The exception's time is its Event Schedule, so the Resource must also be Eventable. Eventable cannot be turned off while Availability is on.

Setting Field type Required Purpose
Schedulable relation Relation Yes The Schedulable records the exception applies to.
Effect field Select Yes A Select with the options open, closed, and replace. open adds time, closed removes it, and replace sets the hours of every date it touches. An empty or unknown value closes time.

An exception needs a start before its end. A repeating exception applies at any date, also past the dates the engine has written.

Booking

Booking Records take capacity from Schedulable Records: a lesson from its tutor, an order from its event and its ticket type. Every write path checks capacity: the admin, the Data API, page forms, workflows, and CSV import. A booking that does not fit is refused with the key $capacity, and two people can never take the last place at once.

Setting Field type Required Purpose
Takes capacity from Relations Yes Relations to Schedulable Resources, such as the event and the ticket type.
Quantity field Number No How many places one booking takes. One when empty.
Status field Select or Text When a status is held or the Resource is Eventable The status that decides what the booking takes.
Status effects List No What each status does: takes capacity, held until a time, waiting, or released. A status that is not listed takes capacity.
Status for a lapsed hold Text When a status is held or a status source is set A released status that a hold takes when it lapses.
Status for a repeat date that does not fit Text When the Resource is Eventable A waiting status that a repeating booking takes on a full date.
Hold expiry field Date and Time When a status is held When a held booking stops taking capacity. An empty value holds until the status changes, such as "reserve until the invoice is paid".
Time source relation Relation No A related Eventable and Schedulable Record, such as a class session, whose time the booking copies into its start and end fields.
Status source relation Relation No A related Booking Record, such as the order of an order line, whose status and hold expiry the booking copies.
Source status field Select or Text No Where the copied status goes when the booking also keeps its own status, such as a ticket of an order. The more restrictive status applies.
Buffer before and after Number No Minutes added around a timed booking.
Idempotency field Unique Text No A retried create with the same value returns the first booking, with meta.booking.replayed set, when the caller can see it.
Require open hours Boolean No Refuse a new or moved booking outside the open hours of the records it books. Needs a booking time: the Resource is Eventable or has a time source.
Minimum notice Number No Minutes. Slot search offers no time that starts sooner. A direct create, such as an admin or API booking, is not checked.
Book up to Number No Days. Slot search offers no time after the end of that many days from today. A direct create is not checked.

An Eventable booking takes time: on a tutor with a capacity of 1, two lessons cannot overlap. A Schedulable record without a capacity has no limit, so its timed bookings can overlap. Ticket types and limit-only records, such as clients and locations, use this. The Booking settings dialog names each resource without a capacity field that a timed booking takes capacity from. A record whose capacity is empty also has no limit, so give each existing record a capacity after you map the field. Any other booking takes places. A booking that ended before now is never refused, so you can always edit past bookings.

A held booking stops counting when its hold lapses, even before the platform writes the lapsed-hold status a minute later. Confirming a held booking keeps its place. A waiting booking takes nothing until a person or workflow moves it to a status that takes capacity, and then it must fit.

Changing how places are counted, such as a relation, a status effect, or a buffer on bookings with a time, recomputes every booking of the Resource, usually within seconds. Until it finishes, a save that takes more places, such as a new booking, a confirmation, or a restore, is refused with "Capacity setup is in progress. Try again in a minute." Edits that change no places, cancellations, and deletions go through. A Resource without bookings is ready at once. Turning Booking off and on again also recomputes.

When many people book one record at once, a booking that cannot get its turn is refused with "Many people are booking right now. Try again." under the same $capacity key. A workflow Transaction step and a script transaction return the same error.

With Require open hours on, every write path refuses a new or moved booking whose time, buffers included, is outside open hours: "This time is outside open hours." under the $capacity key. A booking whose time did not move, such as a confirmed hold or a status change, is not checked again, and neither is a booking that ended before now. A restored booking and a waiting booking that moves to a status that takes capacity are checked. Turning the setting on recomputes nothing.

Slot search finds the times a booking of one length can start. It searches groups of Schedulable records: an "all of" group needs every record free, and an "any of" pool needs one, picked by its strategy:

  • first_available: the first free record in the order given.
  • least_booked: the fewest bookings in the searched window.
  • round_robin: least booked, then the record whose latest booking in the window is earliest.

A time is offered only when its buffered span is inside the reader's open time and no capacity or booking limit refuses it. The Booking Resource's notice and advance are a floor that a search can tighten but not loosen. A search covers at most 62 days, 5 groups, 50 records in a pool, and 20 in an "all of" group, and returns at most 500 slots. A slot is advice: the booking write decides.

Where How
Data API POST /api/data/resources/{booking resource}/slots, and POST /api/data/resources/{schedulable resource}/availability for open time with the source of each piece
Pages Sdk.hooks.useAvailableSlots, Sdk.hooks.useAvailability, Sdk.Components.SlotPicker, and the Calendar backgroundTimes option
Workflows scheduling.find_slots, and scheduling.book_slot, which books a slot as the run identity, tries the next free pool member when admission refuses the pick, and returns the booking as the caller can read it

Guests can search and read records their policies let them read. A record they cannot read is refused as a missing one. A guest gets at most 60 searches and reads a minute from one address in one workspace, then HTTP 429. A public booking page books through a request workflow that runs as a chosen identity. In such a workflow, scheduling.find_slots searches, and scheduling.book_slot checks the slot again, with the visitor's view, so a visitor can book only a time their own search could offer. scheduling.book_slot returns the booking as the visitor can read it, including field and relation limits. When the visitor cannot read the booking, the result holds only its id, so later steps can still reference it. To show a confirmation to a guest, add a public Show policy on the booking resource that grants only the fields the guest should see.

Repeating bookings

A repeating date that does not fit is created with the waiting status, never skipped or moved. The save response lists every such date of the series in meta.series.waiting, with meta.series.counts.waiting, so a page can show "4 of 52 need rescheduling" with an action for each date. Each entry's reason is full for a date with no capacity left and closed for a date outside open hours when Require open hours is on. When a series lowers the capacity of a session that already has more bookings, the session keeps its capacity and the response lists it with series_kept_capacity.

Choosing Capabilities

Enable Capabilities only when the Resource needs platform behavior.

If the Resource needs to... Enable Group
Let records log in. Authenticatable Identity and communication
Send messages to records. Notifiable Identity and communication
Represent payment customers. Payer Payments and commerce
Track money owed. Payable Payments and commerce
Break totals into line items. Itemizable Payments and commerce
Add discounts, fees, or other payable-level credits and charges. Payable Adjustment Payments and commerce
Store payment attempts and successes. Payment Payments and commerce
Store a Payer's saved Stripe payment methods. Payment Method Payments and commerce
Store refunds. Refund Payments and commerce
Act as a sellable catalog item. Product Catalog and pricing
Store an amount and billing cadence for a product. Price Catalog and pricing
Bill a recurring agreement each period. Subscription Subscriptions
Add a priced line to a recurring agreement. Subscription Item Subscriptions
Appear as a calendar event. Eventable Scheduling and calendars
Be booked or assigned to events. Schedulable Scheduling and calendars
Change regular hours on some dates, such as time off or a holiday. Availability Scheduling and calendars
Take capacity from people, rooms, events, or ticket types. Booking Scheduling and calendars
Lock selected Fields after a Record matches a rule. Lockable Data protection and authorization
Act as the workspace Role or Permission catalog. Role / Permission Data protection and authorization