Open Booking API 1.0

Draft Community Group Report

Latest editor's draft:
https://openactive.io/open-booking-api/EditorsDraft/
Editors:
Leigh Dodds (Open Data Institute)
Nick Evans (Open Data Institute)
Chris Thorpe
Participate:
GitHub openactive/open-booking-api
File a bug
Commit history
Pull requests
Version:
1.0

Abstract

This document specifies an HTTP API for placing bookings to participate in physical activities, either by attending events or through the use of leisure or sports facilities.

Status of This Document

This specification was published by the OpenActive Community Group. It is not a W3C Standard nor is it on the W3C Standards Track. Please note that under the W3C Community Contributor License Agreement (CLA) there is a limited opt-out and other conditions apply. Learn more about W3C Community and Business Groups.

Contributions to this document are not only welcomed but are actively solicited and should be made via GitHub Issues and pull requests. The source code is available on GitHub.

Note

This document represents a complete Editors Draft. It is currently in the final stages of community review and we hope that it is nearing stability. The draft is intended to allow developers to provide feedback by developing proof-of-concept implementations. We encourage developers to explore this API and contribute to the development of the specification.

If you wish to make comments regarding this document, please send them to [email protected] (subscribe, archives).

1. Introduction

This section is non-normative.

The document is an output of the OpenActive Community Group. As part of the OpenActive initiative, the community group is developing standards that will promote the publication and use of open opportunity data in helping people to become more physically active.

The Community Group have already published specifications that define standard data models ([Modelling-Opportunity-Data]) and support the publication and harvesting of data in standard formats ([RPDE]).

This existing work helps increase the discovery of opportunities for people to be active. This specification builds on that work by defining an HTTP API that can be implemented by Booking Systems that are publishing opportunity data. By allowing third-party applications ("Brokers"). to place bookings in their systems, it becomes possible for more people to participate in events or make use of leisure and sporting facilities.

The specification defines the flow of HTTP requests and responses between the Booking System (server) and the Broker (client). This includes definitions of the overall application flow, detailed request and response formats and the potential error conditions that applications may encounter.

1.1 Scope and requirements

The current scope of the specification should conform to a set of use cases and requirements that have previously been identified and discussed with the OpenActive community.

The focus of the initial versions of this specification will be on the simplest use cases that will support making bookings on behalf of users.

The core functionality includes:

Additional optional functionality includes:

1.1.1 Out of Scope in this version

A number of additional requirements that relate to the booking of events and facilities are currently out of scope:

  • creating and managing accounts for Customers
  • more complex pricing options, e.g. membership based pricing
  • waiting lists for events
  • collecting marketing preferences from the Customer
  • business-to-business tax calculation
  • cancellation fees
  • "subscriptions" to allow a Customer to participate ad-hoc in a Seller's activity, with such participation recorded for later reconciliation
  • refunds/cancellations after the opportunity has occurred
  • back-office systems between Broker and Booking System, such as for payment reconciliation

It is possible that future versions of this specification may include support for these or other features. Revisions will be driven by the needs of the community, please raise feature requests via GitHub.

1.1.2 Out of Scope by design

By design this specification will not define some types of functionality.

These have been declared as permanently out of scope because they are either adequately covered by existing specifications, or will be covered by future work of the OpenActive Community Group.

The functionality that is currently defined as out of scope includes:

  • API authentication and security – while the specification recommends some best practices relating to authentication and security it does not mandate a specific means of securing an API. Implementers are free to choose the system that offers the best security for their platform and users.
  • Payment processing – the design assumes that all payment handling will be coordinated by the Broker, in a separate payment flow. Brokers are able to use the payment and reconciliation mechanisms that provide the best options for their Sellers and Customers.
  • API business models – the design is agnostic to any business models that might govern the use or provision of the API, e.g. revenue-sharing or transaction processing fees.
  • Opportunity discovery – finding, filtering and searching for opportunity data. This specification assumes that the client and server applications have already shared data about the relevant opportunities, e.g. via [RPDE] feed containing [Modelling-Opportunity-Data] data.
  • API endpoint discovery – the Broker finding and connecting to the API endpoints included in this specification are included in the [Dataset-API-Discovery] specification.

1.2 Specification dependencies

Version 1.0 of the Open Booking specification depends on the following:

Note that the versions are constrained only to major version releases (breaking changes), and that the flexibility provided by supporting minor versions enables implementors to take advantage of additional features as the specifications evolve.

Issue 105: Versioning across specifications Community Feedback Requested

There are a number of specifications that relate to each other, with versions that exist across each.

Should we have one unified version number or separate versions with dependencies?

Versions for conformance

There's an advantage in a common versioning pattern for specifications, similar to the .NET Standard it allows all actors to understand what version of a specification to request and support.

Version for interoperability

The goal of having a custom media type is to advertise the request/response formats (e.g. vnd.openactive.booking+json; version=2. Having a version number allows clients and servers to advertise what version they support (or are trying to use) and possibly negotiate on a preferred version if a server supported both.

Options for the version number

1. Versioned dependencies

The version number is the version of the specific specification (varies for each spec)

The Open Booking specification could normatively refer to a version of the Model spec (Booking 1.0 uses Model 2.x) and a version of the RDPE spec (Booking 1.0 requires an RPDE 1.x feed containing Orders which conforms to the Booking 1.x model).

For data publishing, RPDE doesn't normatively require a version of the model spec. It's a generic protocol that can carry different formats, so the data user would need to get that information from the feed, and couldn't request a different version of the model using media types, but would need to check each open feed URL for the version they were looking for.

So dependencies don't need to be advertised in the media type, as they can be inferred by the primary specification in the case of booking (Booking 1.0 with Model 3.0, and Booking 1.0 with RDPE 2.0 aren't valid choices) or from the feed in the case of data publishing.

This means that a server could offer an RPDE 2.0 feed in addition to a 1.0 feed.

2. Single platform version

The version number is the version of the "platform" (one unified version number)

One overall media type (vnd.openactive.booking+json; version=2) with the version representing a single version for all of OpenActive specifications. This means that we lose some ability for separate evolution/updates (e.g. I can't ask you for an RPDE 2.0 feed on its own), but instead have a guarantee about what features are supported across all endpoints for a particular version.

So as a data user working with vnd.openactive.rpde+json; version=2 you could be sure that at least a minimum set of fields would be included (as you could infer the minimum Model version supported), and the same for vnd.openactive.booking+json; version=2.

This makes validation of conformance easier as it limits the number of valid configurations. Experimentation could still be possible via vnd.openactive.booking+json; version=next, vnd.openactive.booking+json; version=2.1CR or similar

1.3 Audience

The document is primarily intended for the following audiences:

The following sections introduce terminology, concepts and requirements that underpin the design of the API. This content might also be useful for a wider audience, e.g. business analysts or product managers looking for a high-level overview of the API functionality.

2. Conformance

As well as sections marked as non-normative, all authoring guidelines, diagrams, examples, and notes in this specification are non-normative. Everything else in this specification is normative.

The key words MAY, MUST, MUST NOT, NOT REQUIRED, OPTIONAL, RECOMMENDED, REQUIRED, SHOULD, and SHOULD NOT are to be interpreted as described in [RFC2119].

This specification makes use of the compact IRI Syntax; please refer to the Compact IRIs from [JSON-LD].

3. Typographical Conventions

The following typographic conventions are used in this specification:

markup
Markup (elements, attributes, properties), machine processable values (string, characters, media types), property name, or a file name is in a monospace font.
Definition
A definition of a term, to be used elsewhere in this or other specifications, is underlined and in black.
hyperlink
A hyperlink is underlined and in blue.
[reference]
A document reference (normative or informative) is enclosed in square brackets and links to the references section.
Note

Notes are in light green boxes with a green left border and with a "Note" header in green. Notes are normative or informative depending on the whether they are in a normative or informative section, respectively.

Examples are in light khaki boxes, with khaki left border, and with a
numbered "Example" header in khaki. Examples are always informative.
The content of the example is in monospace font and may be syntax colored.

4. Key Actors

Of all the organisations and systems involved in a transaction, the API defined by this specification is scoped tightly to only specify interactions between the Broker and Booking System, with high level prompts for interactions with Customers and Payment Providers.

Actors in scope
Figure 1 Illustration of actors and systems involved in a transaction, and the focus of this specification being between Broker and Booking System.
Customer
The user who places a booking using an application provided by a Broker. The Customer is not necessarily the attendee, so e.g. a parent may book on behalf of their child.
Broker
The organisation or developer providing an application that allows Customers to make bookings. Those applications will be clients of the API defined in this specification
Booking System
The organisation or developer providing an application that maintains bookable inventory on behalf of the Seller. The platform or service that provides a server-side implementation of this API.
Seller
The organisation providing access to events or facilities via a Booking System. e.g. a leisure provider running yoga classes.
Payment Provider
The notional service providing payment processing between the Customer, Broker and Seller. Although this specification refers to the Payment Provider as a single notional service, it may in reality be composed of multiple connected services providing the same functionality to allow for the widest variety of business models.

5. Booking Flow

5.1 Customer Journey

The booking journey of a Customer is generalised into the following logical steps:

  1. Select: Customer selects an Offer to participate in an opportunity (e.g. ScheduledSession or Slot)
  2. Register / Login: Customer enters personal details
  3. Book and Pay: Customer enters payment details

Note that some or all steps may be skipped, or may not be presented to the user, e.g. if their registration or payments details are already known, or they are using a voice interface. These steps are not indicative of the user experience, and simply provide a common language around the flow.

High level user journey
Figure 2 Illustration of a typical Customer journey.

5.2 High-level summary of the API

This specification assumes that the Broker has already obtained opportunity data from the Booking System that includes descriptions of SessionSeries, FacilityUse, Slot etc. The Broker MUST present an up-to-date view of availability data (for example by frequently harvesting data via an opportunity data [RPDE] open feed) for the Customer to select items to book, as this data may change over time. The Booking System MUST ensure that the remainingAttendeeCapacity and remainingUses properties in the opportunity data are up-to-date, and only include places already booked and not places currently reserved by leases.

High level booking flow
Figure 3 Illustration of high level booking flow.

5.2.1 Broker contract with Booking System

To complete a successful booking journey:

  • The Broker SHOULD call C1 (without Customer details)
  • The Broker MUST call C2 (with Customer details), to retrieve the total order, with the exception of free orders where the order total is not required.
  • The Broker MUST call B (with payment details), and store the resulting Order.
  • The Broker MUST process all updates to the Order from the Orders feed

If B fails, the Broker MUST cancel the payment without any consequence to the Customer or Seller.

5.2.2 Booking System contract with the Broker

  • The Booking System MUST respond to C1 (without Customer details) with an OrderQuote that includes an indicative total order price, or a relevant error.
  • The Booking System MUST respond to C2 (with Customer details) with an OrderQuote that includes an exact total order price, or a relevant error.
  • The Booking System MUST respond to B with a completed Order, or a relevant error. The whole booking succeeds or fails as one.
  • Upon any cancellations or updates to the Order or any OrderItem within it, the Booking System MUST include the updated Order in the Orders feed.

With the exception of leasing, C1 and C2 MUST NOT have any side effects.

5.2.3 Leasing

In order to ensure that items cannot be "stolen" from a Customer's shopping basket by another Customer, the use of time-bound leases are RECOMMENDED, however they are not required to ensure a low barrier of entry for implementers of the specification.

Individually at C1 and C2, the Booking System can opt to create (or extend) a lease for the Broker. Whether they opt to do so may depend on the Booking System's capability to support leases, or on the Seller's configuration of the Booking System. At C1 the lease is anonymous, at C2 Customer details are provided.

If a lease is created at C1 and/or C2 it is used during B, if not or if it had expired the booking completes anyway provided sufficient inventory is available.

The specification is designed to allow for leasing to be implemented at a later stage by a Booking System without affecting the Broker's implementation.

When a lease is created, a Lease object MUST be returned in the OrderQuote.

Example 2: Example of lease
"lease": {
  "type": "Lease",
  "leaseExpires": "2018-10-01T11:00:00Z"
}

The leaseExpires MAY be used to inform the Customer of the time they have remaining.

5.3 Step-by-step process description

Sequence diagram
Figure 4 Sequence diagram showing API interactions

i) "1. Select" - User browses Broker site anonymously and adds something to basket

ii) C1 - Broker call with OrderQuote including UUID without a customer object to check availability and that the combination of items requested can be purchased (call is idempotent), Booking System responds with an OrderQuote.

iii) "2. Register / Login" - User clicks checkout and enters personal details

iv) C2 - Broker call with OrderQuote including UUID with a customer object to check viability (call is idempotent), Booking System responds with an OrderQuote.

v) "3. Book and Pay" - User enters payment details and clicks Book

vi) Authorise Payment - Broker pre-authorises totalPaymentDue from Payment Provider

vii) B - Broker call with Order including UUID with a Person object to check viability, with a Payment object, and totalPaymentDue including the total amount pre-authed (call is idempotent, can be retried if 500 returned), Booking System responds with an Order.

viii) Capture Payment - Broker captures totalPaymentDue from Payment Provider

ix) Invoice Generation and Customer Notification - Upon successfully capturing payment, the Broker generates invoices, sends Customer notifications, and stores the Order in a success state based on the response from B.

x) Refunds and Cancellation - The Broker subscribes to updates from the Booking System to process cancellations and refunds.

5.3.1 Booking flow with approval

For the case where any OrderItems in an Order require approval (orderItemStatus of https://openactive.io/OrderApprovalPending), the whole Order MUST be held in a pending state by the Booking System, with the Broker storing the Order with an internal status of https://openactive.io/OrderApprovalPending.

An Order pending approval MUST be approved or rejected atomically; partial approval is not supported in this version of the specification.

For an Order that requires approval, the flow differs from the above as follows:

  • OrderQuote is returned at C1 and C2 with any OrderItems that require approval having with orderItemStatus of https://openactive.io/OrderApprovalPending. The Customer MAY then be prompted to supply an orderApprovalRequestNote to display to the seller, which can be included with the Order at B.
  • Order is returned at B with any OrderItems that require approval having an orderItemStatus of https://openactive.io/OrderApprovalPending. If this is the case, the Customer MUST be notified that the Order is awaiting approval.
  • If approval is given, the Booking System updates the Order in the Orders feed setting all OrderItems to an https://openactive.io/OrderConfirmed state. The Broker receives this update from the Orders feed and completes the remaining steps of Capture Payment, Invoice Generation and Customer Notification as in the original flow above.
  • If approval is not given, the Booking System MUST make the Order available in an RPDE deleted state in the Orders feed and the Broker MUST notify the Customer that their booking was not approved, and any payment authorisation MUST be withdrawn.

A Broker MAY cancel an Order before it has been approved by using the Order Deletion endpoint.

Sequence diagram with approval
Figure 5 Sequence diagram showing API interactions, with approval step

5.3.2 Single state Orders

The Order is created in an OrderConfirmed state (via OrderAppovalPending if approval is used), and although their OrderItems may be cancelled by either the Customer or the Seller, the Order itself within the Booking System will not exist in the any other state. The orderStatus property of the Order MUST NOT be used and is reserved for future evolution of the specification.

5.3.3 Payment Capture Rollback

The following actions MUST be performed to rollback the Order following an error during Capture Payment, starting at the internal status of the Order and continuing through the list from that point unless otherwise specified:

  1. https://openactive/PaymentCaptured - Invoice generation and notification should simply be retried, with no further rollback steps taken - a rollback SHOULD NOT be performed from this status.
  2. https://schema.org/OrderPaymentDue - Issue a DELETE request for the Order to remove it from the Booking System.
  3. https://openactive/PaymentAuthorized - Cancel the associated Payment Authorisation from the Payment Provider.
  4. Soft delete the contents of the Order from the Broker (including any personal data), retaining the UUID.

Note that due to the ordering of the Booking Flow, no notifications will have been sent to the Customer and no money taken, so no notifications regarding the rollback are required assuming it is performed promptly.

5.3.4 Order Feed Inconsistencies

An error condition exists where an unrecognised Order item is found by the Broker on the feed. Due to internal status of Orders being monitored as described previously, there should always be a matching Order to any UUID on the Orders feed even if it has been soft-deleted. This would make an unrecognised Order an impossible scenario, and hence an unrecognised Order should be logged as a serious error within the Broker and promptly investigated.

6. Broker Roles

A Broker is an actor that arranges transactions between a Seller and Customer, either directly or indirectly.

From a contractual and taxation perspective, there are two types of Broker, and agent (AgentBroker), and a reseller (ResellerBroker), as well direct purchase (NoBroker).

Note that this specification does not deal with contractual relationships, and simply provides a mechanism to record bookings.

6.1 ResellerBroker

6.1.1 Definition

A reseller is a company or individual that purchases goods or services with the intention of selling them rather than consuming or using them. This is usually done for profit (but could be resold at a loss).

6.1.2 Contractual Relationships

In the context of OpenActive, a ResellerBroker contracts directly with the Seller as a business-to-business relationship to purchase access to the opportunity. It then, at a later point in time (which may only be milliseconds later), separately forms a contractual relationship with the Customer, who purchases access to the opportunity from the ResellerBroker.

ResellerBroker relationships
Figure 6 Contractual relationships in ResellerBroker mode

6.1.3 Taxation

The ResellerBroker's purchase from the Seller is business-to-business, which is subject to the appropriate taxation based on the ResellerBroker as the Customer. The Customer's purchase from the ResellerBroker is business-to-consumer, which is subject to the appropriate taxation based on the ResellerBroker as the Seller.

Hence any tax exemption that the Customer may enjoy when purchasing directly from the Seller (e.g. if the Seller is an VAT exempt eligible body) is not relevant here, as no direct contractual relationship is formed between Seller and Customer.

6.1.4 Scope of Specification

This specification is designed to govern the interaction between the ResellerBroker and Seller. It does include provision for the interaction between the ResellerBroker and Customer explicitly, though it may be repurposed by the Broker to perform this function by treating the Broker as a Booking System. This specification also does not include provision for the recording of the execution of the contractual relationship with any Payment Provider (e.g. for payment processing fees). Such relationships MUST be handled separately. When the brokerRole is set to ResellerBroker, this indicates that the payee for accounting and tax purposes is the broker specified in the Order. Note that the customer may still optionally be included in the Order, for example to help front-of-house staff identify the Customer.

6.1.5 Conformance criteria

When the Broker generates the Invoice, it must be made payable to Order.broker, Order.broker MUST be provided and Order.customer is optional.

6.2 AgentBroker

6.2.1 Definition

An agent is authorized to act on behalf of another (the Seller, or "principle") to create legal relations with a third party (the Customer). Succinctly, it may be referred to as the equal relationship between a Seller and an agent whereby the principal, expressly or implicitly, authorizes the agent to work under his or her control and on his or her behalf. The agent is required to negotiate on behalf of the principal (Seller) and/or bring them and third parties (Customers) into contractual relationship.

6.2.2 Contractual Relationships

There are three separate types of contractual relationship involved: AgentBroker with Seller, known as the principal-agent relationship or "internal" relationship; AgentBroker with Customer with whom they deal on their Seller's behalf ("external relationship"); and Seller with Customer when arranged by an AgentBroker.

AgentBroker relationships
Figure 7 Contractual relationships in AgentBroker mode

6.2.3 Taxation

While facilitated by the AgentBroker, the primary purchase is made by the Customer directly from the Seller, as would be the case if the Customer was to purchase from the Seller independently. Hence any tax exemption that the Customer may enjoy when purchasing directly from the Seller (e.g. if the Seller is an VAT exempt eligible body) is relevant here.

The Customer's relationship with AgentBroker is business-to-consumer. Hence any additional services (e.g. customer-facing booking fees) are subject to the appropriate taxation based on the AgentBroker as the seller.

The AgentBroker's relationship with the Seller is business-to-business. Hence any additional services (e.g. booking commission) are subject to the appropriate taxation.

6.2.4 Scope of Specification

This specification is designed to govern the recording of the execution of the contractual relationship between the Customer and Seller. The scope of this specification does not include the contractual relationship between the AgentBroker and the Seller (e.g. for booking commission), between the AgentBroker and the Customer (e.g. for booking fees) or with any Payment Provider (e.g. for payment processing fees). Such relationships and the reconciliation of associated invoices MUST be handled separately. When the brokerRole is set to AgentBroker, this indicates that the payee for accounting and tax purposes is the customer specified in the Order.

6.2.5 Conformance criteria

When the Broker generates the Invoice, it must be made payable to Order.customer, Order.broker MUST be provided and Order.customer MUST be provided.

6.2.6 Informed purchase

When using a brokerRole of AgentBroker, the Broker MUST make the Customer aware that they are purchasing directly from the Seller via the Broker, and not directly from the Broker.

6.3 NoBroker

6.3.1 Definition

This specification supports direct purchase by a Customer (for example in the context of the Seller's own website, or for businesses that purchase large volumes of FacilityUse slots to run leagues).

6.3.2 Contractual Relationships

The contractual relationship is a simple one between the Seller and the Customer.

NoBroker relationships
Figure 8 Contractual relationships in NoBroker mode

6.3.3 Taxation

The purchase is made by the Customer directly from the Seller, as would be the case if the Customer was to purchase from the Seller outside of this specification. Hence any tax exemption that the Customer may enjoy when purchasing directly from the Seller (e.g. if the Seller is an VAT exempt eligible body) is relevant here.

6.3.4 Scope of Specification

This specification is designed to govern the recording of the execution of the contractual relationship between the Customer and Seller. The scope of this specification does not include the contractual relationship with any Payment Provider (e.g. for payment processing fees). Such relationships MUST be handled separately.

When the brokerRole is set to NoBroker, this indicates that the payee for accounting and tax purposes is the customer specified in the Order.

6.3.5 Conformance criteria

When an Invoice is generated, it must be made payable to Order.customer, Order.broker MUST NOT be provided and Order.customer MUST be provided.

7. Systems of Record

7.1 Roles and responsibilities overview

For the purposes of this specification, the key components of a booking are: Order, Invoice, Payment and Refund.

Note that OrderQuote is a subclass of Order used during the Order creation process, and is not explicitly persisted.

The Broker has visibility of all components, while for simplicity of implementation the Booking System only has visibility of the Order, as shown below:

Roles and responsibilities
Figure 9 Roles and responsibilities

Orders are the basis of the exchange between the Booking System and the Broker, with OrderQuotes used as part of the Order creation process. This specification describes a detailed model and API to allow Brokers to manipulate Orders, and only high level functional requirements for the expected behaviour of Invoices, Payments, and Refunds.

Component Description Owner Audit
Order A mutable object that encompasses the live state of the bookable Events within the Booking System. An Order has a orderItems, a totalPaymentDue, and all tax details, but is not itself a legal representation of a purchase from the Seller. Booking System. Mutable
Invoice An immutable object that is a legal representation of the purchase, which MUST be rendered to the Customer as a legally permissible tax receipt, and exist as a tax point. A new Invoice MUST be generated for the Order to replace the previous Invoice each occasion that an orderItem within an Order is cancelled. Broker Immutable
Payment A immutable object that represents the original payment to the Seller for the initial Order. Although modelling the Payment is outside the scope of this specification, it is recommended that one payment exists for each Order. Payment Provider Immutable
Refund An immutable object that represents each refund made against the Payment, up to the Payment's total amount Payment Provider Immutable

7.2 Invoices and Tax receipts

Due to the variety of business models in use in OpenActive, the Booking System MUST NOT send receipts directly to the Customer for AgentBroker and ResellerBroker bookings.

For each successful Order operation (where C1, C2 and B together constitute a successful Order Creation operation), the Broker MUST generate a new Invoice in the form of a tax receipt, on the basis of the Orders feed contents. To ensure that the Customer is provided with the correct tax receipt, if totalPaymentTaxSpecification is provided in an Order in the Orders feed, the Broker MUST generate a new or updated Invoice and either (a) send a tax receipt representation of the Invoice to the Customer and/or (b) offer a means to allow the Customer to easily retrieve a full tax receipt representation of the specific version of the Invoice at any future date for any previous booking made. As it is ultimately the Seller's responsibility to send tax receipts, they need to be confident that the Broker is making tax information available to its Customers in an accurate and timely manner.

The tax receipt sent by the Broker MUST comply with any legal requirements of the jurisdiction of operation.

Note by following specification, for all Broker modes where a Seller may need to supply a tax receipt manually for any reason, the Seller will have the details of the Customer and the transaction enough to generate the most recent tax receipt.

7.3 Payments and Refunds

Although the details of Payments and Refunds are not in the scope of this specification, a single Payment MUST be related to each Order, and a unique reference to this Payment MUST be included in the Payment identifier of the Order sent to the Booking System. Note that this Payment is permitted to represent a virtual Payment consolidating a number of actual Payments to allow for a wide variety of Payment methods. In the case where the Seller is paid monthly, the Payment may also represent a reference to a future invoice against which the eventual Payment will be paid. In all cases the Payment identifier MUST be uniquely resolvable for audit purposes.

7.3.1 Payment reconciliation

Although automated payment reconciliation is outside the current scope of this specification, a Booking System MAY include identifiers suitable for payment reconciliation to be handled out-of-band with each OrderItem using the additionalProperty property.

In keeping with the Schema.org definition of PropertyValue, the name and value properties MUST be provided within any given PropertyValue, and the Broker SHOULD include these in any reconciliation process with the Seller.

Example 3: Example of additionalProperty for OrderItem
"additionalProperty": [
  {
    "type": "PropertyValue",
    "name": "SiteID",
    "value": "ABC1234"
  },
  {
    "type": "PropertyValue",
    "name": "DeptID",
    "value": "D89"
  }
],

7.4 Tax calculation

7.4.1 Business-to-consumer tax calculation by Booking System is mandatory

For AgentBroker and NoBroker modes, where the Customer is of type Person and a tax receipt is usually generated by the Booking System, the tax calculation and associated properties this specification MUST be implemented by the Booking System (even if the resulting tax amount is zero).

7.4.2 Business-to-business tax calculation by Booking System is optional

Tax calculation at the Booking System is OPTIONAL when the brokerRole of ResellerBroker is specified, or when a Customer of type Organization is specified. If tax calculation is not supported for a ResellerBroker, unitTaxSpecification and totalPaymentTaxSpecification MUST NOT be included, and the taxMode https://openactive/TaxNet MUST be returned in the OrderQuote and Order (i.e. explicitly not including tax).

This allows tax to be calculated by the ResellerBroker or business Customer and reconciled outside of this specification.

Note that for business-to-business sales for a Seller with taxMode of the organizer or provider set to https://openactive/TaxGross, the Offers available in the opportunity data will still be calculated as tax-inclusive for a business-to-consumer sale, and so MUST be presented by the Broker as such, with the business-to-business tax calculation occurring during the booking flow. If the usecase of presenting the correct price up-front gains traction, additional price data must be made available as part of the openly published opportunity data to resolve this shortcoming.

7.5 Tax mode

The taxMode (https://openactive/TaxNet or https://openactive/TaxGross) is REQUIRED to be specified at the Seller level, and MUST be reflected within the Organization that is included in the REQUIRED organizer or provider properties within opportunity data specified in [Modelling-Opportunity-Data]. It MUST also be consistent for all Orders originating from that Seller.

Note that taxMode only effects the interpretation of the price of the Offer. totalPaymentDue is always Gross (tax inclusive), and unitTaxSpecification and totalTaxSpecification include only the taxes themselves so are uneffected.

taxMode https://openactive/TaxNet https://openactive/TaxGross
Price displayed Excludes tax Includes all applicable taxes assuming a Person as a customer
Currencies usually associated USD EUR, AUD, GBP
Example price of opportunity before tax 10.00 10.00
Example tax rate 0.2 0.2
Price displayed to Customer during browsing (Offer) 10.00 12.00
Total paid by Customer (totalPaymentDue) 12.00 12.00

7.6 Free opportunities

Free opportunities, defined by those with at least one Offer with a price of 0, MUST follow the same workflow as paid opportunities, except with no interaction with a Payment Provider, and no payment included in the Order Creation request.

For free Offers publishers MAY not include priceCurrency. If a priceCurrency is not available then Brokers SHOULD display "Free" or equivalent when displaying a zero price.

7.7 Offer overrides

The unit price of an OrderItem may be overridden to allow for business models with variable pricing (where price differs from list price) and dynamic pricing (where price is not known at the point of purchase).

An OfferOverride or DynamicOffer type may be supplied to the acceptedOffer property at C1, C2 and B. When an OfferOverride or DynamicOffer is used, it must be used consistently between C2 and B to OrderQuote, it must be used by the Booking System instead of acceptedOffer to calculate the tax and totals.

Issue 96: Allow price overrides in this version of the specification? Community Feedback Requested

The only business model not automatically permissible within this specification (if the section "Offer overrides" was to be removed) is one that requires price variability on the part of the Seller.

As the Invoice generation and Payment is the responsibility of the Broker (so the invoice may contain additional items as needed and Orders can be assigned to arbitrary Invoices which cover arbitrary time periods), the only thing that isn't flexible by default is the total price of the Order itself and the price of the items within it.

Should this version of the specification include price overrides?

Pros:

  • Including price overrides ensures that the specification covers all business models.

Cons:

  • Including price overrides slightly increases complexity of implementation.

7.7.1 OfferOverride

The unitTaxSpecification is calculated by the Booking System exactly as it would be for an standard Offer. The price of the OfferOverride may be zero, and also may be higher or lower than the available Offers that exist in the Booking System.

OfferOverride MUST NOT be used when isAccessibleForFree is true, as free events cannot be overridden. Note that Booking Systems that only offer free opportunities MUST NOT implement OfferOverride support, and must always throw an error when it is used.

Example 4: Example of OfferOverride
"acceptedOffer": {
  "type": "OfferOverride",
  "description": "Winger space for Speedball.",
  "name": "Speedball winger position",
  "price": 10.00,
  "priceCurrency": "GBP"
}

7.7.2 DynamicOffer

The DynamicOffer MUST NOT include a price, and MUST NOT contribute to the totalPaymentDue of the Order. unitTaxSpecification MUST NOT be calculated by the Booking System for a DynamicOffer.

The identifier of the DynamicOffer MUST be used consistently in order to allow the Seller to reconcile opportunities booked using a particular type of DynamicOffer out-of-band.

Example 5: Example of DynamicOffer
"acceptedOffer": {
  "type": "DynamicOffer",
  "name": "MoveGB Membership"
  "identifier": "MOVE"
}

8. Order Operations

8.1 Opportunity Selection

Before entering into the Booking Flow, pairs of appropriate acceptedOffer and orderedItem must be selected by the Customer.

The Booking System MUST ensure that the remainingAttendeeCapacity and remainingUses properties in the opportunity data are up-to-date, and only include places already booked and not places currently reserved by leases.

8.1.1 Definition of an Open Booking 'bookable' opportunity

An opportunity of type Event, ScheduledSession, HeadlineEvent, Slot or CourseInstance is deemed to be bookable via the Open Booking API if:

  • availableChannel of a valid Offer (that is not an IndicativeOffer) includes https://openactive.io/OpenBookingPrepayment.

  • The endDate is not already in the past (note that bookings are still possible after the startDate).

  • The eventStatus of the Event, ScheduledSession, or Slot is not https://schema.org/EventCancelled or https://schema.org/EventPostponed.

  • The remainingAttendeeCapacity or remainingUses is greater than the number required.

  • The potentialAction of dataset site includes an OpenBookingAction object.

  • The validFromBeforeStartDate duration (if provided) subtracted from the startDate in the past. This allows for a "bookahead" window to be specified.

  • The taxMode (https://openactive/TaxNet or https://openactive/TaxGross) is specified within the Organization that is included in the REQUIRED organizer or provider properties.

  • The Broker has machine readable permission to access the relevant instance of a compliant Open Booking API provided by the Booking System via an API key or similar token.

The Broker MUST NOT attempt to book an opportunity that is not bookable by the above criteria. The Booking System MUST include an error of value UnavailableOpportunityError against each OrderItem in the OrderQuote that is not bookable.

Note that although the Customer may be ineligible to attend a bookable opportunity based on the genderRestriction or ageRange, this specification encourages the Broker NOT to capture gender and age data from the Customer and NOT to enforce validation of these restrictions, but instead to prominently display any restrictions in the booking process to ensure that the Customer is aware of these.

There may be more restrictions placed by a Seller on a booking. For example, it may be that one of the party must be over a certain age as a supervising adult if some of the party are minors. It is the responsibility of the Booking System and Seller to make such restrictions available as oa:attendeeInstructions on the SessionSeries, FacilityUse, Event, HeadlineEvent or CourseInstance, and the Broker MUST display these instructions clearly during the booking process.

8.1.2 Applicable Offers

Applicable Offers are a complete set of Offers for an opportunity, deduced from inheritance following the parent/child relationship defined by the superEvent/subEvent properties for all types that subclass schema:Event, and defined by the facilityUse/event properties for oa:FacilityUse and oa:Slot.

The following recursive algorithm should be applied to calculate applicable offers:

  1. The applicable set of offers is initialised with the offers property of the bookable opportunity.
  2. If a parent is available for a bookable opportunity, the applicable set of offers MUST also include all Offers specified in the offers property of the parent, where the identifier of that Offer does not match an existing applicable offer. This allows Offers to be overridden in the child using the identifier attribute.
  3. Step 2 is repeated for each parent recursively.
  4. The Broker MUST then filter this applicable set to provide a set of Offers which are current (based on validFromBeforeStartDate) and are available to the Customer through the Broker (based on availableChannel).
  5. The Broker MAY additionally filter based on the Offers that apply to the Customer, for example based on ageRange if specified in the Offer. This specification provides no guidance as to how the filtering should be performed, however it recommends against gathering additional personal data about the Customer in order to restrict Offers presented, and instead advocates clear messaging regarding the restrictions of each Offer to allow the Customer to make an informed decision.

Note that IndicativeOffers are ignored for the algorithm above, and are not considered bookable.

Issue 103: How do we delete Offers with inheritance? Community Feedback Requested

If SessionSeries has Offers A, B, C, and ScheduledSession only needs B, how do we remove A and C from the ScheduledSession?

As a data publisher - if this is a use case that you are aware of within your booking system, please comment with it here.

8.1.3 IndicativeOffer

There are cases where Offers are provided at the SessionSeries or FacilityUse level which are indicative, and are not intended to be inherited and applied to the ScheduledSessions or Slots within them. For such cases an IndicativeOffer may be used to indicate that the offer must not be considered bookable.

Example 6: Example of IndicativeOffer
"offers": [
  {
    "type": "IndicativeOffer",
    "name": "Off Peak",
    "price": 7.00,
    "priceCurrency": "GBP"
  },
  {
    "type": "IndicativeOffer",
    "name": "Peak",
    "price": 10.00,
    "priceCurrency": "GBP"
  }
]

8.2 Amending the OrderQuote before B

The Broker MAY call C1 and MUST call C2 when the Customer updates their basket to add and remove items.

When repeated calls to C1 or C2 are made with the same UUID, the Booking System MUST expire the leases of items that have been removed from the basket.

All leases may be cancelled by submitting an OrderQuote with the same UUID but without any orderItems as either C1 or C2. The Broker SHOULD make a reasonable attempt to cancel all leases when it is aware that a Customer has abandoned their journey. For Booking Systems that have not implemented leasing, an OrderQuote without any orderItems can simply be ignored.

8.3 Cancellation after B

Either the whole Order or individual OrderItems within the Order may be cancelled after B, however OrderItems cannot be replaced or added to an existing Order within this version of the specification.

The cancellation may be requested by either the Customer or the Seller, and results in an updated Order item on the Orders feed. Once a cancellation status is placed on an OrderItem within an Order in the Orders feed it is assumed by the Booking System to have been processed, and it MUST NOT be reversed. A new Order will need to created to reinstate the booking.

Issue 97: Arbitrary refunds are not in scope Community Feedback Requested

Refunds are only permitted for entire items, and partial item refunds (including cancellation fees) are not in scope for this version of the specification.

Although it is understood that some booking systems support partial item refunds, this is a superset of the functionality currently offered within the specification, so should not be an issue for refunds triggered due to cancellations via the Broker. As it stands Seller requested cancellation will be limited to whole item refunds and no cancellation fees.

Please comment if partial item refunds or cancellation fees are essential to your usecase.

8.3.1 Refund after the opportunity has occurred

Although a cancellation may be requested before the opportunity has occurred in order to trigger a refund, cancellation and refund after an opportunity has occurred is currently outside the scope of this specification.

Full and partial refunds after an opportunity has occurred MUST be handled out-of-band directly between the Broker and the Seller.

8.3.2 Customer requested cancellation

Customer requested cancellation
Figure 10 Customer requested cancellation

To cancel an existing OrderItem, the Broker MUST:

  1. Use the latest state of the Order stored during the booking flow and updated via the Orders feed to determine which OrderItems may be cancelled. If allowSimpleCancellation is true and latestCancellationBeforeStartDate subtracted from the opportunity startDate is in the future, a PATCH against the Order including the OrderItem with "orderItemStatus": "https://openactive.io/CustomerCancelled" MUST succeed.

  2. Inform the Customer of the expected refund before they commit to cancellation, where the value of the refund for each OrderItem can be obtained by submitting a PATCH of type OrderQuote including only the id for each of the OrderItems to be cancelled, with an "orderItemStatus": "https://openactive.io/CustomerCancelled" set on each. This will return an updated OrderQuote with a totalPaymentDue, which can be used by the Broker to display the expected refund to the Customer and have them confirm they wish to proceed.

  3. Submit a PATCH for the Order including only the id for each of the OrderItems to be cancelled, with an "orderItemStatus": "https://openactive.io/CustomerCancelled" set on each. On success, a 204 response will be received, without any body. On failure a 5xx response will be received with an error message.

  4. Process any refunds based on the resulting updates to the Orders feed, based on the updated totalPaymentDue. Successfully cancelled OrderItems will have orderItemStatus set to https://openactive.io/CustomerCancelled. Note refunds MUST NOT be processed except in response to updates in the Orders feed.

To cancel an entire Order, the PATCH request MUST include "orderItemStatus": "https://openactive.io/CustomerCancelled" for each OrderItem within it.

Note that there is no provision for a Customer to cancel an Order after the cancellation window specified by latestCancellationBeforeStartDate. They may wish to do this out of politeness even though no refund is possible, with the benefit that their place may be taken by another participant, which can be included in future versions of this specification.

Issue 92: Allow whole Order cancellation? Community Feedback Requested

Should we allow the cancellation of a whole Order?

Pros:

  • Easier for broker

Cons:

  • More complexity for booking system
  • Requires booking system to test both all OrderItems being cancelled to cancel whole Order, as well as cancellation of the Order directly
Issue 102: Cancellation restrictions and atomicity Community Feedback Requested

Challenges identified

Cancellation Restrictions

The spec currently includes two mechanisms to restrict cancellations:

  • allowSimpleCancellation which is available at the OrderItem level - this provides a mechanism for restriction of cancellation based on the OrderItem for a particular Broker
  • latestCancellationBeforeStartDate which creates a window before the event during which the customer cannot cancel the OrderItem which is global to all Brokers and available in the open feed to allow customers to be advised of this constraint during purchase.

The challenge with the above is that there may be additional constraints applied that prevent cancellation in certain scenarios, and hence the call to patch with CustomerCancelled may fail for some other legitimate reasons.

Cancellation Atomicity

The current design requires a number of separate PATCH calls to be made to cancel an Order (i.e. /orders/{uuid}/order-items/{order-item-id} for each OrderItem), which are batched by the Booking System.

This is problematic as it complicates the error conditions for all parties - e.g. if half an Order is cancelled and the other half not due to cancellation restrictions or other issue, the cancellation action requested by the user is left incomplete. The same is true of e.g. cancelling all places within a particular event within a multi-event Order.

Proposed Solutions

  1. Add a specific CancellationNotPermittedError that can contain a free-text Customer-facing description of the reason the item cannot be cancelled. This would mean the UX of a Broker would likely change from "Cancel" to "Request Cancellation", as the cancellation cannot be guaranteed.

  2. Refine the cancellation PATCH to be at the Order level and include an array of OrderItems to be cancelled atomically.

Current state of specification

Solution 2 above has been amended in the spec, based on the consensus on the call today.

Solution 1 may no longer be necessary, as we had not considered allowSimpleCancellation on the call? Does allowSimpleCancellation meet the usecase you were describing @dolkensp?

8.3.3 Seller requested cancellation

Seller requested cancellation
Figure 11 Seller requested cancellation

OrderItems cancelled by the Seller will have orderItemStatus set to https://openactive.io/SellerCancelled in the Orders feed, and will optionally include a cancellationMessage. Refunds MUST be processed in response to the resulting updates in the Orders feed, and the Customer MUST be notified with the cancellationMessage if provided.

8.4 Notifications, updates and refunds

An Orders feed, which is a secure [RPDE] feed of Orders, MUST be provided by the Booking System, with the contents of the feed specific to the authenticating Broker. This allows the Broker to maintain an updated state of all their bookings across a number of Booking Systems, even when changes are made outside of the Broker.

This also allows the Broker to handle refunds to cancellations, and process notifications to Customers in a consistent way.

8.4.1 Booking System: Updating the Orders feed

The Booking System must ensure that the Orders in the Orders feed represent the current state of OrderItems within the system, for the properties included in the feed. Hence any change to any property within an Order in the feed must result in a change to the modified property associated with that Order.

In order to allow a natural batching of updates in the Orders feed, where multiple cancellations may be made across multiple Orders for the same Customer, cancellation updates made to Orders in the feed by the Booking System MUST be posted with a modified date 30 seconds into the future. The following query will easily exclude such updates while still meeting the [RPDE] specification:

Example 7: Example of RPDE query with natural batching
--include first part of WHERE clause only if @afterTimestamp and @afterId provided
   WHERE (
        (modified = @afterTimestamp
              AND id > @afterId)
        OR (modified > @afterTimestamp)
      )
      AND modified <= GETDATE()
ORDER BY modified,
         id

Note that this delay of cancellation requests being processed by the Broker also mitigates the unlikely race condition of an Order that is created and then immediately cancelled by the Booking System, which would result in the Broker receiving an unrecognised Order in the Orders feed.

8.4.2 Broker: Processing the Orders feed

The Broker MUST maintain an up-to-date store of Orders received from the Booking System. Each Order received is compared to the last Order stored, and any differences between the old and new content trigger notifications and refunds to the Customer, as well as updating the Broker's store.

Hence it is important for the Broker to consider their store of Orders as durable, and not simply a cache, to ensure their Customers always get relevant notifications.

Note that the properties included in the Orders feed and the Order response from the Order Creation call MAY be a subset of the Order request to B (effectively making the Orders feed a series of PATCH requests for the Broker), and hence any additional properties required (including properties that are OPTIONAL in the Order response but that were used to create the Order) SHOULD be stored alongside the Order by the Broker during B.

8.4.3 New Orders

New Orders MUST be stored by the Broker based on the response to the Order Creation call, but only stored with a success state on successful completion of the Booking Flow

New Orders are not included in the feed until they have been updated at least once. This minimises the volume of Orders in the Orders feed, and allows the polling frequency of the feed to be greatly reduced, which reduces the overall load on the Booking System. It also removes any race conditions between B completing and Order Creation updates from the Orders feed being received.

Hence all Orders in the Orders feed will always be recognised by the Broker as updates to an existing stored Order, and the Broker should treat any unrecognised Orders in the feed as a serious implementation error.

8.4.4 Cancellation, refund calculation and notification

Regardless of the source of the cancellation, the process for refunding and notifying the Customer and updating the Broker's records is exactly the same.

The Broker MUST process refunds for each new cancellation found in the feed, and it is the Broker's responsibility to continually retry failed refund processing and escalate any processing failure to the Customer accordingly. Note that the Orders feed is "fire and forget" for the Booking System: once an OrderItem is updated with a cancellation status in the Orders feed, the status MUST not be reversed, and is assumed by the Booking System to be processed.

For Orders found with OrderItems that contain orderItemStatus with new https://openactive.io/CustomerCancelled or https://openactive.io/SellerCancelled values, the new totalPaymentDue value of the Order must be made to equal the value of the associated Payment less any Refunds, by creating an additional Refunds if necessary.

The Customer MUST be notified after a refund is successfully processed, using the orderItemStatus to indicate the source of the cancellation.

If refund processing takes longer than 15 minutes, and a new status of https://openactive.io/SellerCancelled was detected by the Broker, then the Customer must be notified immediately of a cancellation ahead of the notification of the completed refund. This ensures the Seller can rely on the Broker as a notification channel in the event of cancellation. The Customer MUST be notified with the cancellationMessage for the OrderItem if provided.

Note that the Customer MUST be notified in the event of all cancellations, even if no refund is due for free opportunities.

8.4.5 Change of logistics notifications

The orderedItem within the OrderItem of the Orders feed includes an id reference to an opportunity, which may be found and cross-referenced with other sources of opportunity data, for example an [RPDE] open feed.

This specification does not require the Orders feed to include the full contents of the opportunities within the orderedItem, and hence the Broker MUST monitor the opportunity data feeds for this information and MUST provide a notification to the Customer for substantive changes to the opportunity (e.g. changes to startDate).

Issue 100: How much content should Orders feed provide? Community Feedback Requested

The Orders feed contains opportunity data references to e.g. ScheduledSessions.

In the case that the startDate changes for a booked ScheduledSession, should the Broker expect to get this update from the Orders feed, or from the other open data feeds that already include this information?

Option 1: Orders feed focusses on financial transactions only i.e. anything that results in a cancellation or refund (feed items effectively PATCH):

  • This greatly simplifies implementation of the Orders feed for the booking system
  • The broker needs to link updates received via the open feeds to updates in the Order items
  • The specification is tied more closely to the presence of open opportunity feeds

Option 2: Orders feed includes updates to logistics and transactions (feed items effectively PATCH)

  • More complex implementation for the booking system, but still only a limited number of fields to store
  • The broker only needs to monitor the Orders feed to update Customers

Option 3: Orders feed includes full Order (feed items effectively PUT)

  • Issues around GDPR if implementation causes sharing of Customer data
  • Much more complex for booking system
  • Broker can be sure they have accurate representation of Order if they lose data, as they can do an RPDE resync (however note that Broker still is required to be responsible for Invoice and payment data, so they need to have a robust store of this in any case).

Based on the largest barrier to progress in OpenActive is scaling adoption/implementation on the booking system side, Option 1 has been used in this version of the specification to create the lowest possible barrier to entry for booking systems.

8.5 Amending the Order after B

OrderItems cannot be replaced or added to an existing Order within this version of the specification. A new Order must be created, and the old Order cancelled.

For the common case of rescheduling a SessionSeries or FacilityUse booking, the following steps are recommended for the Broker:

  1. Request an OrderQuote for the new orderItems with a new UUID, using C2.

  2. Check the latest stored state of Orders from the Orders feed to ensure that old orderItem allowSimpleCancellation.

  3. Clearly display messaging to the Customer that explains that this transaction will involve a refund and new purchase, and so they will expect a refund for the old item and a new transaction for the new one. Information should be displayed to the Customer if any price difference exists between the two Orders, for the Customer to confirm.

  4. Request new payment details from the Customer and Authorise the amount specified by C2, then DELETE the old orderItem, and if successful, complete B as normal.

The new Order is completed as described in the new booking process, and the existing Order is cancelled with the refund processed as described in the next section.

8.6 Delivery of Terms and Conditions and Privacy Policy

The Broker MUST make the Customer aware of and assent to any termsOfService provided within the seller, broker, and bookingService (which may include Terms and Conditions and Privacy Policy) before the Customer completes B.

Since, during booking, the details of the Customer are provided to the Booking System and the Seller, the Broker SHOULD inform the Customer of this any implications with respect to GDPR and any other data protection legislation.

Note that termsOfService may only be specified at the seller, broker or bookingService level, and may not be provided for a specific Offer or OrderItem.

Example 8: Properties for Terms and Conditions and Privacy Policy
"termsOfService": [
  {
    "type": "Terms",
    "name": "Privacy Policy",
    "url": "https://example.com/privacy-policy"
  },
  {
    "type": "Terms",
    "name": "Terms and Conditions",
    "url": "https://example.com/terms-and-conditions"
  }
]

The URLs in the example are only illustrative.

Issue 95: termsOfService is only provided at an Organization level Community Feedback Requested

Scope reduction: termsOfService may now only be specified at the seller, broker or
bookingService level, and may not be provided for a specific Offer or OrderItem.

Offer or OrderItem level termsOfService is reserved for future versions of the specification.

Please comment if this is problematic.

8.7 Access control

For opportunities based at locations that have access control, it is REQUIRED that OrderItem include unique access control data within accessCode and accessToken. An accessCode SHOULD be provided for manual use in the event that an accessToken fails.

8.7.1 Text-based access control

PIN codes, Order identifiers, or simply the e-mail address of the Customer are all permissible for the purposes of access control.

In keeping with the Schema.org definition of PropertyValue, the name property SHOULD be used for the name of the property, and the description property SHOULD be used for any human-readable access control code(s).

Example 9: Example of accessCode used for a PIN Code
"accessCode": [
  {
    "type": "PropertyValue",
    "name": "PIN Code",
    "description": "1234"
  }
],
Example 10: Example of accessCode used for a booking reference
"accessCode": [
  {
    "type": "PropertyValue",
    "name": "Booking Reference",
    "description": "123456789"
  }
],

8.7.2 Image-based access control

Images to be displayed to provide access, for example access tickets rendered by the booking system, MAY be included as an ImageObject within the accessToken array.

Example 11: Example of ImageObject in accessToken
"accessToken": [
  {
    "type": "ImageObject",
    "url": "https://access.example.com/476ac24c694da79c5e33731ebbb5f1"
  }
],

8.7.3 Extension point for barcode-based access control

Access barcodes to be rendered by the Broker MAY be included as a Barcode within the accessToken array. Note that Barcode subclasses ImageObject, allowing it to contain a rendered barcode image URL in addition to the raw barcode details.

Due to the variety of barcode formats available, the specification expects the Barcode to include additional properties using a custom namespace, enough to allow the barcode to be reproduced by the Broker. This will help inform future versions of the specification.

Example 12: Example of Barcode in accessToken
"accessToken": [
  {
    "type": "Barcode",
    "text": "0123456789",
    "bookwhen:codeType": "code128"
  }
],

8.8 Extension point for including payments

In some limited circumstances it might also be desirable for an Open Booking API to act as a facade over a Booking System, Payment Processor, and Invoicing systems. For these circumstances the specification allows the API defined here to be extended via additional OPTIONAL properties in the Payment type used in B in a custom namespace, to facilitate a full payment.

Example 13: Example of Payment extension point
"payment": {
  "type": "Payment",
  "identifier": "12345678ABCD",
  "paymentMethod": "stripe:StripePayment",
  "stripe:token": "tok_KPte7942xySKBKyrBu11yEpf"
},

Note that due to the variety of business models available in the OpenActive ecosystem, conformance to this specification requires that the use of any native payment functionality in the Booking System be optional, and the Booking System MUST always accept a lack of paymentMethod as indication of an external Payment Processor being used. To maximise flexibility of this extension point, identifier and name within Payment are NOT REQUIRED if paymentMethod is specified.

9. Endpoints

This API has been defined around the concept of URL discovery. This allows resource URLs which match the naming conventions of each Booking System.

9.1 Paths and Verbs

Due to URL discovery, the paths of the endpoints below are illustrative, as the actual paths may vary provided they are consistent within an implementation.

A summary of endpoints defined by this specification is provided below:

Endpoint Name Status Resource HTTP Verb Example Path
Order Creation REQUIRED Order PUT /orders/{uuid}
Order Deletion REQUIRED Order DELETE /orders/{uuid}
OrderItem Cancellation REQUIRED Order PATCH /orders/{uuid}
Orders RPDE Feed REQUIRED Orders feed GET /orders-rpde
Order Status OPTIONAL Order GET /orders/{uuid}
Issue 98: Removal of availability check endpoint in favour of OrderQuote Community Feedback Requested

Since SessionSeries and ScheduledSession, and FacilityUse and Slot are often found in different RPDE feeds and due to Offer inheritance, constructing an object that will be useful for returning the availability of a number of slots is non-trivial.

Furthermore when there are a large number of available Slots (e.g. for squash court) or ScheduledSessions (e.g Virtual RPM classes which run very frequently), the response to "get all Slots within the FacilityUse" or "get all ScheduledSessions within a SessionSeries" can be large and will likely need to be filtered further. Given this complexity, getting just the availability for a single Slot or ScheduledSession would be preferable, however due to Offer inheritance this can also be of limited use, and quickly converges with the functionality already available from OrderQuote.

More detail of these issues can be found here: #91

As a result of the above complexity, this specification no longer includes the Opportunity API to retrieve aspects of the opportunity data, and relies on using RPDE feeds only to get the Customer to the point of selecting an item to begin the booking journey with.

Please comment here with specific use cases that would justify re-inclusion of the Opportunity API aspects here.

9.2 Request and Response

This section includes request and response examples for each of the endpoints specified in this API.

9.2.1 Order Creation

Endpoint Name Status Resource HTTP Verb Example Path
Order Creation REQUIRED Order PUT /orders/{uuid}

The Order Creation is an essential part of the Booking Flow, allowing creation of an Order via an OrderQuote.

It is not possible for opportunities from different Sellers or with different Brokers or on behalf of different Customers to be combined in the same Order.

Note that the broker property in the request body is to help the Booking System to clarify who is making request and to help them in situations such as queries to allow reconciliation of payments. It is not designed as any part of an authentication which MUST be performed outside of the JSON body of the request. Authentication tokens MUST NOT be present within the broker property of the submitted JSON.

In the situation where a middleware is used for various Brokers, independently of API key provisioning at the Booking System level, the broker property SHOULD contain the details of the end-user Broker rather than the middleware.

9.2.1.1 orderQuantity expansion

orderQuantity within OrderItem is only used during Order Creation, and is not persisted, as follows:

  • For OrderQuote request, OrderQuote response, and Order request the OrderItems MUST be unique based on the id of their acceptedOffer and the id of their orderedItem, and orderQuantity MUST be used to specify more than one is desired.
  • For Order response, and the Orders feed, the OrderItems MUST NOT include orderQuantity, and instead OrderItems must be duplicated according to their original orderQuanity, with a unique id provided for each resulting OrderItem.

This allows OrderItems to be cancelled individually once the Order has been created, and errors regarding capacity to be attributed appropriately during Order Creation.

Scenario orderQuantity status Unique OrderItem key
OrderQuote request REQUIRED Composite of acceptedOffer and orderedItem
OrderQuote response REQUIRED Composite of acceptedOffer and orderedItem
Order request REQUIRED Composite of acceptedOffer and orderedItem
Order response - id of OrderItem
Orders feed - id of OrderItem
9.2.1.2 OrderQuote

A PUT request to the Order Creation endpoint of the Booking System with an object of type OrderQuote will return a OrderQuote which represents a "dry run" of the booking, with the state of the Order identical to if request of type Order be made, without any side effects (with the exception of optional leasing).

Note that the Booking System MUST NOT store any personal information of the customer provided for the OrderQuote past the expiry of any associated lease created.

Example 14: Order Creation: OrderQuote example request
PUT /api/orders/e11429ea-467f-4270-ab62-e47368996fe8 HTTP/1.1
Host: example.com
Date: Mon, 8 Oct 2018 20:52:35 GMT
Accept: application/vnd.openactive+json; model=2.0, booking=1.0, rpde=1.0

{
  "@context": "https://openactive.io/",
  "type": "OrderQuote",
  "brokerRole": "https://openactive.io/BrokerAgent",
  "broker": {
    "type": "Organization",
    "name": "MyFitnessApp",
    "url": "https://myfitnessapp.example.com",
    "description": "A fitness app for all the community",
    "logo": {
      "type": "ImageObject",
      "url": "http://data.myfitnessapp.org.uk/images/logo.png"
    },
    "address": {
      "type": "PostalAddress",
      "streetAddress": "Alan Peacock Way",
      "addressLocality": "Village East",
      "addressRegion": "Middlesbrough",
      "postalCode": "TS4 3AE",
      "addressCountry": "GB"
    }
  },
  "customer": {
    "type": "Person",
    "email": "[email protected]",
    "telephone": "020 811 8055",
    "givenName": "Geoff",
    "familyName": "Capes"
  },
  "orderedItem": [
    {
      "type": "OrderItem",
      "orderQuantity": 1,
      "acceptedOffer": {
        "type": "Offer",
        "id": "https://example.com/events/452#/offers/878"
      },
      "orderedItem": {
        "type": "ScheduledSession",
        "id": "https://example.com/events/452/subEvents/132"
      }
    }
  ]
}

If successful the Booking System will respond with an OrderQuote:

Example 15: Order Creation: OrderQuote example success response
HTTP/1.1 200 OK
Date: Mon, 8 Oct 2018 20:52:36 GMT
Content-Type: application/vnd.openactive+json; model=2.0, booking=1.0, rpde=1.0

{
  "@context": "https://openactive.io/",
  "type": "OrderQuote",
  "taxMode": "https://openactive/TaxGross",
  "seller": {
    "type": "Organization",
    "identifier": "CRUOZWJ1",
    "name": "Better",
    "legalName": "Greenwich Leisure Limited",
    "description": "A charitable social enterprise for all the community",
    "url": "https://www.better.org.uk",
    "logo": {
      "type": "ImageObject",
      "url": "http://data.better.org.uk/images/logo.png"
    },
    "telephone": "020 3457 8700",
    "email": "[email protected]",
    "vatID": "GB 789 1234 56",
    "address": {
      "type": "PostalAddress",
      "streetAddress": "Alan Peacock Way",
      "addressLocality": "Village East",
      "addressRegion": "Middlesbrough",
      "postalCode": "TS4 3AE",
      "addressCountry": "GB"
    },
    "termsOfService": [
      {
        "type": "Terms",
        "name": "Privacy Policy",
        "url": "https://example.com/privacy-policy"
      },
      {
        "type": "Terms",
        "name": "Terms and Conditions",
        "url": "https://example.com/terms-and-conditions"
      }
    ]
  },
  "bookingService": {
    "type": "BookingService",
    "name": "Playwaze",
    "url": "http://www.playwaze.com",
    "termsOfService": [
      {
        "type": "Terms",
        "url": "https://brokerexample.com/terms.html"
      }
    ]
  },
  "lease": {
    "type": "Lease",
    "leaseExpires": "2018-10-01T11:00:00Z"
  },
  "orderedItem": [
    {
      "type": "OrderItem",
      "orderQuantity": 1,
      "orderItemStatus": "https://openactive.io/OrderConfirmed",
      "allowSimpleCancellation": true,
      "unitTaxSpecification": [
        {
          "type": "TaxChargeSpecification",
          "name": "VAT at 0% for EU transactions",
          "price": 1,
          "priceCurrency": "GBP",
          "rate": 0.2
        }
      ],
      "acceptedOffer": {
        "type": "Offer",
        "id": "https://example.com/events/452#/offers/878",
        "description": "Winger space for Speedball.",
        "name": "Speedball winger position",
        "price": 10,
        "priceCurrency": "GBP",
        "validFromBeforeStartDate": "P6D",
        "latestCancellationBeforeStartDate": "P1D"
      },
      "orderedItem": {
        "type": "ScheduledSession",
        "identifier": 123,
        "id": "https://example.com/events/452/subEvents/132",
        "eventStatus": "https://schema.org/EventScheduled",
        "maximumAttendeeCapacity": 30,
        "remainingAttendeeCapacity": 20,
        "startDate": "2018-10-30T11:00:00Z",
        "endDate": "2018-10-30T12:00:00Z",
        "duration": "PT1H",
        "superEvent": {
          "type": "SessionSeries",
          "id": "https://example.com/events/452",
          "name": "Speedball",
          "duration": "PT1H",
          "organizer": {
            "type": "Organization",
            "name": "Central Speedball Association",
            "url": "http://www.speedball-world.com"
          },
          "location": {
            "type": "Place",
            "url": "https://www.everyoneactive.com/centres/Middlesbrough-Sports-Village",
            "name": "Middlesbrough Sports Village",
            "identifier": "0140",
            "address": {
              "type": "PostalAddress",
              "streetAddress": "Alan Peacock Way",
              "addressLocality": "Village East",
              "addressRegion": "Middlesbrough",
              "postalCode": "TS4 3AE",
              "addressCountry": "GB"
            },
            "geo": {
              "type": "GeoCoordinates",
              "latitude": 54.543964,
              "longitude": -1.20978500000001
            }
          }
        }
      }
    }
  ],
  "totalPaymentDue": {
    "type": "PriceSpecification",
    "price": 5,
    "priceCurrency": "GBP"
  },
  "totalTaxSpecification": [
    {
      "type": "TaxChargeSpecification",
      "name": "VAT at 20%",
      "price": 1,
      "priceCurrency": "GBP",
      "rate": 0.2
    }
  ]
}

If there are any issues with the orderedItems provided in the OrderQuote, the Booking System MUST respond with a 409 Conflict response (as the error is expected to be resolved, and the request resubmitted, as per [RFC2616]), with error details provided against each offending OrderItem. Note that totalPaymentDue and totalTaxSpecification MUST be calculated excluding any OrderItems that include errors.

Example 16: Order Creation: OrderQuote example OrderItem error response
HTTP/1.1 409 Conflict
Date: Mon, 8 Oct 2018 20:52:36 GMT
Content-Type: application/vnd.openactive+json; model=2.0, booking=1.0, rpde=1.0

{
  "@context": "https://openactive.io/",
  "type": "OrderQuote",
  "taxMode": "https://openactive/TaxGross",
  "seller": {
    "type": "Organization",
    "identifier": "CRUOZWJ1",
    "name": "Better",
    "legalName": "Greenwich Leisure Limited",
    "description": "A charitable social enterprise for all the community",
    "url": "https://www.better.org.uk",
    "logo": {
      "type": "ImageObject",
      "url": "http://data.better.org.uk/images/logo.png"
    },
    "telephone": "020 3457 8700",
    "email": "[email protected]",
    "vatID": "GB 789 1234 56",
    "address": {
      "type": "PostalAddress",
      "streetAddress": "Alan Peacock Way",
      "addressLocality": "Village East",
      "addressRegion": "Middlesbrough",
      "postalCode": "TS4 3AE",
      "addressCountry": "GB"
    },
    "termsOfService": [
      {
        "type": "Terms",
        "name": "Privacy Policy",
        "url": "https://example.com/privacy-policy"
      },
      {
        "type": "Terms",
        "name": "Terms and Conditions",
        "url": "https://example.com/terms-and-conditions"
      }
    ]
  },
  "bookingService": {
    "type": "BookingService",
    "name": "Playwaze",
    "url": "http://www.playwaze.com",
    "termsOfService": [
      {
        "type": "Terms",
        "url": "https://brokerexample.com/terms.html"
      }
    ]
  },
  "lease": {
    "type": "Lease",
    "leaseExpires": "2018-10-01T11:00:00Z"
  },
  "orderedItem": [
    {
      "type": "OrderItem",
      "orderQuantity": 1,
      "orderItemStatus": "https://openactive.io/OrderConfirmed",
      "allowSimpleCancellation": true,
      "unitTaxSpecification": [
        {
          "type": "TaxChargeSpecification",
          "name": "VAT at 0% for EU transactions",
          "price": 1,
          "priceCurrency": "GBP",
          "rate": 0.2
        }
      ],
      "acceptedOffer": {
        "type": "Offer",
        "id": "https://example.com/events/452#/offers/878",
        "description": "Winger space for Speedball.",
        "name": "Speedball winger position",
        "price": 10,
        "priceCurrency": "GBP",
        "validFromBeforeStartDate": "P6D",
        "latestCancellationBeforeStartDate": "P1D"
      },
      "orderedItem": {
        "type": "ScheduledSession",
        "identifier": 123,
        "id": "https://example.com/events/452/subEvents/132",
        "eventStatus": "https://schema.org/EventScheduled",
        "maximumAttendeeCapacity": 30,
        "remainingAttendeeCapacity": 20,
        "startDate": "2018-10-30T11:00:00Z",
        "endDate": "2018-10-30T12:00:00Z",
        "duration": "PT1H",
        "superEvent": {
          "type": "SessionSeries",
          "id": "https://example.com/events/452",
          "name": "Speedball",
          "duration": "PT1H",
          "organizer": {
            "type": "Organization",
            "name": "Central Speedball Association",
            "url": "http://www.speedball-world.com"
          },
          "location": {
            "type": "Place",
            "url": "https://www.everyoneactive.com/centres/Middlesbrough-Sports-Village",
            "name": "Middlesbrough Sports Village",
            "identifier": "0140",
            "address": {
              "type": "PostalAddress",
              "streetAddress": "Alan Peacock Way",
              "addressLocality": "Village East",
              "addressRegion": "Middlesbrough",
              "postalCode": "TS4 3AE",
              "addressCountry": "GB"
            },
            "geo": {
              "type": "GeoCoordinates",
              "latitude": 54.543964,
              "longitude": -1.20978500000001
            }
          }
        }
      },
      "error": [
        {
          "type": "OpportunityIsFullError",
          "description": "There are no spaces remaining in this opportunity"
        }
      ]
    }
  ],
  "totalPaymentDue": {
    "type": "PriceSpecification",
    "price": 0,
    "priceCurrency": "GBP"
  },
  "totalTaxSpecification": [
    {
      "type": "TaxChargeSpecification",
      "name": "VAT at 20%",
      "price": 0,
      "priceCurrency": "GBP",
      "rate": 0.2
    }
  ]
}

If there are issues with other properties of the OrderQuote outside of orderedItem, the Booking System MUST respond with a JSON-LD response which includes only the appropriate OpenBookingError and the appropriate status code.

Example 17: Order Creation: OrderQuote example failure response
HTTP/1.1 500 Internal Server Error
Date: Mon, 8 Oct 2018 20:52:36 GMT
Content-Type: application/vnd.openactive+json; model=2.0, booking=1.0, rpde=1.0

{
  "@context": "https://openactive.io/",
  "type": "TemporarilyUnableToProduceOrderQuoteError",
  "description": "Temporary error occurred in the database"
}
9.2.1.3 Order

A PUT request to the Order Creation endpoint of the Booking System with an object of type Order will create the Order and complete the booking from the point of the view of the Booking System.

Example 18: Order Creation: Order example request
PUT /api/orders/e11429ea-467f-4270-ab62-e47368996fe8 HTTP/1.1
Host: example.com
Date: Mon, 8 Oct 2018 20:52:35 GMT
Accept: application/vnd.openactive+json; model=2.0, booking=1.0, rpde=1.0

{
  "@context": "https://openactive.io/",
  "type": "Order",
  "brokerRole": "https://openactive.io/BrokerAgent",
  "broker": {
    "type": "Organization",
    "name": "MyFitnessApp",
    "url": "https://myfitnessapp.example.com",
    "description": "A fitness app for all the community",
    "logo": {
      "type": "ImageObject",
      "url": "http://data.myfitnessapp.org.uk/images/logo.png"
    },
    "address": {
      "type": "PostalAddress",
      "streetAddress": "Alan Peacock Way",
      "addressLocality": "Village East",
      "addressRegion": "Middlesbrough",
      "postalCode": "TS4 3AE",
      "addressCountry": "GB"
    }
  },
  "customer": {
    "type": "Person",
    "email": "[email protected]",
    "telephone": "020 811 8055",
    "givenName": "Geoff",
    "familyName": "Capes"
  },
  "orderedItem": [
    {
      "type": "OrderItem",
      "orderQuantity": 1,
      "acceptedOffer": {
        "type": "Offer",
        "id": "https://example.com/events/452#/offers/878"
      },
      "orderedItem": {
        "type": "ScheduledSession",
        "id": "https://example.com/events/452/subEvents/132"
      }
    }
  ],
  "payment": {
    "type": "Payment",
    "name": "AcmeBroker Points",
    "identifier": "1234567890npduy2f"
  }
}

If successful the server will respond with the location of a newly created Order resource:

Example 19: Order Creation: Order example minimal response
HTTP/1.1 201 Created
Date: Mon, 8 Oct 2018 20:52:36 GMT
Content-Type: application/vnd.openactive+json; model=2.0, booking=1.0, rpde=1.0
Location: /api/orders/e11429ea-467f-4270-ab62-e47368996fe8

{
  "@context": "https://openactive.io/",
  "type": "Order",
  "id": "https://example.com/api/orders/e11429ea-467f-4270-ab62-e47368996fe8",
  "taxMode": "https://openactive/TaxGross",
  "seller": {
    "type": "Organization",
    "identifier": "CRUOZWJ1",
    "name": "Better",
    "legalName": "Greenwich Leisure Limited",
    "description": "A charitable social enterprise for all the community",
    "url": "https://www.better.org.uk",
    "logo": {
      "type": "ImageObject",
      "url": "http://data.better.org.uk/images/logo.png"
    },
    "telephone": "020 3457 8700",
    "email": "[email protected]",
    "vatID": "GB 789 1234 56",
    "address": {
      "type": "PostalAddress",
      "streetAddress": "Alan Peacock Way",
      "addressLocality": "Village East",
      "addressRegion": "Middlesbrough",
      "postalCode": "TS4 3AE",
      "addressCountry": "GB"
    },
    "termsOfService": [
      {
        "type": "Terms",
        "name": "Privacy Policy",
        "url": "https://example.com/privacy-policy"
      },
      {
        "type": "Terms",
        "name": "Terms and Conditions",
        "url": "https://example.com/terms-and-conditions"
      }
    ]
  },
  "bookingService": {
    "type": "BookingService",
    "name": "Playwaze",
    "url": "http://www.playwaze.com",
    "termsOfService": [
      {
        "type": "Terms",
        "url": "https://brokerexample.com/terms.html"
      }
    ]
  },
  "orderedItem": [
    {
      "type": "OrderItem",
      "id": "https://example.com/api/orders/123e4567-e89b-12d3-a456-426655440000/order-items/1234",
      "orderItemStatus": "https://openactive.io/OrderConfirmed",
      "allowSimpleCancellation": true,
      "unitTaxSpecification": [
        {
          "type": "TaxChargeSpecification",
          "name": "VAT at 0% for EU transactions",
          "price": 1,
          "priceCurrency": "GBP",
          "rate": 0.2
        }
      ],
      "acceptedOffer": {
        "type": "Offer",
        "id": "https://example.com/events/452#/offers/878",
        "description": "Winger space for Speedball.",
        "name": "Speedball winger position",
        "price": 10,
        "priceCurrency": "GBP",
        "validFromBeforeStartDate": "P6D",
        "latestCancellationBeforeStartDate": "P1D"
      },
      "orderedItem": {
        "type": "ScheduledSession",
        "identifier": 123,
        "id": "https://example.com/events/452/subEvents/132",
        "eventStatus": "https://schema.org/EventScheduled",
        "startDate": "2018-10-30T11:00:00Z",
        "endDate": "2018-10-30T12:00:00Z",
        "duration": "PT1H",
        "superEvent": {
          "type": "SessionSeries",
          "id": "https://example.com/events/452",
          "name": "Speedball",
          "organizer": {
            "type": "Organization",
            "name": "Central Speedball Association",
            "url": "http://www.speedball-world.com"
          },
          "location": {
            "type": "Place",
            "url": "https://www.everyoneactive.com/centres/Middlesbrough-Sports-Village",
            "name": "Middlesbrough Sports Village",
            "identifier": "0140",
            "address": {
              "type": "PostalAddress",
              "streetAddress": "Alan Peacock Way",
              "addressLocality": "Village East",
              "addressRegion": "Middlesbrough",
              "postalCode": "TS4 3AE",
              "addressCountry": "GB"
            },
            "geo": {
              "type": "GeoCoordinates",
              "latitude": 54.543964,
              "longitude": -1.20978500000001
            }
          }
        }
      },
      "accessToken": [
        {
          "type": "Barcode",
          "text": "0123456789"
        }
      ]
    }
  ],
  "totalPaymentDue": {
    "type": "PriceSpecification",
    "price": 5,
    "priceCurrency": "GBP"
  },
  "totalTaxSpecification": [
    {
      "type": "TaxChargeSpecification",
      "name": "VAT at 20%",
      "price": 1,
      "priceCurrency": "GBP",
      "rate": 0.2
    }
  ],
  "payment": {
    "type": "Payment",
    "name": "AcmeBroker Points",
    "identifier": "1234567890npduy2f"
  }
}

Both OrderQuote and Order responses can optionally reflect back properties provided by the Booking System for completeness, for example:

Example 20: Order Creation: Order example full response
HTTP/1.1 201 Created
Date: Mon, 8 Oct 2018 20:52:36 GMT
Content-Type: application/vnd.openactive+json; model=2.0, booking=1.0, rpde=1.0
Location: /api/orders/e11429ea-467f-4270-ab62-e47368996fe8

{
  "@context": "https://openactive.io/",
  "type": "Order",
  "id": "https://example.com/api/orders/e11429ea-467f-4270-ab62-e47368996fe8",
  "orderNumber": "AB000001",
  "taxMode": "https://openactive/TaxGross",
  "brokerRole": "https://openactive.io/BrokerAgent",
  "broker": {
    "type": "Organization",
    "name": "MyFitnessApp",
    "url": "https://myfitnessapp.example.com",
    "description": "A fitness app for all the community",
    "logo": {
      "type": "ImageObject",
      "url": "http://data.myfitnessapp.org.uk/images/logo.png"
    },
    "address": {
      "type": "PostalAddress",
      "streetAddress": "Alan Peacock Way",
      "addressLocality": "Village East",
      "addressRegion": "Middlesbrough",
      "postalCode": "TS4 3AE",
      "addressCountry": "GB"
    }
  },
  "customer": {
    "type": "Person",
    "email": "[email protected]",
    "telephone": "020 811 8055",
    "givenName": "Geoff",
    "familyName": "Capes"
  },
  "seller": {
    "type": "Organization",
    "identifier": "CRUOZWJ1",
    "name": "Better",
    "legalName": "Greenwich Leisure Limited",
    "description": "A charitable social enterprise for all the community",
    "url": "https://www.better.org.uk",
    "logo": {
      "type": "ImageObject",
      "url": "http://data.better.org.uk/images/logo.png"
    },
    "telephone": "020 3457 8700",
    "email": "[email protected]",
    "vatID": "GB 789 1234 56",
    "address": {
      "type": "PostalAddress",
      "streetAddress": "Alan Peacock Way",
      "addressLocality": "Village East",
      "addressRegion": "Middlesbrough",
      "postalCode": "TS4 3AE",
      "addressCountry": "GB"
    },
    "termsOfService": [
      {
        "type": "Terms",
        "name": "Privacy Policy",
        "url": "https://example.com/privacy-policy"
      },
      {
        "type": "Terms",
        "name": "Terms and Conditions",
        "url": "https://example.com/terms-and-conditions"
      }
    ]
  },
  "bookingService": {
    "type": "BookingService",
    "name": "Playwaze",
    "url": "http://www.playwaze.com",
    "termsOfService": [
      {
        "type": "Terms",
        "url": "https://brokerexample.com/terms.html"
      }
    ]
  },
  "orderedItem": [
    {
      "type": "OrderItem",
      "id": "https://example.com/api/orders/123e4567-e89b-12d3-a456-426655440000/order-items/1234",
      "orderItemStatus": "https://openactive.io/OrderConfirmed",
      "allowSimpleCancellation": true,
      "unitTaxSpecification": [
        {
          "type": "TaxChargeSpecification",
          "name": "VAT at 0% for EU transactions",
          "price": 1,
          "priceCurrency": "GBP",
          "rate": 0.2
        }
      ],
      "acceptedOffer": {
        "type": "Offer",
        "id": "https://example.com/events/452#/offers/878",
        "description": "Winger space for Speedball.",
        "name": "Speedball winger position",
        "price": 10,
        "priceCurrency": "GBP",
        "validFromBeforeStartDate": "P6D",
        "latestCancellationBeforeStartDate": "P1D"
      },
      "orderedItem": {
        "type": "ScheduledSession",
        "identifier": 123,
        "id": "https://example.com/events/452/subEvents/132",
        "eventStatus": "https://schema.org/EventScheduled",
        "startDate": "2018-10-30T11:00:00Z",
        "endDate": "2018-10-30T12:00:00Z",
        "duration": "PT1H",
        "superEvent": {
          "type": "SessionSeries",
          "id": "https://example.com/events/452",
          "name": "Speedball",
          "organizer": {
            "type": "Organization",
            "name": "Central Speedball Association",
            "url": "http://www.speedball-world.com"
          },
          "location": {
            "type": "Place",
            "url": "https://www.everyoneactive.com/centres/Middlesbrough-Sports-Village",
            "name": "Middlesbrough Sports Village",
            "identifier": "0140",
            "address": {
              "type": "PostalAddress",
              "streetAddress": "Alan Peacock Way",
              "addressLocality": "Village East",
              "addressRegion": "Middlesbrough",
              "postalCode": "TS4 3AE",
              "addressCountry": "GB"
            },
            "geo": {
              "type": "GeoCoordinates",
              "latitude": 54.543964,
              "longitude": -1.20978500000001
            }
          }
        }
      },
      "accessToken": [
        {
          "type": "Barcode",
          "text": "0123456789"
        }
      ]
    }
  ],
  "totalPaymentDue": {
    "type": "PriceSpecification",
    "price": 5,
    "priceCurrency": "GBP"
  },
  "totalTaxSpecification": [
    {
      "type": "TaxChargeSpecification",
      "name": "VAT at 20%",
      "price": 1,
      "priceCurrency": "GBP",
      "rate": 0.2
    }
  ],
  "payment": {
    "type": "Payment",
    "name": "AcmeBroker Points",
    "identifier": "1234567890npduy2f"
  }
}

If there are issues any properties of the Order, including the orderedItem, the Booking System MUST respond with a 400 Bad Request response which includes only the appropriate Error. The Broker is expected to retry the OrderQuote to get specific orderedItem level errors.

9.2.2 Order Deletion

Endpoint Name Status Resource HTTP Verb Example Path
Order Deletion REQUIRED Order DELETE /orders/{uuid}

Deleting an Order allows for the whole Booking Flow to be reversed for fatal error and testing scenarios.

When an Order is deleted the Booking System must soft delete it as if it was not created, rolling back the Booking Flow.

The Broker MUST NOT use Order Deletion for cancellation, but instead only use it in the unusual case of a fatal error or testing. An appropriate approach for cancellation is defined elsewhere.

The corresponding Order MUST be marked as deleted in the Orders feed.

Example 21: Order Deletion: example request
DELETE /api/orders/e11429ea-467f-4270-ab62-e47368996fe8 HTTP/1.1
Host: example.com
Date: Mon, 8 Oct 2018 20:52:35 GMT
Accept: application/vnd.openactive+json; model=2.0, booking=1.0, rpde=1.0
Example 22: Order Deletion: example response
HTTP/1.1 204 No Content
Date: Mon, 8 Oct 2018 20:52:36 GMT
Content-Type: application/vnd.openactive+json; model=2.0, booking=1.0, rpde=1.0

9.2.3 OrderItem Cancellation

Endpoint Name Status Resource HTTP Verb Example Path
OrderItem Cancellation REQUIRED Order PATCH /orders/{uuid}

All other properties except the id and orderItemStatus inside the OrderItem MUST be ignored. OrderItems omitted from the PATCH request MUST also be ignored.

A dry-run of the call MAY be used to inform the user of the refund they can expect (note the response represents only a partial OrderQuote, which SHOULD be combined with the Order stored in the Broker to create a full view).

Example 23: OrderItem Cancellation: example OrderQuote request
PATCH /api/orders/e11429ea-467f-4270-ab62-e47368996fe8 HTTP/1.1
Host: example.com
Date: Mon, 8 Oct 2018 20:52:35 GMT
Accept: application/vnd.openactive+json; model=2.0, booking=1.0, rpde=1.0

{
  "@context": "https://openactive.io/",
  "type": "OrderQuote",
  "orderedItem": [
    {
      "type": "OrderItem",
      "id": "https://example.com/api/orders/123e4567-e89b-12d3-a456-426655440000/order-items/1234",
      "orderItemStatus": "https://openactive.io/CustomerCancelled"
    }
  ]
}
Example 24: OrderItem Cancellation: example OrderQuote success response
HTTP/1.1 200 OK
Date: Mon, 8 Oct 2018 20:52:36 GMT
Content-Type: application/vnd.openactive+json; model=2.0, booking=1.0, rpde=1.0
Location: /api/orders/e11429ea-467f-4270-ab62-e47368996fe8

{
  "@context": "https://openactive.io/",
  "type": "OrderQuote",
  "id": "https://example.com/api/orders/e11429ea-467f-4270-ab62-e47368996fe8",
  "taxMode": "https://openactive/TaxGross",
  "orderedItem": [
    {
      "type": "OrderItem",
      "id": "https://example.com/api/orders/123e4567-e89b-12d3-a456-426655440000/order-items/1234",
      "orderItemStatus": "https://openactive.io/CustomerCancelled"
    }
  ],
  "totalPaymentDue": {
    "type": "PriceSpecification",
    "price": 0,
    "priceCurrency": "GBP"
  },
  "totalTaxSpecification": [
    {
      "type": "TaxChargeSpecification",
      "name": "VAT at 20%",
      "price": 0,
      "priceCurrency": "GBP",
      "rate": 0.2
    }
  ]
}

Whether or not the dry-run was completed, the cancellation may be requested:

Example 25: OrderItem Cancellation: example Order request
PATCH /api/orders/e11429ea-467f-4270-ab62-e47368996fe8 HTTP/1.1
Host: example.com
Date: Mon, 8 Oct 2018 20:52:35 GMT
Accept: application/vnd.openactive+json; model=2.0, booking=1.0, rpde=1.0

{
  "@context": "https://openactive.io/",
  "type": "Order",
  "orderedItem": [
    {
      "type": "OrderItem",
      "id": "https://example.com/api/orders/123e4567-e89b-12d3-a456-426655440000/order-items/1234",
      "orderItemStatus": "https://openactive.io/CustomerCancelled"
    }
  ]
}

The cancellation request succeeds and fails atomically.

Example 26: OrderItem Cancellation: example Order success response
HTTP/1.1 204 No Content
Date: Mon, 8 Oct 2018 20:52:36 GMT
Content-Type: application/vnd.openactive+json; model=2.0, booking=1.0, rpde=1.0

9.2.4 Orders RPDE Feed

Endpoint Name Status Resource HTTP Verb Example Path
Orders RPDE Feed REQUIRED Orders feed GET /orders-rpde
Example 27: Order RPDE Feed: example request
GET /api/orders-rpde HTTP/1.1
Host: example.com
Date: Mon, 8 Oct 2018 20:52:35 GMT
Accept: application/vnd.openactive+json; model=2.0, booking=1.0, rpde=1.0
Example 28: Order RPDE Feed: example response
HTTP/1.1 200 OK
Date: Mon, 8 Oct 2018 20:52:36 GMT
Content-Type: application/vnd.openactive+json; model=2.0, booking=1.0, rpde=1.0

{
  "next": "/api/orders-rpde?afterTimestamp=1521565719&afterId=e11429ea-467f-4270-ab62-e47368996fe8",
  "items": [
    {
      "state": "updated",
      "kind": "Order",
      "id": "e11429ea-467f-4270-ab62-e47368996fe8",
      "modified": 1521565719,
      "data": {
        "@context": "https://openactive.io/",
        "type": "Order",
        "id": "https://example.com/api/orders/e11429ea-467f-4270-ab62-e47368996fe8",
        "taxMode": "https://openactive/TaxGross",
        "orderedItem": [
          {
            "type": "OrderItem",
            "id": "https://example.com/api/orders/123e4567-e89b-12d3-a456-426655440000/order-items/1234",
            "orderItemStatus": "https://openactive.io/OrderConfirmed",
            "allowSimpleCancellation": true,
            "unitTaxSpecification": [
              {
                "type": "TaxChargeSpecification",
                "name": "VAT at 0% for EU transactions",
                "price": 1,
                "priceCurrency": "GBP",
                "rate": 0.2
              }
            ],
            "acceptedOffer": {
              "type": "Offer",
              "id": "https://example.com/events/452#/offers/878",
              "description": "Winger space for Speedball.",
              "name": "Speedball winger position",
              "price": 10,
              "priceCurrency": "GBP",
              "validFromBeforeStartDate": "P6D",
              "latestCancellationBeforeStartDate": "P1D"
            },
            "orderedItem": {
              "type": "ScheduledSession",
              "id": "https://example.com/events/452/subEvents/132"
            }
          }
        ],
        "totalPaymentDue": {
          "type": "PriceSpecification",
          "price": 5,
          "priceCurrency": "GBP"
        },
        "totalTaxSpecification": [
          {
            "type": "TaxChargeSpecification",
            "name": "VAT at 20%",
            "price": 1,
            "priceCurrency": "GBP",
            "rate": 0.2
          }
        ]
      }
    }
  ]
}

9.2.5 Order Status

A Booking System MAY provide an endpoint to allow a Broker to retrieve complete Orders. In future versions of the specification, endpoints may be provided which permit a Broker to see all Orders created by a Customer. In this current implementation, the Broker MUST keep its own record of Orders as described elsewhere in this specification, and not rely on this OPTIONAL Order Status endpoint.

Endpoint Name Status Resource HTTP Verb Example Path
Order Status OPTIONAL Order GET /orders/{uuid}
Example 29: Order Status: example request
GET /api/orders/e11429ea-467f-4270-ab62-e47368996fe8 HTTP/1.1
Host: example.com
Date: Mon, 8 Oct 2018 20:52:35 GMT
Accept: application/vnd.openactive+json; model=2.0, booking=1.0, rpde=1.0
Example 30: Order Status: example response
HTTP/1.1 200 OK
Date: Mon, 8 Oct 2018 20:52:36 GMT
Content-Type: application/vnd.openactive+json; model=2.0, booking=1.0, rpde=1.0
Location: /api/orders/e11429ea-467f-4270-ab62-e47368996fe8

{
  "@context": "https://openactive.io/",
  "type": "Order",
  "id": "https://example.com/api/orders/e11429ea-467f-4270-ab62-e47368996fe8",
  "taxMode": "https://openactive/TaxGross",
  "brokerRole": "https://openactive.io/BrokerAgent",
  "broker": {
    "type": "Organization",
    "name": "MyFitnessApp",
    "url": "https://myfitnessapp.example.com",
    "description": "A fitness app for all the community",
    "logo": {
      "type": "ImageObject",
      "url": "http://data.myfitnessapp.org.uk/images/logo.png"
    },
    "address": {
      "type": "PostalAddress",
      "streetAddress": "Alan Peacock Way",
      "addressLocality": "Village East",
      "addressRegion": "Middlesbrough",
      "postalCode": "TS4 3AE",
      "addressCountry": "GB"
    }
  },
  "customer": {
    "type": "Person",
    "email": "[email protected]",
    "telephone": "020 811 8055",
    "givenName": "Geoff",
    "familyName": "Capes"
  },
  "seller": {
    "type": "Organization",
    "identifier": "CRUOZWJ1",
    "name": "Better",
    "legalName": "Greenwich Leisure Limited",
    "description": "A charitable social enterprise for all the community",
    "url": "https://www.better.org.uk",
    "logo": {
      "type": "ImageObject",
      "url": "http://data.better.org.uk/images/logo.png"
    },
    "telephone": "020 3457 8700",
    "email": "[email protected]",
    "vatID": "GB 789 1234 56",
    "address": {
      "type": "PostalAddress",
      "streetAddress": "Alan Peacock Way",
      "addressLocality": "Village East",
      "addressRegion": "Middlesbrough",
      "postalCode": "TS4 3AE",
      "addressCountry": "GB"
    },
    "termsOfService": [
      {
        "type": "Terms",
        "name": "Privacy Policy",
        "url": "https://example.com/privacy-policy"
      },
      {
        "type": "Terms",
        "name": "Terms and Conditions",
        "url": "https://example.com/terms-and-conditions"
      }
    ]
  },
  "bookingService": {
    "type": "BookingService",
    "name": "Playwaze",
    "url": "http://www.playwaze.com",
    "termsOfService": [
      {
        "type": "Terms",
        "url": "https://brokerexample.com/terms.html"
      }
    ]
  },
  "orderedItem": [
    {
      "type": "OrderItem",
      "id": "https://example.com/api/orders/123e4567-e89b-12d3-a456-426655440000/order-items/1234",
      "orderItemStatus": "https://openactive.io/OrderConfirmed",
      "allowSimpleCancellation": true,
      "unitTaxSpecification": [
        {
          "type": "TaxChargeSpecification",
          "name": "VAT at 0% for EU transactions",
          "price": 1,
          "priceCurrency": "GBP",
          "rate": 0.2
        }
      ],
      "acceptedOffer": {
        "type": "Offer",
        "id": "https://example.com/events/452#/offers/878",
        "description": "Winger space for Speedball.",
        "name": "Speedball winger position",
        "price": 10,
        "priceCurrency": "GBP",
        "validFromBeforeStartDate": "P6D",
        "latestCancellationBeforeStartDate": "P1D"
      },
      "orderedItem": {
        "type": "ScheduledSession",
        "identifier": 123,
        "id": "https://example.com/events/452/subEvents/132",
        "eventStatus": "https://schema.org/EventScheduled",
        "startDate": "2018-10-30T11:00:00Z",
        "endDate": "2018-10-30T12:00:00Z",
        "duration": "PT1H",
        "superEvent": {
          "type": "SessionSeries",
          "id": "https://example.com/events/452",
          "name": "Speedball",
          "organizer": {
            "type": "Organization",
            "name": "Central Speedball Association",
            "url": "http://www.speedball-world.com"
          },
          "location": {
            "type": "Place",
            "url": "https://www.everyoneactive.com/centres/Middlesbrough-Sports-Village",
            "name": "Middlesbrough Sports Village",
            "identifier": "0140",
            "address": {
              "type": "PostalAddress",
              "streetAddress": "Alan Peacock Way",
              "addressLocality": "Village East",
              "addressRegion": "Middlesbrough",
              "postalCode": "TS4 3AE",
              "addressCountry": "GB"
            },
            "geo": {
              "type": "GeoCoordinates",
              "latitude": 54.543964,
              "longitude": -1.20978500000001
            }
          }
        }
      },
      "accessToken": [
        {
          "type": "Barcode",
          "text": "0123456789"
        }
      ]
    }
  ],
  "totalPaymentDue": {
    "type": "PriceSpecification",
    "price": 5,
    "priceCurrency": "GBP"
  },
  "totalTaxSpecification": [
    {
      "type": "TaxChargeSpecification",
      "name": "VAT at 20%",
      "price": 1,
      "priceCurrency": "GBP",
      "rate": 0.2
    }
  ],
  "payment": {
    "type": "Payment",
    "name": "AcmeBroker Points",
    "identifier": "1234567890npduy2f"
  }
}

9.2.6 URL discovery

The URL for the initial Order Creation action MUST be discoverable by parsing the OpenBookingAction defined in the potentialAction property of the published Dataset embedded within dataset site.

The EntryPoint object MUST state the url of the Order Creation endpoint, and MUST contain the encodingType and the httpMethod as PUT.

Example 31: Example of a potentialAction
"potentialAction": {
  "@type": "OpenBookingAction",
  "target": {
    "@type": "EntryPoint",
    "urlTemplate": "https://example.com/api/orders/{uuid}",
    "encodingType": [
      "application/vnd.openactive+jsonld; model=2.0, booking=1.0"
    ],
    "httpMethod": "PUT"
  },
  "supportingData": {
    "@type": "DataFeed",
    "distribution": [
      {
        "@type": "DataDownload",
        "name": "Order",
        "additionalType": "https://schema.org/Order",
        "encodingFormat": [
          "application/vnd.openactive+json; model=2.0, booking=1.0, rpde=1.0"
        ],
        "contentUrl": "https://example.com/api/feeds/offers"
      }
    ]
  }
}

Since discovery of URLs is an implicit part of the specification, Brokers SHOULD avoid hard-coding URLs for specific actions into their applications and client libraries. Brokers SHOULD use most applicable URL discovered at the point in the workflow they are currently in - e.g. not use a cached URL that has been previously successfully used. They should also not attempt to create URLs for a specific action based on patterns within previously used URLs.

It is the responsibility of Booking Systems to advertise the URL for further activity in the booking workflow at the point of need. For example they MUST advertise the URL for the OrderItem resource in the Orders Orders feed by including the id property within the OrderItem.

Issue 94: URL Discovery using the Dataset Site Community Feedback Requested

The specification proposes to centralise URL discovery in the Dataset site, along with the feeds themselves.

Feedback on this approach welcome.

9.2.6.1 Example
Example 32: Example of Dataset site embedded JSON-LD
<script type="application/ld+json">
{
  "@context": [
    "https://schema.org",
    "https://openactive.io/"
  ],
  "@type": "Dataset",
  "id": "https://data.example.com/",
  "url": "https://data.example.com/",
  "name": "Acme Leisure Sessions and Facilities",
  "description": "Near real-time availability and rich descriptions relating to the sessions and facilities available from Acme Leisure, published using the OpenActive Modelling Specification 2.0.",
  "keywords": [
    "Sessions",
    "Facilities",
    "Activities",
    "Sports",
    "Physical Activity",
    "OpenActive"
  ],
  "license": "https://creativecommons.org/licenses/by/4.0/",
  "distribution": [
    {
      "additionalType": "https://openactive.io/SessionSeries",
      "encodingFormat": "application/vnd.openactive+json; rpde=1.0, model=2.0",
      "contentUrl": "https://example.com/open-api/feeds/session-series",
      "@type": "DataDownload",
      "name": "SessionSeries"
    },
    {
      "additionalType": "https://openactive.io/ScheduledSession",
      "encodingFormat": "application/vnd.openactive+json; rpde=1.0, model=2.0",
      "contentUrl": "https://example.com/open-api/feeds/sessions",
      "@type": "DataDownload",
      "name": "ScheduledSession"
    },
    {
      "additionalType": "https://openactive.io/FacilityUse",
      "encodingFormat": "application/vnd.openactive+json; rpde=1.0, model=2.0",
      "contentUrl": "https://example.com/open-api/feeds/facility-uses",
      "@type": "DataDownload",
      "name": "FacilityUse"
    },
    {
      "additionalType": "https://openactive.io/Slot",
      "encodingFormat": "application/vnd.openactive+json; rpde=1.0, model=2.0",
      "contentUrl": "https://example.com/open-api/feeds/slots",
      "@type": "DataDownload",
      "name": "Slot"
    }
  ],
  "potentialAction": [
    {
      "@type": "OpenBookingAction",
      "target": {
        "@type": "EntryPoint",
        "urlTemplate": "https://example.com/api/orders/{uuid}",
        "encodingType": [
          "application/vnd.openactive+jsonld; model=2.0, booking=1.0"
        ],
        "httpMethod": "PUT"
      },
      "supportingData": {
        "@type": "DataFeed",
        "distribution": [
          {
            "@type": "DataDownload",
            "name": "Order",
            "additionalType": "https://schema.org/Order",
            "encodingFormat": [
              "application/vnd.openactive+json; model=2.0, booking=1.0, rpde=1.0"
            ],
            "contentUrl": "https://example.com/api/feeds/offers"
          }
        ]
      }
    }
  ],
  "discussionUrl": "https://github.com/example/opendata/issues",
  "documentation": "https://docs.example.com/",
  "inLanguage": "en-GB",
  "publisher": {
    "legalName": "Acme Leisure Ltd",
    "logo": {
      "@type": "ImageObject",
      "url": "https://example.com/logo.png"
    },
    "email": "[email protected]",
    "@type": "Organization",
    "name": "Acme Leisure",
    "description": "We are able to continually reinvest in facilities, products and importantly people.",
    "url": "https://example.com/"
  },
  "datePublished": "2019-02-20T13:22:28.7743707Z",
  "schemaVersion": "https://www.openactive.io/modelling-opportunity-data/2.0/"
}
</script>

10. Model

These models are designed compatible with [Modelling-Opportunity-Data], and includes conformance criteria that are both stricter and more relaxed when existing models are used in the context of this specification.

10.1 Order Model

10.1.1 schema:Order

Property Broker Request Booking System Response Orders feed Type Notes
@type REQUIRED REQUIRED REQUIRED schema:Text Order
@id - REQUIRED REQUIRED URI A URI providing a unique identifier for the resource, which MUST match the PUT URL containing the UUID
schema:identifier - OPTIONAL - schema:Integer, schema:Text, schema:PropertyValue or an array of schema:PropertyValue A local identifier for the tax charge, used to identify the type of tax within the Booking System. Note an array of PropertyValue can be used to provide multiple identifiers where available. -
schema:seller - REQUIRED - schema:Organization The organisation providing access to events or facilities via a Booking System. e.g. a leisure provider running yoga classes.
oa:taxMode - REQUIRED REQUIRED oa:TaxMode Either https://openactive/TaxNet or https://openactive/TaxGross
oa:broker REQUIRED OPTIONAL - schema:Organization The organisation or developer providing an application that allows Customers to make bookings. Those applications will be clients of the API defined in this specification.
oa:brokerRole REQUIRED OPTIONAL - oa:BrokerType Either https://openactive/AgentBroker, https://openactive/ResellerBroker or https://openactive/NoBroker.
schema:orderNumber - OPTIONAL - schema:Text The Customer-facing identifier of the Order.
schema:customer REQUIRED - - schema:Organization or a schema:Person The person or organization purchasing the Order. MUST be either an .
oa:bookingService - REQUIRED - oa:BookingService Details about the Booking System
schema:orderedItem REQUIRED REQUIRED REQUIRED Array of schema:OrderItem The items that constitute the Order
schema:totalPaymentDue REQUIRED REQUIRED REQUIRED schema:PriceSpecification The total price of the Order, which includes or excludes tax depending on the taxMode.
oa:totalTaxSpecification - REQUIRED REQUIRED Array of oa:TaxChargeSpecification Breakdown of tax payable for the Order.
oa:payment REQUIRED OPTIONAL - oa:Payment The payment associated with the Order by the Broker
oa:orderApprovalRequestNote RECOMMENDED OPTIONAL - schema:Text A note supplied to the Seller in the case where the Order requires approval.

The Booking System is NOT REQUIRED to reflect the properties provided at B back to the Broker, so the Broker must store any details sent alongside the Order response.

10.1.2 oa:OrderQuote

OrderQuote subclasses Order, and includes of the above properties as specified for Order with the addition of oa:lease, without oa:payment, and with totalPaymentDue only required in the response:

Property Broker Request Booking System Response Type Notes
@type REQUIRED REQUIRED schema:Text OrderQuote
oa:lease - OPTIONAL oa:Lease The lease on the OrderItems which lasts for the duration specified by the Booking System.
oa:payment - - oa:Payment payment is not expected for OrderQuote.
schema:totalPaymentDue - REQUIRED REQUIRED schema:PriceSpecification
oa:orderApprovalRequestNote - - schema:Text orderApprovalRequestNote is not expected for OrderQuote.

10.1.3 schema:Organization for seller

The seller MUST be a schema:Organization with the following properties, in order for tax receipts to be successfully generated.

The seller is specified with each Order to account for scenarios where multiple Seller brands are in use.

Property Broker Request Booking System Response Type Notes
@type - REQUIRED schema:Text Organization
schema:identifier - OPTIONAL schema:Text The identifier of the Seller used by the Booking System.
schema:name - REQUIRED schema:Text Trading name of the Seller.
schema:legalName - REQUIRED schema:Text Legal name of the Seller, used on tax receipts.
schema:email - RECOMMENDED schema:Text Email address used to contact the Seller.
oa:taxMode - REQUIRED oa:TaxMode Either https://openactive/TaxNet or https://openactive/TaxGross
schema:vatID - REQUIRED schema:Text The Value-added Tax ID of the of the Seller.
schema:telephone - OPTIONAL schema:Text Telephone number of the Seller.
schema:url - RECOMMENDED schema:URL The URL of the website of the Seller.
schema:logo - RECOMMENDED schema:ImageObject Logo of the Seller, used on tax receipts.
schema:address - REQUIRED schema:PostalAddress Address of the Seller, used on tax receipts.
schema:termsOfService - OPTIONAL Array of oa:Terms The terms of service of the Seller.

10.1.4 schema:Organization for broker

The broker MUST be a schema:Organization with the following properties, in order for the Booking System to represent the third-party booking.

The broker is specified with each Order to account for scenarios where multiple Broker brands are in use.

Property Broker Request Booking System Response Type Notes
@type REQUIRED - schema:Text Organization
schema:identifier OPTIONAL - schema:Text The identifier of the brand used by the Broker.
schema:name REQUIRED - schema:Text Name of the Broker.
schema:email RECOMMENDED - schema:Text Support email address used to contact the Broker.
schema:telephone OPTIONAL - schema:Text Support telephone number of the Broker.
schema:url RECOMMENDED - schema:URL The URL of the website of the Broker.
schema:logo RECOMMENDED - schema:ImageObject Logo of the Broker, used within the Booking System.
schema:termsOfService OPTIONAL - Array of oa:Terms The terms of service of the Broker.

10.1.5 schema:OrderItem

Property Broker Request Booking System Response Type Notes
@type REQUIRED REQUIRED schema:Text OrderItem
@id REQUIRED REQUIRED schema:URL A URI providing a unique identifier for the OrderItem
schema:orderItemStatus - REQUIRED schema:OrderStatus Either https://openactive.io/SellerCancelled, https://openactive.io/CustomerCancelled, or https://openactive.io/OrderConfirmed
schema:orderQuanity REQUIRED REQUIRED schema:Integer MUST NOT be used for Order responses.
oa:allowSimpleCancellation - RECOMMENDED schema:Boolean Whether the event can be cancelled.
oa:unitTaxSpecification - REQUIRED Array of oa:TaxChargeSpecification Breakdown of tax payable for the OrderItem.
schema:acceptedOffer REQUIRED REQUIRED schema:Offer The offer from the associated orderedItem that has been selected by the Customer. The price of which includes or excludes tax depending on the taxMode of the Order.
schema:orderedItem REQUIRED REQUIRED oa:ScheduledSession, oa:Slot, oa:HeadlineEvent, schema:Event or schema:CourseInstance The specific bookable Thing that has been selected by the Customer. See the [Modelling-Opportunity-Data] for more information on these types. Note that the Broker Request and Orders feed only requires id within these objects to be included, all other properties are ignored.
schema:additionalProperty - RECOMMENDED Array of schema:PropertyValue PropertyValue that contains a text value useful for reconciliation.
schema:accessCode - RECOMMENDED Array of schema:PropertyValue PropertyValue that contains a text value usable for entrance. Not applicable for an OrderQuote.
oa:accessToken - RECOMMENDED Array of schema:ImageObject ImageObject or Barcode that contains reference to an asset (e.g. Barcode, QR code image or PDF) usable for entrance. Not applicable for an OrderQuote.
oa:error - REQUIRED Array of oa:OpenBookingError Array of errors related to the OrderItem being included in the Order, only applicable for an OrderQuote.
oa:cancellationMessage - OPTIONAL schema:Text A message set by the Seller in the event of opportunity cancellation, only applicable for an Order and where the OrderItem has orderItemStatus set to https://openactive.io/SellerCancelled

Note that schema:orderQuantity is only used during the booking flow, and MUST NOT be used within the completed Order.

10.1.6 schema:Offer

Use of properties of Offer defined in the [Modelling-Opportunity-Data] are also encouraged.

Property Broker Request Booking System Response Type Notes
@type REQUIRED REQUIRED schema:Text Offer
@id REQUIRED REQUIRED schema:URL A URI providing a unique identifier for the Offer
schema:price - REQUIRED schema:Float The offer price available to participants, which includes or excludes tax depending on the taxMode of the Order.
schema:priceCurrency - REQUIRED schema:Text The currency of the price. Specified as a 3-letter ISO 4217 value. If a PriceSpecification has a zero price, then this property is not required. Otherwise the priceCurrency MUST be specified.
schema:name - RECOMMENDED schema:Text The name of the offer suitable for display to participants.
oa:availableChannel REQUIRED REQUIRED Array of oa:AvailableChannelType Can include https://openactive.io/OpenBookingPrepayment, https://openactive.io/TelephoneAdvanceBooking, https://openactive.io/TelephonePrepayment, https://openactive.io/OnlinePrepayment
oa:validFromBeforeStartDate - OPTIONAL schema:Duration The duration before the startDate for which this Offer is valid, given in [ISO8601] format. This is a relative equivalent of schema:validFrom, to allow for Offer inheritance.
oa:latestCancellationBeforeStartDate - OPTIONAL schema:Duration The duration before the startDate during which this Offer may not be cancelled, given in [ISO8601] format.

Note that for an Offer to be bookable under this specification, availableChannel must include https://openactive.io/OpenBookingPrepayment.

10.1.7 schema:OfferOverride

OfferOverride subclasses Offer.

Property Broker Request Booking System Response Type Notes
@type REQUIRED REQUIRED schema:Text Offer
@id REQUIRED REQUIRED schema:URL A URI providing a unique identifier for the Offer
schema:price REQUIRED REQUIRED schema:Float The offer price available to participants, which includes or excludes tax depending on the taxMode of the Order.
schema:priceCurrency REQUIRED REQUIRED schema:Text The currency of the price. Specified as a 3-letter ISO 4217 value. If a PriceSpecification has a zero price, then this property is not required. Otherwise the priceCurrency MUST be specified.
schema:name RECOMMENDED RECOMMENDED schema:Text The name of the offer suitable for display to participants.
oa:availableChannel - - - Not included in OfferOverride.
oa:validFromBeforeStartDate OPTIONAL OPTIONAL schema:Duration The duration before the startDate for which this Offer is valid, given in [ISO8601] format. This is a relative equivalent of schema:validFrom, to allow for Offer inheritance.
oa:latestCancellationBeforeStartDate OPTIONAL OPTIONAL schema:Duration The duration before the startDate during which this Offer may not be cancelled, given in [ISO8601] format.

10.1.8 schema:DynamicOffer

DynamicOffer subclasses Offer.

Property Broker Request Booking System Response Type Notes
@type REQUIRED REQUIRED schema:Text Offer
schema:identifier REQUIRED REQUIRED schema:Text Used consistently in order to allow the Seller to reconcile opportunities booked using a particular type of DynamicOffer out-of-band.
schema:name RECOMMENDED RECOMMENDED schema:Text The name of the offer suitable for display to participants.
oa:validFromBeforeStartDate OPTIONAL OPTIONAL schema:Duration The duration before the startDate for which this Offer is valid, given in [ISO8601] format. This is a relative equivalent of schema:validFrom, to allow for Offer inheritance.
oa:latestCancellationBeforeStartDate OPTIONAL OPTIONAL schema:Duration The duration before the startDate during which this Offer may not be cancelled, given in [ISO8601] format.
schema:price - - - Not included in DynamicOffer.
schema:priceCurrency - - - Not included in DynamicOffer.
oa:availableChannel - - - Not included in OfferOverride.

10.1.9 schema:Person

For business-to-consumer transactions, the customer MUST be a schema:Person.

Property Broker Request Booking System Response Type Notes
@type REQUIRED - schema:Text Person
schema:identifier OPTIONAL - schema:Text The identifier of the Customer used by the Broker and/or Payment Provider.
schema:email REQUIRED - schema:Text Email address used to uniquely identify the Customer.
schema:givenName RECOMMENDED - schema:Text Name of the Customer.
schema:familyName RECOMMENDED - schema:Text Name of the Customer.
schema:telephone OPTIONAL - schema:Text Telephone number of the Customer.

Note that the Person is not sent to the Broker, only received by the Booking System, to avoid any inadvertent data sharing between Brokers when the properties of the Person are updated. It is strongly RECOMMENDED that the personal data received by the Booking System be treated as a "guest checkout" and is not used to create a Customer record in the Booking System.

In this current iteration of the Booking API specification, the programmatic creation of Customer records on the Booking System by the Broker is considered to be out of scope.

For conformance with this specification the Booking System MUST NOT require more data about the Customer than is specified as REQUIRED properties above.

Issue 99: Only require e-mail for b2c booking Community Feedback Requested

Following conventions of e-commerce guest checkouts that require only e-mail address (e.g. https://stripe.com/payments/checkout), and in the spirit of GDPR only requiring a minimum of personal data to be captured for provision of a service, this specification has only "e-mail address" as a required field.

This also removes complexity in some systems where an e-mail address may be shared by multiple family members, as the Person is not named.

Please comment with use cases that require more than just "e-mail" to be captured for a guest checkout transaction.

10.1.10 schema:Organization for customer

For business-to-business transactions, the customer MUST be a schema:Organization with the following properties.

Property Broker Request Booking System Response Type Notes
@type REQUIRED - schema:Text Organization
schema:identifier OPTIONAL - schema:Text The identifier of the Customer used by the Broker and/or Payment Provider.
schema:email REQUIRED - schema:Text Email address used to uniquely identify the Customer.
schema:name REQUIRED - schema:Text Name of the Customer.
schema:telephone OPTIONAL - schema:Text Telephone number of the Customer.
schema:address REQUIRED - schema:PostalAddress Address of the business Customer.

10.1.11 schema:PostalAddress

For schema:Organization when used for customer or seller, the following properties are required.

Property Broker Request Booking System Response Type Notes
@type REQUIRED - schema:Text PostalAddress
schema:streetAddress REQUIRED - schema:Text Street address
schema:addressLocality REQUIRED - schema:Text Town or other area
schema:addressRegion REQUIRED - schema:Text Region
schema:postalCode REQUIRED - schema:Text Postcode
schema:addressCountry REQUIRED - schema:Text ISO 3166-1 alpha-2 country code.

10.1.12 oa:Lease

Used for lease. An object is provided for extensibility.

Property Broker Request Booking System Response Type Notes
@type - REQUIRED schema:Text Lease
oa:leaseExpires - REQUIRED schema:DateTime Expiry DateTime of the lease in [ISO8601] format
schema:identifier - OPTIONAL schema:Text The identifier of the payment held by the Broker and/or Payment Provider.

10.1.13 schema:PriceSpecification

Used for totalPaymentDue.

Property Broker Request Booking System Response Type Notes
@type - REQUIRED schema:Text PriceSpecification
schema:price - REQUIRED schema:Float The total amount.
schema:priceCurrency - REQUIRED schema:Text The currency of the price. Specified as a 3-letter ISO 4217 value. If a PriceSpecification has a zero price, then this property is not required. Otherwise the priceCurrency MUST be specified.

10.1.14 oa:TaxChargeSpecification

TaxChargeSpecification subclasses PriceSpecification, and so includes the same properties and a few additional ones. These are listed below.

TaxChargeSpecification are intended to be rendered to the Customer as-is, and are not designed to be manipulated by the Broker.

Property Broker Request Booking System Response Type Notes
@type - REQUIRED schema:Text TaxChargeSpecification
schema:price - REQUIRED schema:Float The monetary value of the tax charge.
schema:priceCurrency - REQUIRED schema:Text The currency of the tax charge. Specified as a 3-letter ISO 4217 value. If an tax charge has a zero price, then this property is not required. Otherwise the priceCurrency MUST be specified.
schema:identifier - OPTIONAL schema:Integer, schema:Text, schema:PropertyValue or an array of schema:PropertyValue A local identifier for the tax charge, used to identify the type of tax within the Booking System. Note an array of PropertyValue can be used to provide multiple identifiers where available.
schema:name - REQUIRED schema:Text The name of the tax charge, e.g. "VAT at 0% for EU transactions"
oa:rate - OPTIONAL schema:Float The rate of VAT.

10.1.15 oa:Payment

Used for payment.

Property Broker Request Booking System Response Type Notes
@type - REQUIRED schema:Text Payment
schema:name - OPTIONAL schema:Text Optional free text description of the payment method for the Booking System, to help the Seller in discussions with the Customer (e.g. "AcmeBroker Points" or "AcmeBroker via Credit Card")
schema:identifier - REQUIRED schema:Text The identifier of the payment held by the Broker and/or Payment Provider.
schema:paymentMethod - REQUIRED schema:PaymentMethod If using the payment extension point, this references the payment method used. If not present indicates the external payment processor.
schema:accountId - OPTIONAL schema:Text A reference used by the Seller to group transactions, which is used to aid reconciliation.

Note that identifier and are NOT REQUIRED if paymentMethod is specified, to maximise flexibility of the payments extension point.

10.1.16 oa:BookingService

oa:BookingService subclasses schema:Service.

Used for bookingService.

Property Broker Request Booking System Response Type Notes
@type - REQUIRED schema:Text BookingService
schema:name - OPTIONAL schema:Text The name of the Booking System
schema:url - OPTIONAL schema:URL The URL of the website of the Booking System.
schema:termsOfService - OPTIONAL Array of oa:Terms The terms of service of the Booking System.

10.1.17 oa:Terms

Used for termsOfService.

Property Broker Request Booking System Response Type Notes
@type - REQUIRED schema:Text Terms
schema:name - REQUIRED schema:Text The name of the terms which must distinguish this from other terms provided, e.g. "Terms and Conditions" or "Privacy Policy"
schema:url - REQUIRED schema:URL The URL of the webpage containing the contents of the terms.

10.2 Discovery Model

10.2.1 oa:OpenBookingAction

oa:OpenBookingAction subclasses schema:OrderAction.

Used for potentialAction.

Property Broker Request Booking System Response Type Notes
@type - REQUIRED schema:Text OpenBookingAction
schema:target - REQUIRED schema:EntryPoint The Order Creation endpoint
schema:supportingData - REQUIRED schema:DataFeed The Orders feed.

10.2.2 schema:EntryPoint

Used for target.

Property Broker Request Booking System Response Type Notes
@type - REQUIRED schema:Text EntryPoint
schema:urlTemplate - REQUIRED schema:URL An [RFC6570] compliant URI template that can be used to generate a unique URL for the Order from a {uuid} parameter.
schema:encodingType - REQUIRED Array of schema:Text The versions of Open Booking supported.
schema:httpMethod - REQUIRED schema:Text Always PUT

10.2.3 schema:DataFeed for supportingData

Property Broker Request Booking System Response Type Notes
@type - REQUIRED schema:Text DataFeed
schema:distribution - REQUIRED Array of schema:DataDownload Versions of the Orders feed feed available

10.2.4 schema:DataDownload

Property Broker Request Booking System Response Type Notes
@type - REQUIRED schema:Text DataDownload
schema:contentUrl - REQUIRED schema:URL The base URL of the Orders feed
schema:additionalType - REQUIRED schema:URL Always https://schema.org/Order.
schema:encodingFormat - REQUIRED Array of schema:Text The versions of Open Booking supported by this feed.

10.3 Error Model

10.3.1 oa:OpenBookingError

Property Status Type Notes
@type REQUIRED schema:Text Error
schema:name RECOMMENDED schema:Text A short, human-readable summary of the problem type. It SHOULD NOT change from occurrence to occurrence of the problem, except for purposes of localization
schema:description RECOMMENDED schema:Text A slightly longer, human-readable summary of the problem type. It largely SHOULD NOT change from occurrence to occurrence of the problem, except for purposes of localization or to provide specific information about why the error occurred in that particular case.
oa:instance RECOMMENDED schema:URL The requested URL.
oa:method RECOMMENDED schema:Text The method of the request (e.g. GET).
oa:status OPTIONAL schema:Integer An integer representing the HTTP status code.
oa:invalidParams OPTIONAL Array of schema:Text An array of invalid parameters, if appropriate.
oa:requestId OPTIONAL schema:Text Used by technical support for diagnostics purposes.

10.3.2 oa:OpenBookingError subclasses

A number of OpenBookingError subclasses are specified to identify the problem type.

10.3.2.1 Order Creation - OrderQuote and Order error responses
Error Type Status Code Use Case
IncompleteCustomerDetailsError 400 If the email address of the Customer is not supplied within a schema:Person object; or if the customer property supplied is not a valid schema:Person or schema:Organization object.
IncompleteBrokerDetailsError 400 If there is insufficient detail in the schema:Organisation object describing the Broker; or if the broker property supplied is not a valid schema:Organisation object.
IncompletePaymentDetailsError 400 If the payment property of the Order is missing or does not include an identifier or paymentMethod.
OrderAlreadyExistsError 400 If the UUID used for an OrderQuote already represents a completed Order.
10.3.2.2 Order Creation - OrderItem errors

Note that all OrderItem errors for an OrderQuote request result in a 409 Conflict response, as the error is expected to be resolved, and the request resubmitted, as per [RFC2616] section 10.4.10.

Error Type Status Code Use Case
IncompleteOrderItemError 409 If there is a missing acceptedOffer or orderedItem property on the schema:OrderItem.
UnacceptableOfferError 409 If the acceptedOffer is not a URL which corresponds to an Applicable Offer for the opportunity.
UnknownOfferError 409 If the acceptedOffer is not a URL which corresponds to an Offer within the Booking System.
UnknownOpportunityDetailsError 409 If the orderedItem is not a URL which corresponds to an opportunity on the Booking System.
UnavailableOpportunityError 409 If the Offer contained in the acceptedOffer property is not bookable.
OpportunityIsFullError 409 If there are no available spaces for the opportunity contained in the orderedItem property
OpportunityHasInsufficientCapacityError 409 If there are not enough available spaces in the opportunity contained in the orderedItem property to fulfil the number requested by the orderQuantity property.
10.3.2.3 API authentication
Error Type Status Code Use Case
UnauthenticatedError 403 If the Broker did not supply any form of authentication.
NoAPITokenError 403 If the Broker did not supply an API key.
InvalidAPITokenError 401 If the Broker supplied an invalid API key, either malformed or expired.
InvalidAuthorizationDetailsError 401 If the Broker supplied an invalid set of authorization details, either malformed or expired.
10.3.2.4 Technical errors
Error Type Status Code Use Case
TemporarilyUnableToProduceOrderQuoteError 500 If the Booking System is unable for technical reasons to produce an OrderQuote where the data provided to it is sufficient to allow it to do so.
TemporarilyUnableToCreateOrderError 500 If the Booking System is unable for technical reasons to create an Order where the data provided to it is sufficient to allow it to do so.
TemporarilyUnableToUpdateOrderError 500 If the Booking System is unable for technical reasons to update an Order (which includes attempting to PATCH for cancellation) where the data provided to it is sufficient to allow it to do so.
TemporarilyUnableToDeleteOrderError 500 If the Booking System is unable for technical reasons to delete an Order where the data provided to it is sufficient to allow it to do so
10.3.2.5 Generic errors
Error Type Status Code Use Case
UnknownOrderError 404 Where a Booking System has no Order matching the one requested.
UnknownOrIncorrectEndpointError 404 Where a Booking System has no endpoint matching the one requested.
NotFoundError 404 Where a Booking System does not have the generic resource specified.
MethodNotAllowed 405 Where a Booking System does not recognise a specific HTTP method for the endpoint requested with that specific HTTP verb.
GoneError 410 Where an Order has been soft-deleted by an Order Deletion request.
TooManyRequestsError 429 Where the Booking System is rate-limiting the Broker.

11. Common Requirements

This section defines some common functionality that applies to the design of the whole API.

11.1 Media types

Custom media types are used in this API to allow clients to choose the format of the data they receive, and for Booking Systems to stipulate which versions of the specification they support.

Booking Systems MAY choose to support additional media types but MUST support the media type(s) defined by this specification.

Media types will conform to the following pattern:

application/vnd.openactive+json; model=[m], booking=[b], rpde=[r]

The current media type is:

application/vnd.openactive+json; model=2.0, booking=1.0, rpde=1.0

Brokers specifying a media type without a version, such as:

application/vnd.openactive+json

will receive data in in the most current format.

If you are building an application as a Broker and care about the stability of the response, or have a client application which is tied to a specific version you should always try to use a media type containing the version number.

Issue 93: Alignment media types Community Feedback Requested

The specification has been updated to use media types that follow this pattern:
application/vnd.openactive+json; model=2.0, booking=1.0, rpde=1.0

Instead of:
application/vnd.openactive.1.0+json

Rationalle:

  • Separates versions of booking spec from the RPDE and model versions, so they may evolve separately.
  • It uses media type parameters, which simplifies IANA registration at a later date (IANA registered media types do not have versions baked in)

Please comment on this issue if you can foresee any challenges with this.

11.2 Response formats

Requests and response documents exchanged via this API will all be valid [JSON-LD] documents, with the exception of the Orders feed, where only documents included in the data property are valid [JSON-LD].

The [JSON-LD] documents MUST include a [JSON-LD] context that refers to the [OpenActive-Vocabulary] as follows:

Example 33: minimal JSON-LD document
{
  "@context": "https://openactive.io/"
}

Unless otherwise specified the request and response documents MUST conform to the [Modelling-Opportunity-Data] data model.

11.3 Errors

Error responses MUST be served using an appropriate HTTP 4XX status code or HTTP 5XX status code specified in the model.

All error responses from this API MUST be returned in a JSON-LD format subclass of the OpenBookingError using a content type of application/vnd.openactive+json (optionally including any version parameters).

Example 34: Error response example
HTTP/1.1 400 Bad Request
Date: Mon, 8 Oct 2018 20:52:36 GMT
Content-Type: application/vnd.openactive+json; model=2.0, booking=1.0, rpde=1.0

{
  "@context": "https://openactive.io/",
  "type": "IncompleteCustomerDetailsError",
  "description": "No customer details supplied"
}

11.4 Rate Limiting

The Booking System MAY choose to apply rate limiting for each unique set of authentication credentials, in which case they MUST adhere to the following.

All rate-limited responses from this API MUST return a JSON-LD format TooManyRequestsError object using a content type of application/vnd.openactive+json (optionally including any version parameters).

The response MUST also include the Retry-After HTTP Header with a value set to the number of seconds after which the Broker SHOULD retry as per [RFC2616] section 14.37.

Example 35: Rate limited response example
HTTP/1.1 429 Too Many Requests
Date: Mon, 8 Oct 2018 20:52:36 GMT
Content-Type: application/vnd.openactive+json; model=2.0, booking=1.0, rpde=1.0
Retry-After: 8

{
  "@context": "https://openactive.io/",
  "type": "TooManyRequestsError",
  "description": "Rate Limit Reached. Retry in 8 seconds."
}

11.5 Security

All HTTP requests and responses SHOULD be secured using SSL.

11.6 Authentication

11.6.1 API level authentication and data security

All of the API transactions described in this document MUST require authentication.

The content and data available to all endpoints provided by the Booking System MUST be specific to the authentication credentials, to ensure that bookings are secure. It is the responsibility of the Booking System to ensure that only authentication credentials that created an Order can amend and cancel the Order.

Note that a client using the same authentication credentials may represent more than one Broker, and the Booking System MUST make no assumptions regarding which Broker is being represented by a particular set of credentials. For the avoidance of doubt: the same Broker accessing a Booking System using two sets of credentials MUST not be able to access Order's created by one set of credentials when using the other.

In terms of implementation, all data partitioning and security MUST be based on the authentication credentials; the broker property within the Order is for information only.

This specification does not mandate a particular authentication method, but its recommendation is that implementers SHOULD consider using [OAuth2] as it is well-defined, widely supported and can be used in a variety of different application flows (e.g. via a Javascript web application or between servers).

Other server-to-server authentication mechanisms such as basic authentication, API key or bearer token based mechanisms are also appropriate.

11.6.2 Customer level authentication

OpenID Connect ([OpenIdConnect]) is recommended as standard way of verifying the identity of Customer. It is a thin layer that sits atop OAuth2.

One principle relating to authentication and the Booking API is that Orders created by a specific Customer SHOULD NOT be able to be changed by any other Customer. It is the responsibility of the Broker to ensure that subsequent requests to an Order should only be initiated by the Customer which created the Order.

12. Future versions of this API

Future iterations of the specification be shaped by the OpenActive community, and we encourage you to get involved.

A. Acknowledgements

This section is non-normative.

The editors thank all members of the OpenActive Community Group for their contributions.

Icons made by Freepik, Darius Dan, Eucalyp, smalllikeart, bqlqn from www.flaticon.com.

B. References

B.1 Normative references

[JSON-LD]
JSON-LD 1.0. W3C. W3C Recommendation. URL: https://www.w3.org/TR/json-ld/
[Modelling-Opportunity-Data]
Modelling Opportunity Data. OpenActive Community Group. URL: https://openactive.io/modelling-opportunity-data/
[OAuth2]
OAuth 2.0. URL: https://oauth.net/2/
[OpenActive-Vocabulary]
OpenActive Vocabulary. OpenActive Community Group. URL: https://openactive.io/ns/
[OpenIdConnect]
OpenID Connect. URL: https://openid.net/connect/
[RFC2119]
Key words for use in RFCs to Indicate Requirement Levels. S. Bradner. IETF. March 1997. Best Current Practice. URL: https://tools.ietf.org/html/rfc2119
[RFC2616]
Hypertext Transfer Protocol -- HTTP/1.1. R. Fielding; J. Gettys; J. Mogul; H. Frystyk; L. Masinter; P. Leach; T. Berners-Lee. IETF. June 1999. Draft Standard. URL: https://tools.ietf.org/html/rfc2616
[RFC6570]
URI Template. J. Gregorio; R. Fielding; M. Hadley; M. Nottingham; D. Orchard. IETF. March 2012. Proposed Standard. URL: https://tools.ietf.org/html/rfc6570
[RPDE]
Realtime Paged Data Exchange. OpenActive Community Group. URL: https://openactive.io/realtime-paged-data-exchange/

B.2 Informative references

[Dataset-API-Discovery]
Dataset API Discovery. OpenActive Community Group. URL: https://openactive.io/dataset-api-discovery/EditorsDraft/