From 16e1fd6f7969a2669517e5ef30bd35d5d94dc4ab Mon Sep 17 00:00:00 2001 From: Oliver Morgan Date: Mon, 3 Feb 2020 17:17:49 +0100 Subject: [PATCH] initial commit --- CHANGELOG.md | 5 + CODE_OF_CONDUCT.md | 73 ++ CONTRIBUTING.md | 16 + LICENSE | 201 +++++ LICENSE_header.txt | 11 + README.md | 23 + docs/openapi_v1.0.0-alpha.yaml | 1467 ++++++++++++++++++++++++++++++++ drafts/ERROR_HANDLING.md | 138 +++ 8 files changed, 1934 insertions(+) create mode 100644 CHANGELOG.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 LICENSE_header.txt create mode 100644 README.md create mode 100644 docs/openapi_v1.0.0-alpha.yaml create mode 100644 drafts/ERROR_HANDLING.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..265f555 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +### v1.0.0-alpha + +Initial draft of the Core API, including `Supplier` and `Product` lists, product `Availability`, creating a booking `Reservation`, creating a `Cancellation`, and retrieving the latest status of a `Booking`. + +:warning: This version of the API has not been officially adopted by the ICF working group. The API is under peer review and open for feedback until v1.0.0 is officially adopted. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..73c2794 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,73 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +education, socio-economic status, nationality, personal appearance, race, +religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at LXSupplierConnectivity@Expedia.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..2103be7 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,16 @@ +### Bugs +We use Github Issues for our bug reporting. If you find a bug in one of the templates or tools, please make sure the bug isn't already listed before opening a new issue. + +### Feedback & Enhancement Requests +If you have feedback, questions, or enhancement requests for the Core API or any Capability API please use the appropriate Slack channel at https://theicf.slack.com. + +### Peer Review & Acceptance Process +If you would like to propose a change or addition, please fork the repository and then open a Pull Request. This will provide a place for comments and feedback to be given. The initial peer review process will allow 2 weeks for feedback. If any substantial changes are required before acceptance the peer review process may take longer. Once feedback has been received and addressed, the working group will vote to adopt the changes into the official API, the changes will be merged, CHANGELOG.md updated, and minimum supported version will be assigned to the updated API changes. + +### Contributing to Documentation +To contribute to documentation, you can directly modify the corresponding .md files in the docs directory. Please submit a pull request. Once your PR is merged, the documentation is automatically built and made publicly available. + +### License +By contributing to `icf`, you agree that your contributions will be licensed under its Apache License. + +[icf documentation]: https://github.com/ExpediaGroup/icf diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE_header.txt b/LICENSE_header.txt new file mode 100644 index 0000000..51fca54 --- /dev/null +++ b/LICENSE_header.txt @@ -0,0 +1,11 @@ +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..06cfa96 --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# Independent Connectivity Forum + +This project includes an independently-developed API specification for facilitating communcation between partners, SLA guidelines, as well as templates and validation tools for implementing the API. + +## API Structure + +The API is divided into two main areas: Core and Capability. + +### Core API + +This represents the minimum required implementation for any partner using this API. The functionality defined in the Core API has been determined to be common enough across all products and bookings that implementation is mandatory. + +### Capability API + +To provide better support for features that may not be required by all products or bookings, the Core API is built to support capabilities. These capabilities can be specific to a particular Reseller or Booking platform, but there will also be adopted standards for capabilities that are very common (e.g. Pricing, Booking Questions, etc.) to provide a standard interface for the most common additional features that are not covered in the Core API. + +## API Templates + +To be developed. + +## API Tools + +To be developed. diff --git a/docs/openapi_v1.0.0-alpha.yaml b/docs/openapi_v1.0.0-alpha.yaml new file mode 100644 index 0000000..4ae352d --- /dev/null +++ b/docs/openapi_v1.0.0-alpha.yaml @@ -0,0 +1,1467 @@ +openapi: 3.0.0 +# Added by API Auto Mocking Plugin +servers: + - description: | + SwaggerHub API Auto Mocking + url: https://virtserver.swaggerhub.com/gimpster/ICF/1.0.0-alpha +info: + description: | + An open-source API for connecting Resellers and Booking Platforms. + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [BCP 14](https://tools.ietf.org/html/bcp14) [RFC 2119](https://tools.ietf.org/html/rfc2119) [RFC 8174](https://tools.ietf.org/html/rfc8174) when, and only when, they appear in all capitals, as shown here. + version: "1.0.0-alpha" + title: 'Independent Connectivity Forum API' + license: + name: Apache 2.0 + url: 'http://www.apache.org/licenses/LICENSE-2.0.html' +paths: + /suppliers: + get: + tags: + - Suppliers + summary: 'Returns a list of suppliers and associated contact details.' + description: | + This list MAY be limited based on the suppliers that the authenticated user has been granted access to. + operationId: 'getSuppliers' + responses: + '200': + description: | + List of suppliers with details. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Supplier' + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + + /suppliers/{supplierId}/products: + get: + tags: + - Products + summary: 'Returns a list of products for a specific supplier.' + description: | + **WARNING: this endpoint may live under a different domain, path or both depending on the supplier endpoint URL returned by the GET /suppliers.** + + Contains all product details necessary to ingest, map, and sell. + operationId: 'getProducts' + parameters: + - $ref: '#/components/parameters/SupplierId' + responses: + '200': + description: | + List of products with details. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Product' + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + + /suppliers/{supplierId}/availability/calendar: + get: + tags: + - Availability + summary: Get a list of dates on which tickets are available + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/CalendarItem' + operationId: getCalendar + parameters: + - $ref: '#/components/parameters/LocalDateStart' + - $ref: '#/components/parameters/LocalDateEnd' + - $ref: '#/components/parameters/ProductId' + - $ref: '#/components/parameters/SupplierId' + - $ref: '#/components/parameters/OptionIds' + description: |- + **WARNING: this endpoint may live under a different domain, path or both depending on the supplier endpoint URL returned by the GET /suppliers.** + + Returns a list of dates on which tickets are available. + Only dates that have availability SHALL BE returned. + A minimum range of 31 days MUST BE supported. + + This endpoint proved a relatively lightweight way to show a calendar in a user interface containing a one month calendar with the days where there is no availability greyed out. + + /suppliers/{supplierId}/availability: + get: + tags: + - Availability + summary: 'Returns a list of dates and their availability status.' + description: | + **WARNING: this endpoint may live under a different domain, path or both depending on the supplier endpoint URL returned by the GET /suppliers.** + + For any dates which are never available for booking, the response MUST exclude those dates entirely. + + If the product's `availabilityType` is `OPENING_HOURS` then the `localDateTimeStart` and `localDateTimeEnd` are the hours of operation. If a product has more than one hours of operation on the same day (e.g. the supplier is open 8-5 but closed for lunch from 12-1) then one availability object MUST be returned for each contiguous range of time for that day. + + The availability `id` value MUST be sent when making a Reservation request. + + The `status` field SHOULD be used to infer how frequently your cache should be updated from the Booking Platform. The RECOMMENDED frequency is as follows: + + * `FREESALE`: Always available. Refresh no more than once/week. + * `AVAILABLE`: Currently available for sale, but has a fixed capacity. Refresh every 12 hours. + * `LIMITED`: Currently available for sale, but has a fixed capacity and may be sold out soon. Refresh at least once/hour. + * `SOLD_OUT`: Currently sold out, but additional availability may free up. Refresh no more than once/hour. + * `CLOSED`: Currently not available for sale, but not sold out (e.g. temporarily on stop-sell) and may be available for sale soon. Refresh no more than once/12 hours. + operationId: 'availabilityOverview' + parameters: + - $ref: '#/components/parameters/SupplierId' + - $ref: '#/components/parameters/ProductId' + - $ref: '#/components/parameters/OptionId' + - $ref: '#/components/parameters/LocalDateStart' + - $ref: '#/components/parameters/LocalDateEnd' + responses: + '200': + description: | + List of availability objects with status. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/AvailabilityStatus' + example: + - id: '28271273-a317-40fc-8f42-79725a7072a3' + localDateTimeStart: '2019-10-31T08:30:00Z' + localDateTimeEnd: '2019-10-31T10:00:00Z' + status: 'AVAILABLE' + vacancies: 100 + - id: '6143d137-fdf6-4da1-a558-20aa93eb55f0' + localDateTimeStart: '2019-10-31T12:00:00Z' + localDateTimeEnd: '2019-10-31T13:00:00Z' + status: 'FREESALE' + vacancies: null + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + post: + tags: + - Availability + summary: 'Returns a list of dates after evaluating the specific Unit and Availability IDs that the customer would like to book.' + description: | + **WARNING: this endpoint may live under a different domain, path or both depending on the supplier endpoint URL returned by the GET /suppliers.** + + This request is intended to provide the Booking Platform a complete view of the Unit IDs, Unit quantity, and Availability IDs so that additional restrictions and policies can be validated within the Booking Platform prior to making a Reservation. The purpose is to provide a clear and accurate answer to the Reseller about whether the requested booking configuration could be accepted by the Supplier. This is to support complex booking requirements without the Reseller needing to know the details of the restriction (e.g. "must purchase at least 1 adult ticket if a child ticket is purchased"). + operationId: 'availabilityCheck' + parameters: + - $ref: '#/components/parameters/SupplierId' + requestBody: + $ref: '#/components/requestBodies/AvailabilityCheckRequest' + responses: + '200': + description: | + List of availability objects with status. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/AvailabilityStatus' + example: + - id: '28271273-a317-40fc-8f42-79725a7072a3' + localDateTimeStart: '2019-10-31T08:30:00Z' + localDateTimeEnd: '2019-10-31T10:00:00Z' + status: 'AVAILABLE' + vacancies: 100 + - id: '6143d137-fdf6-4da1-a558-20aa93eb55f0' + localDateTimeStart: '2019-10-31T12:00:00Z' + localDateTimeEnd: '2019-10-31T13:00:00Z' + status: 'FREESALE' + vacancies: null + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + + /suppliers/{supplierId}/bookings: + get: + tags: + - Bookings + summary: 'Gets the current details of an in-progress reservation or completed booking.' + description: | + **WARNING: this endpoint may live under a different domain, path or both depending on the supplier endpoint URL returned by the GET /suppliers.** + + This returns the current state of any valid booking. This request MAY be made at any point after the initial `createReservation` request is processed successfully and it MUST return the booking reservation object. + operationId: 'getBooking' + parameters: + - $ref: '#/components/parameters/SupplierId' + - $ref: '#/components/parameters/Uuid' + responses: + '200': + $ref: '#/components/responses/BookingReservationResponse' + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + + /suppliers/{supplierId}/bookings/{uuid}: + get: + tags: + - Bookings + summary: 'Get the booking details.' + description: | + **WARNING: this endpoint may live under a different domain, path or both depending on the supplier endpoint URL returned by the GET /suppliers.** + operationId: 'getBookings' + parameters: + - $ref: '#/components/parameters/SupplierId' + - $ref: '#/components/parameters/Uuid' + responses: + '200': + $ref: '#/components/responses/BookingReservationResponse' + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + post: + tags: + - Bookings + summary: 'Create a new booking reservation.' + description: | + **WARNING: this endpoint may live under a different domain, path or both depending on the supplier endpoint URL returned by the GET /suppliers.** + + This creates a new booking reservation. + operationId: 'createReservation' + parameters: + - $ref: '#/components/parameters/SupplierId' + - $ref: '#/components/parameters/Uuid' + requestBody: + $ref: '#/components/requestBodies/CreateReservationRequest' + responses: + '200': + $ref: '#/components/responses/BookingReservationResponse' + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + delete: + tags: + - Bookings + summary: 'Expire the hold of an existing reservation.' + description: | + **WARNING: this endpoint may live under a different domain, path or both depending on the supplier endpoint URL returned by the GET /suppliers.** + + This expires the availability hold of an existing reservation so that the availablity is release for other booking reservations. This request is a courtesy, however Resellers SHOULD send this in order to ensure proper cleanup of any outstanding holds. + operationId: 'expireReservation' + parameters: + - $ref: '#/components/parameters/SupplierId' + - $ref: '#/components/parameters/Uuid' + responses: + '200': + $ref: '#/components/responses/BookingReservationResponse' + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + + /suppliers/{supplierId}/bookings/{uuid}/cancel: + post: + tags: + - Cancellations + summary: 'Create a new cancellation request.' + description: | + **WARNING: this endpoint may live under a different domain, path or both depending on the supplier endpoint URL returned by the GET /suppliers.** + + This creates a new cancellation request for a booking with `status` of `CONFIRMED`. Cancellation policy rules MUST be validated at this time. + operationId: 'createCancellation' + parameters: + - $ref: '#/components/parameters/SupplierId' + - $ref: '#/components/parameters/Uuid' + requestBody: + $ref: '#/components/requestBodies/CreateCancellationRequest' + responses: + '200': + $ref: '#/components/responses/BookingReservationResponse' + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + delete: + tags: + - Cancellations + summary: 'Expire the hold of an existing cancellation request.' + description: | + **WARNING: this endpoint may live under a different domain, path or both depending on the supplier endpoint URL returned by the GET /suppliers.** + + This expires the hold of an existing cancellation request. If a cancellation request is sent again in the future, it MUST start with a new `createCancellation` request because the `utcRequestedAt` SHOULD be cleared after successfully processing this request. + operationId: 'expireCancellation' + parameters: + - $ref: '#/components/parameters/SupplierId' + - $ref: '#/components/parameters/Uuid' + responses: + '200': + $ref: '#/components/responses/BookingReservationResponse' + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + + /suppliers/{supplierId}/bookings/{uuid}/confirmCancel: + post: + tags: + - Cancellations + summary: 'Confirm an existing cancellation request.' + description: | + **WARNING: this endpoint may live under a different domain, path or both depending on the supplier endpoint URL returned by the GET /suppliers.** + + This confirms an existing cancellation request. Any cancellation policy MUST reference the `utcRequestedAt` and not use the timestamp of this request. + operationId: 'confirmCancellation' + parameters: + - $ref: '#/components/parameters/SupplierId' + - $ref: '#/components/parameters/Uuid' + requestBody: + $ref: '#/components/requestBodies/ConfirmCancellationRequest' + responses: + '200': + $ref: '#/components/responses/BookingReservationResponse' + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + + /suppliers/{supplierId}/bookings/{uuid}/extend: + post: + tags: + - Bookings + summary: 'Extend the hold of an existing reservation.' + description: | + **WARNING: this endpoint may live under a different domain, path or both depending on the supplier endpoint URL returned by the GET /suppliers.** + + This extends the hold of an existing reservation. The `utcHoldExpiration` MUST NOT be elapsed when this request is sent, otherwise the response MAY show a `status` of `EXPIRED`. + operationId: 'extendReservation' + parameters: + - $ref: '#/components/parameters/SupplierId' + - $ref: '#/components/parameters/Uuid' + requestBody: + $ref: '#/components/requestBodies/ExtendReservationRequest' + responses: + '200': + $ref: '#/components/responses/BookingReservationResponse' + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + + /suppliers/{supplierId}/bookings/{uuid}/confirm: + post: + tags: + - Bookings + summary: 'Confirm an existing reservation' + description: | + **WARNING: this endpoint may live under a different domain, path or both depending on the supplier endpoint URL returned by the GET /suppliers.** + + This confirms an existing reservation. The `utcHoldExpiration` MUST NOT be elapsed when this request is sent, otherwise the response MAY show a `status` of `EXPIRED`. + operationId: 'confirmReservation' + parameters: + - $ref: '#/components/parameters/SupplierId' + - $ref: '#/components/parameters/Uuid' + requestBody: + $ref: '#/components/requestBodies/ConfirmReservationRequest' + responses: + '200': + $ref: '#/components/responses/BookingReservationResponse' + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + +components: + schemas: + Availability: + type: object + required: + - id + - localDateTimeStart + - localDateTimeEnd + properties: + id: + type: string + description: | + This MUST be a unique identifier within the scope of the Option. + example: '28271273-a317-40fc-8f42-79725a7072a3' + localDateTimeStart: + type: string + description: | + This MUST be an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) compliant date and time. + format: date-time + example: '2019-10-31T08:30:00Z' + localDateTimeEnd: + type: string + description: | + This MUST be an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) compliant date and time. + format: date-time + example: '2019-10-31T10:00:00Z' + AvailabilityStatus: + allOf: + - $ref: '#/components/schemas/Availability' + - type: object + required: + - status + - vacancies + properties: + status: + type: string + description: | + This represents whether the availability in this configuration is currently bookable. The values have the following meanings: + + * `FREESALE`: Always available. + * `AVAILABLE`: Currently available for sale, but has a fixed capacity. + * `LIMITED`: Currently available for sale, but has a fixed capacity and may be sold out soon. + * `SOLD_OUT`: Currently sold out, but additional availability may free up. + * `CLOSED`: Currently not available for sale, but not sold out (e.g. temporarily on stop-sell) and may be available for sale soon. + enum: + - FREESALE + - AVAILABLE + - LIMITED + - SOLD_OUT + - CLOSED + example: 'AVAILABLE' + vacancies: + type: integer + nullable: true + description: | + Returns `null` when `status` is `FREESALE`. This SHOULD be a shared pool for all Unit types in the Option. If availability is tracked per-Unit then this value MUST be equal to the available quantity for the Unit that has the most remaining. + example: 100 + AvailabilityId: + type: string + description: | + A valid availability ID that matches the `id` returned from `GET /availability`. + example: '28271273-a317-40fc-8f42-79725a7072a3' + Booking: + type: object + required: + - uuid + - status + - utcHoldExpiration + - utcConfirmedAt + - utcDeliveredAt + - productId + - optionId + - availability + - contact + - deliveryMethods + - voucher + - unitItems + - cancellationRequest + properties: + uuid: + type: string + format: uuid + description: | + This is a randomly-generated UUID that MUST be tracked by both the Reseller and Booking Platform for locating this record. + example: 'f149068e-300e-452a-a856-3f091239f1d7' + resellerReference: + $ref: '#/components/schemas/ResellerReference' + supplierReference: + $ref: '#/components/schemas/SupplierReference' + status: + $ref: '#/components/schemas/Status' + utcHoldExpiration: + type: string + format: date-time + nullable: true + description: | + This MUST be an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) compliant date and time. The value represents the time at which the availability hold will be released. This SHOULD be equivalent to the time calculated by adding `holdExpirationMinutes` to the current UTC time but MAY be either earlier or later than the requested duration. + example: '2019-10-31T08:30:00Z' + utcConfirmedAt: + type: string + format: date-time + nullable: true + description: | + This MUST be an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) compliant date and time. The value represents the time at which the reservation was confirmed (either automatically by the Booking Platform or manually by the Supplier). + example: '2019-10-31T08:30:00Z' + utcDeliveredAt: + type: string + format: date-time + nullable: true + description: | + This MUST be an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) compliant date and time. The value represents the time at whcih the VOUCHER was delivered. + example: '2019-10-31T08:30:00Z' + refreshFrequency: + type: string + enum: + - HOURLY + - DAILY + description: | + This is the RECOMMENDED refresh interval for the Reseller and SHOULD be used by the Reseller to control the frequency at which they make a `getBooking` request for the following scenarios: + + * To see if a booking/cancellation has changed out of a `PENDING` status into `CONFIRMED` or `REJECTED`. + * To see if a booking has had any new Vouchers or Tickets delivered for the booking. + * To see if a booking has changed from `CONFIRMED` to `CANCELLED` in the event of a supplier-initiated cancellation. + * To see if a booking has an updated `utcRedeemedAt`/`utcResolvedAt` value for the Voucher or any of the Tickets. + example: 'HOURLY' + productId: + type: string + description: | + A valid product ID that matches the `id` returned from `GET /suppliers/{supplierId}/products`. + example: 'adult' + optionId: + type: string + description: | + A valid option ID that matches the `id` returned from `GET /suppliers/{supplierId}/products`. + example: 'LR1-01' + availability: + $ref: '#/components/schemas/Availability' + contact: + $ref: '#/components/schemas/Contact' + deliveryMethods: + type: array + items: + $ref: '#/components/schemas/DeliveryMethod' + minItems: 1 + maxItems: 2 + voucher: + # https://github.com/OAI/OpenAPI-Specification/issues/1368 + nullable: true + allOf: + - $ref: '#/components/schemas/Ticket' + unitItems: + type: array + items: + $ref: '#/components/schemas/UnitItemTicket' + minItems: 1 + cancellationRequest: + # https://github.com/OAI/OpenAPI-Specification/issues/1368 + nullable: true + allOf: + - $ref: '#/components/schemas/CancellationRequest' + CancellationRequest: + type: object + required: + - reason + - reasonDetails + - status + - refund + - utcRequestedAt + - utcHoldExpiration + - utcConfirmedAt + - utcResolvedAt + properties: + reason: + $ref: '#/components/schemas/Reason' + reasonDetails: + $ref: '#/components/schemas/ReasonDetails' + status: + $ref: '#/components/schemas/Status' + refund: + type: string + enum: + - FULL + - PARTIAL + - NONE + description: | + This value indicates the expected refund from the Supplier. + + * `FULL` indicates that the Supplier has fully refunded the booking and will not be paid by the Reseller for this booking. This is the expected state when a valid cancellation request is made before any cancellation cutoff policy or when a Supplier approves a cancellation request that was made after the cutoff. + * `PARTIAL` indicates that the Supplier has agreed to partially refund the customer. This may be due to a cancellation policy that grants a partial refund or because the Supplier has agreed to partially refund the customer when the cancellation policy would otherwise have not allowed any refund. + * `NONE` indicates that no refund will be given by the Supplier. The customer may still be refunded by the Reseller but the Supplier MUST still be paid for this booking. + example: 'FULL' + utcRequestedAt: + type: string + format: date-time + description: | + This MUST be an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) compliant date and time. The value represents the time at which the cancellation request was originally started. This timestamp MUST be used for any validation against a cancellation policy. This is important because there may be some delay in confirming this cancellation request during the 2-phase workflow which could finish just after the cancellation policy cutoff has elapsed. + example: '2019-10-31T08:30:00Z' + utcHoldExpiration: + type: string + format: date-time + nullable: true + description: | + This MUST be an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) compliant date and time. The value represents the time at which the cancellation request hold will be released. This SHOULD be equivalent to the time calculated by adding `holdExpirationMinutes` to the current UTC time but MAY be either earlier or later than the requested duration. + example: '2019-10-31T08:30:00Z' + utcConfirmedAt: + type: string + format: date-time + nullable: true + description: | + This MUST be an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) compliant date and time. The value represents the time at which the cancellation request was confirmed by the Reseller. + example: '2019-10-31T08:30:00Z' + utcResolvedAt: + type: string + format: date-time + nullable: true + description: | + This MUST be an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) compliant date and time. The value represents the time at which the cancellation was confirmed. This will typically be the same as `utcConfirmedAt` however if the cancellation request required manual approval from the Supplier, it may be different. + example: '2019-10-31T08:30:00Z' + Capability: + type: object + required: + - id + - revision + - required + properties: + id: + type: string + description: | + This MUST be a unique identifier within the scope of the ICF. Officially adopted capabilities will be identified only by the name of the capability but any capabilities that are specific to a particular partner MUST be prefixed with that partner's identifier and separated from the capability name with a `/`. + example: 'api.my-booking-platform.com/dynamic-pricing' + revision: + type: integer + description: | + This represents which revision of the capability is supported. This is not a version numbers and therefore there is no implied version compatibility like you would have with a semantic version, therefore if multiple revisions are supported, then one Capability object should be returned for each revision that is supported. + example: 2 + required: + type: boolean + description: | + This indicates whether the capability is merely supported or strictly required. Resellers that don't support the capability or specific revision will use this flag to filter out products they are unable to support. + example: true + Contact: + type: object + required: + - fullName + - emailAddress + - phoneNumber + - locales + - country + properties: + fullName: + type: string + description: | + The full name of the lead traveller. + example: 'Mr. Traveller' + emailAddress: + type: string + format: email + description: | + The contact email of the lead traveller. + example: 'traveller@fake.com' + phoneNumber: + type: string + nullable: true + description: | + The contact phone number of the lead traveller. + example: '+1 555-555-1212' + locales: + type: array + items: + $ref: '#/components/schemas/Locale' + example: + - en-GB + - en-US + - en + minItems: 0 + country: + type: string + nullable: true + description: | + This MUST be a valid [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country code. + example: 'GB' + DeliveryFormat: + type: string + enum: + - PDF_URL + - QRCODE + - AZTEC + - CODE128 + - CODE39 + description: | + This indicates the format for the `deliveryValue` so that it can be parsed or encoded correctly. + example: 'CODE39' + DeliveryMethod: + type: string + enum: + - TICKET + - VOUCHER + description: | + A value of `TICKET` indicates that there will be one `deliveryValue` for each ticket in the Booking while a value of `VOUCHER` indicates that there will be one `deliveryValue` that is shared among all tickets in the Booking. + example: 'VOUCHER' + DeliveryOption: + type: object + required: + - deliveryFormat + - deliveryValue + properties: + deliveryFormat: + # https://github.com/OAI/OpenAPI-Specification/issues/1368 + nullable: true + allOf: + - $ref: '#/components/schemas/DeliveryFormat' + deliveryValue: + type: string + nullable: true + description: | + Represents the value for the voucher or ticket that should be used. If the `deliveryFormat` is `PDF_URL` then this value MUST be a valid [URI](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier) that resolves to a `PDF` resource. For any other `deliveryFormat` this value MUST be encoded according to the `deliveryFormat` value. + example: '01234567890' + Locale: + type: string + description: | + This MUST be a valid [BCP 47](https://tools.ietf.org/html/bcp47) [RFC 5646](https://tools.ietf.org/html/rfc5646) [RFC 4647](https://tools.ietf.org/html/rfc4647) language tag. + example: 'en-GB' + Option: + type: object + required: + - id + - internalName + - units + properties: + id: + type: string + description: | + This MUST be a unique identifier within the scope of the Product. + example: '0001' + internalName: + type: string + description: | + This SHOULD be a friendly name for the Option to facilitate easier identification. It MUST NOT be shown to the customer. + example: 'Morning' + reference: + type: string + description: | + This is an internal reference identifier that the Supplier wishes to use. It MAY be non-unique. + example: 'LR1-01' + units: + type: array + items: + $ref: '#/components/schemas/Unit' + minItems: 1 + example: + - id: 'adult' + internalName: 'Adult' + reference: 'LR1-01-01' + type: 'ADULT' + - id: '0001-0001-child' + internalName: 'Child' + reference: 'LR1-01-02' + type: 'CHILD' + Product: + type: object + required: + - id + - internalName + - locale + - timeZone + - instantConfirmation + - instantDelivery + - availabilityType + - deliveryFormats # confirm + - deliveryMethods # confirm + - redemptionMethod + - options + properties: + id: + type: string + description: | + This MUST be unique within the scope of the Supplier. + example: '0001' + internalName: + type: string + description: | + This SHOULD be a friendly name for the Product to facilitate easier identification. It MUST NOT be shown to the customer. + example: 'Morning tour' + reference: + type: string + description: | + This is an internal reference identifier that the Supplier wishes to use. It MAY be non-unique. + example: 'LR1-01' + locale: + $ref: '#/components/schemas/Locale' + timeZone: + type: string + description: | + This MUST be a valid database name from the ICANN [tz database](https://en.wikipedia.org/wiki/Tz_database). Any calculation of UTC offset or UTC DTC offset MUST use the correct offset from this database. + example: 'Europe/London' + instantConfirmation: + type: boolean + description: | + This indicates whether the Reseller can expect an immediate confirmation of whether the Supplier has accepted the booking. If `false` then the Reseller MUST be able to delay confirmation to the customer while waiting for the Supplier to accept or reject the Booking. + + When `instantConfirmation` is set to `false` one should expect created reservations to first get into a `PENDING` state. + example: true + instantDelivery: + type: boolean + description: | + This indicates whether the Reseller can expect immediate delivery of the customer's tickets. If `false` then the Reseller MUST be able to delay delivery of the tickets to the customer. + example: true + availabilityType: + type: string + description: | + This indicates whether the Product redemption is valid only for a specific start time (e.g. a guided tour) or valid any time during normal business hours. + enum: + - START_TIME + - OPENING_HOURS + example: 'START_TIME' + deliveryFormats: + type: array + items: + $ref: '#/components/schemas/DeliveryFormat' + description: | + This MAY contain more than one value if some Options or Units under this Product will use different delivery formats. The Reseller MUST be able to support all of the specified formats in order to sell this Product. + example: + - PDF_URL + - CODE39 + deliveryMethods: + type: array + items: + $ref: '#/components/schemas/DeliveryMethod' + description: | + A product MAY support both types of delivery methods. + example: + - TICKET + - VOUCHER + redemptionMethod: + $ref: '#/components/schemas/RedemptionMethod' + capabilities: + type: array + items: + $ref: '#/components/schemas/Capability' + minItems: 0 + example: + - id: 'dynamic-pricing' + revision: 1 + required: true + - id: 'api.my-booking-platform.com/dynamic-pricing' + revision: 2 + required: false + options: + type: array + items: + $ref: '#/components/schemas/Option' + minItems: 1 + example: + - id: '0001' + internalName: 'Morning' + reference: 'LR1-01' + units: + - id: 'adult' + internalName: 'Adult' + reference: 'LR1-01-01' + type: 'ADULT' + - id: '0001-0001-child' + internalName: 'Child' + reference: 'LR1-01-02' + type: 'CHILD' + - id: '0002' + internalName: 'Afternoon' + reference: 'LR1-02' + units: + - id: 'adult' + internalName: 'Adult' + reference: 'LR1-01-01' + type: 'ADULT' + - id: '0001-0001-child' + internalName: 'Child' + reference: 'LR1-01-02' + type: 'CHILD' + Reason: + type: string + enum: + - CUSTOMER + - SUPPLIER + - FRAUD + - OTHER + description: | + This value indicates the reason that the cancellation request was sent. + + * `CUSTOMER` is the most common and indicates that the customer requested the cancellation. + * `SUPPLIER` indicates that the supplier requested the cancellation (possibly due to bad weather or other unexpected circumstances). + * `FRAUD` indicates that the booking cancellation is being requested by the Reseller because it has been determined the booking was fraudulent. + * `OTHER` indicates that the cancellation reason does not fall into one of these categories. This SHOULD be used only in rare circumstances. + example: 'CUSTOMER_REQUESTED' + ReasonDetails: + type: string + nullable: true + description: | + This field provides additional details about the reason for the cancellation request. It may include information from the customer, supplier, or support agent about the reason for the cancellation (especially in the case of requesting a cancellation outside of the normal policy which may require manual approval from the supplier). + example: 'Child came down with the flu the day before the activity.' + RedemptionMethod: + type: string + description: | + This indicates the redemption requirements for the customer. A value of `MANIFEST` indicates that the customer MUST provide a form of identification to redeem and as such a printed or digital copy of the ticket is OPTIONAL. A value of `DIGITAL` indicates that the customer MUST provide a copy of the ticket but MAY be digital or printed. A value of `PRINT` indicates that the customer MUST provide a printed copy of the ticket (this is typically only used when the Supplier must retain the printed copy for their records). + enum: + - MANIFEST + - DIGITAL + - PRINT + example: 'DIGITAL' + ResellerReference: + type: string + nullable: true + description: | + An OPTIONAL tracking reference for the Reseller that SHOULD be tracked by the Booking Platform. This MUST be returned if the value was provided in the request body. + example: '001-002' + Status: + type: string + enum: + - ON_HOLD + - EXPIRED + - PENDING + - REJECTED + - CONFIRMED + - CANCELLED + description: | + After a successful `createReservation` or `createCancellation` request, the `status` MUST be `ON_HOLD`. + + After a successful `confirmReservation` request, the `status` MUST be `CONFIRMED`. + + After a successful `confirmCancellation` request, the `status` MUST be `CANCELLED`. + + Following are the only valid Reservation flow status transitions: + + * New Reservation -> `REJECTED` + * New Reservation -> `ON_HOLD` -> `EXPIRED` + * New Reservation -> `ON_HOLD` -> `CONFIRMED` + * New Reservation -> `ON_HOLD` -> `PENDING` -> `REJECTED` + * New Reservation -> `ON_HOLD` -> `PENDING` -> `CONFIRMED` + + The `PENDING` status MAY appear only for products with the `instantConfirmation` property set to `false`. Poll the /bookings endpoint for status changes. + + Following are the only valid Cancellation flow status transitions: + + * `CONFIRMED` -> `REJECTED` + * `CONFIRMED` -> `ON_HOLD` -> `EXPIRED` + * `CONFIRMED` -> `ON_HOLD` -> `CANCELLED` + * `CONFIRMED` -> `ON_HOLD` -> `PENDING` -> `REJECTED` + * `CONFIRMED` -> `ON_HOLD` -> `PENDING` -> `CANCELLED` + example: 'ON_HOLD' + Supplier: + type: object + required: + - id + - name + - endpoint + - contact + properties: + id: + type: string + description: | + This MUST be unique within the scope of the Booking Platform. + example: '0001' + name: + type: string + description: | + Common name for the supplier that may be displayed to the customer. + example: 'Acme Tour Co.' + endpoint: + type: string + description: | + This is the base URL that will be prepended to ALL other paths. The value SHOULD NOT contain a trailing `/`. + format: uri + example: 'https://api.my-booking-platform.com/v1' + contact: + $ref: '#/components/schemas/SupplierContact' + SupplierContact: + type: object + description: | + Contact details for the supplier. + required: + - email + properties: + website: + type: string + description: | + This SHOULD be the website of the Supplier that is separate from the Booking Platform but MAY be a unique destination within the Booking Platform about the Supplier. + format: url + example: 'https://acme-tours.co.fake' + email: + type: string + description: | + This SHOULD be the email support contact for the Supplier. This information MAY be provided to the customer. + format: email + example: 'info@acme-tours.co.fake' + telephone: + type: string + description: | + This SHOULD be the phone support contact for the Supplier. This information MAY be provided to the customer. + example: '+1 888-555-1212' + address: + type: string + description: | + This SHOULD be the mail address support contact for the Supplier. This information MAY be provided to the customer. + example: '123 Main St, Anytown USA' + SupplierReference: + type: string + nullable: true + description: | + An OPTIONAL tracking reference for the Supplier that SHOULD be tracked by the Reseller. + example: 'ABC-123' + Ticket: + type: object + required: + - deliveryOptions + - redemptionMethod + - utcDeliveredAt + - utcRedeemedAt + properties: + deliveryOptions: + type: array + items: + $ref: '#/components/schemas/DeliveryOption' + redemptionMethod: + $ref: '#/components/schemas/RedemptionMethod' + utcDeliveredAt: + type: string + format: date-time + nullable: true + description: | + This MUST be an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) compliant date and time. The value represents the time at which the voucher was made available to the customer by the Supplier. This will typically be the same as `utcConfirmedAt`. + example: '2019-10-31T08:30:00Z' + utcRedeemedAt: + type: string + format: date-time + nullable: true + description: | + This MUST be an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) compliant date and time. The value represents the time at which the customer redeemed this voucher. + example: '2019-10-31T08:30:00Z' + Unit: + type: object + required: + - id + - internalName + - type + properties: + id: + type: string + description: | + This MUST be a unique identifier within the scope of the Option. + example: 'youth_10_17' + internalName: + type: string + description: | + This SHOULD be a friendly name for the Unit to facilitate easier identification. It MUST NOT be shown to the customer. + example: 'Youth (Summer)' + reference: + type: string + description: | + This is an internal reference identifier that the Supplier wishes to use. It MAY be non-unique. + example: 'LR1-01-new' + type: + type: string + description: | + This is the base unit type for this unit definition. A value of `TRAELLER` MUST only be used in replacement of `ADULT`, `CHILD`, `INFANT`, `YOUTH`, `STUDENT`, or `SENIOR`. + enum: + - ADULT + - CHILD + - INFANT + - YOUTH + - STUDENT + - SENIOR + - TRAVELLER + - RESOURCE + - OTHER + example: 'YOUTH' + UnitItem: + type: object + required: + - uuid + - unitId + properties: + uuid: + type: string + description: | + This is a randomly-generated UUID that MUST be tracked by both the Reseller and Booking Platform for locating this record. + example: '6be0409f-181e-4520-acc1-cc6791896859' + unitId: + type: string + description: | + A valid unit ID that matches the `id` returned from `GET /suppliers/{supplierId}/products`. + example: 'adult' + resellerReference: + $ref: '#/components/schemas/ResellerReference' + UnitItemTicket: + allOf: + - $ref: '#/components/schemas/UnitItem' + - type: object + required: + - ticket + properties: + supplierReference: + $ref: '#/components/schemas/SupplierReference' + ticket: + # https://github.com/OAI/OpenAPI-Specification/issues/1368 + nullable: true + allOf: + - $ref: '#/components/schemas/Ticket' + UnitQuantity: + type: object + required: + - unitId + - quantity + properties: + unitId: + type: string + description: | + A valid unit ID that matches the `id` returned from `GET /suppliers/{supplierId}/products`. + example: 'adult' + quantity: + type: integer + description: | + The total number of this unit that the customer wants to purchase. + example: 2 + Uuid: + type: string + format: uuid + description: | + This UUID is generated for the initial `createReservation` request and MUST never change. This value MUST be tracked by both the Reseller and Booking Platform for locating this record. + example: '7df49d62-57ad-44be-8373-e4c2fe7e63fe' + CalendarItem: + title: CalendarItem + type: object + properties: + localDate: + type: string + format: date + capacity: + type: integer + minimum: 0 + responses: + BadRequest: + description: | + Invalid request (e.g. missing required parameters, invalid model, missing `Date` header, etc.). + Unauthorized: + description: | + Missing `Authorization` header or key could not be validated. + Forbidden: + description: | + The `Authorization` header was validated but the requestor does not have the correct permissions to access the requested resource or perform the requested operation. + NotFound: + description: | + Invalid URI path requested. + InternalServerError: + description: | + An unknown error occurred and the server cannot respond in a sensible way. The response may not include a valid error object if one could not be generated. + BookingReservationResponse: + description: | + A complete representation of the current booking reservation status. + content: + application/json: + schema: + $ref: '#/components/schemas/Booking' + parameters: + LocalDateStart: + name: 'localDateStart' + in: query + description: | + This MUST be an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) compliant date. The start of the date range MUST be treated as inclusive of this date when generating the response. + required: true + schema: + type: string + format: date + LocalDateEnd: + name: 'localDateEnd' + in: query + description: | + This MUST be an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) compliant date. The end of the date range MUST be treated as exclusive of this date when generating the response. + required: true + schema: + type: string + format: date + OptionId: + name: 'optionId' + in: query + description: | + A valid option ID that matches the `id` returned from `GET /suppliers/{supplierId}/products`. + required: true + schema: + type: string + format: string + ProductId: + name: 'productId' + in: query + description: | + A valid product ID that matches the `id` returned from `GET /suppliers/{supplierId}/products`. + required: true + schema: + type: string + format: string + SupplierId: + name: 'supplierId' + in: path + description: | + A valid supplier ID that matches the `id` returned from `GET /suppliers`. + required: true + schema: + type: string + format: string + Uuid: + name: 'uuid' + in: path + description: | + A valid booking reservation UUID that matches the `uuid` sent during the initial `POST /suppliers/{supplierId}/bookings/{uuid}` + required: true + schema: + type: string + format: uuid + OptionIds: + name: optionIds + in: query + description: | + A list of valid option IDs that matches `id`s returned from `GET /suppliers/{supplierId}/products`. TODO: IS THIS REALLY CORRECT?? + schema: + type: array + items: + type: string + format: string + required: false + requestBodies: + AvailabilityCheckRequest: + required: true + description: | + This MUST include all units that the customer is attempting to reserve and SHOULD include all possible availability IDs that the customer may be interested in reserving. + content: + application/json: + schema: + type: object + required: + - productId + - optionId + - availabilityIds + - units + properties: + productId: + type: string + description: | + A valid product ID that matches the `id` returned from `GET /suppliers/{supplierId}/products`. + example: 'adult' + optionId: + type: string + description: | + A valid option ID that matches the `id` returned from `GET /suppliers/{supplierId}/products`. + example: 'LR1-01' + availabilityIds: + type: array + items: + $ref: '#/components/schemas/AvailabilityId' + example: + - '28271273-a317-40fc-8f42-79725a7072a3' + - '6143d137-fdf6-4da1-a558-20aa93eb55f0' + minItems: 1 + units: + type: array + items: + $ref: '#/components/schemas/UnitQuantity' + example: + - id: 'adult' + quantity: 2 + - id: 'child' + quantity: 1 + minItems: 1 + CreateReservationRequest: + required: true + description: | + This MUST include all unitItems that the customer wants to book. + content: + application/json: + schema: + type: object + required: + - uuid + - productId + - optionId + - availabilityId + - unitItems + properties: + uuid: + $ref: '#/components/schemas/Uuid' + resellerReference: + $ref: '#/components/schemas/ResellerReference' + productId: + type: string + description: | + A valid product ID that matches the `id` returned from `GET /suppliers/{supplierId}/products`. + example: 'adult' + optionId: + type: string + description: | + A valid option ID that matches the `id` returned from `GET /suppliers/{supplierId}/products`. + example: 'LR1-01' + availabilityId: + type: string + description: | + A valid availability ID that matches the `id` returned from `GET /suppliers/{supplierId}/availability`. + example: '28271273-a317-40fc-8f42-79725a7072a3' + unitItems: + type: array + items: + $ref: '#/components/schemas/UnitItem' + minItems: 1 + holdExpirationMinutes: + type: integer + description: | + This is the duration that the Reseller would like the product inventory to be temporarily held while the booking is completed. The Booking Platform SHOULD reserve the inventory for at least this duration but MAY reserve for a shorter period of time. The exact hold expiration time will be returned in the response. + example: 30 + ExtendReservationRequest: + required: true + description: | + This MUST include the proper `reason` for requesting the extension and MUST NOT be abused to keep availability reserved due to processing delays by the Reseller. + content: + application/json: + schema: + type: object + required: + - reason + - reasonDetails + - holdExpirationMinutes + properties: + reason: + type: string + enum: + - FRAUD_CHECK + - CUSTOMER_REQUESTED + - OTHER + description: | + This indicates the reason for extending the reservation hold. + + * `FRAUD_CHECK` is the most common scenario where additional time is required to ensure that the booking reservation is not being made using fraudulent payment information. + * `CUSTOMER_REQUESTED` is intended for scenarios where the customer is actively completing the checkout process but requires some additional time to complete. The Reseller SHOULD try to ensure that customers may only extend their reservation once. + * `OTHER` can be used in other unusual circumstances but SHOULD not be abused to maintain a hold without good reason. If this value is specified the Reseller SHOULD provide `reasonDetails` to explain the justification. + example: 'FRAUD_CHECK' + reasonDetails: + type: string + nullable: true + description: | + This provides additional details behind the reason for requesting the extension and SHOULD be provided in all cases, but especially if the `reason` given is `OTHER`. + example: 'Manual fraud review with 2-hour SLA.' + holdExpirationMinutes: + type: integer + description: | + This is the duration that the Reseller would like the product inventory hold to be extended while the booking is completed. The Booking Platform SHOULD extend the hold on the inventory for at least this duration from the time of the request but MAY reserve for a shorter period of time. The exact hold expiration time will be returned in the response. + example: 120 + ConfirmReservationRequest: + required: true + description: | + This confirms an existing booking reservation and MUST be sent before the `utcHoldExpiration` has elapsed. + content: + application/json: + schema: + type: object + required: + - contact + properties: + resellerReference: + $ref: '#/components/schemas/ResellerReference' + contact: + $ref: '#/components/schemas/Contact' + CreateCancellationRequest: + required: true + description: | + This SHOULD be sent prior to any cancellation policy cutoff but MAY be sent if requesting a Supplier-approved exception to the policy. It SHOULD also be sent even if no refund is expected so that the Supplier can attempt to sell the unused inventory to another customer. + content: + application/json: + schema: + type: object + required: + - reason + - reasonDetails + properties: + reason: + $ref: '#/components/schemas/Reason' + reasonDetails: + $ref: '#/components/schemas/ReasonDetails' + ConfirmCancellationRequest: + required: true + description: | + This confirms an existing cancellation request and MUST be sent before the `utcHoldExpiration` has elapsed. + content: + application/json: + schema: + type: object + required: + - reason + - reasonDetails + properties: + reason: + $ref: '#/components/schemas/Reason' + reasonDetails: + $ref: '#/components/schemas/ReasonDetails' + + securitySchemes: + apiKey: + type: apiKey + name: Authorization + in: header +tags: + - name: Suppliers + - name: Products + - name: Availability + - name: Bookings + - name: Cancellations diff --git a/drafts/ERROR_HANDLING.md b/drafts/ERROR_HANDLING.md new file mode 100644 index 0000000..daf6124 --- /dev/null +++ b/drafts/ERROR_HANDLING.md @@ -0,0 +1,138 @@ +## Error Reporting: +### Background +1. The ICF specification distinguishes between retryable and non-retryable errors. Typically any error is non-retryable, but there are a few exceptions. + * A **non-retryable** error is an error that indicates human intervention is needed. For example: + * Invalid product / unit ID provided - _This suggests there is a mapping problem due to a changed configuration_ + * Insufficient credits - _The outstanding rolling deposit with the supplier has depleted and a top-up is needed to continue sales. Note that support for this is not included in the core API specification._ + * Authentication failure - _Credentials may need to be updated_ + * A **retryable** error is an error that the consumer of the API generally won't have to take action on. The supplier is likely to find out about the issue and is bound to fix it soon and the call can simply be retried later. This includes situations like: + * Time outs / down-time + * Server errors like HTTP 500 without error object in the response body + * No more availability for a date / timeslot while placing an order +2. Regardless of what type of HTTP status code is returned, an error messages MUST be present. +3. Any response with an HTTP status code outside the 2xx range indicates something is wrong and the response MUST contain an error object. +Typically an HTTP 400 indicates there's something wrong with the input. + +The client is expected to detect and handle errors appropriately. + +### Error Object +The error response is an array of objects and MUST contain one or more error objects. +Although interrupting code execution when encountering an error is a valid practice, it is preferred that a list containing all errors (if applicable) is returned as it enables the consumer to appropriately handle each error in independently, such as raising an alarm. + +**Single Error Object Structure:** +* `errorCode` contains a namespaced error code. When no specific error code applies, use 1000. In serious cases us 9999 accompanied by an HTTP 500 status code. The namespace is used to distinguish between core and specific capabilities the server may use and must match this regular expression: `/[a-z\-]*\/[0-9]{4}/`. +* `errorMessage` contains a clear, human readable error message that properly describes the issue. +* `fieldName` contains a string identifying the field in the HTTP body or parameter in the URL that the error applies to. Use an empty string if not applicable. + + +```javascript +[{ + "errorCode": "core/1001", + "errorMessage": "The `supplierId` provided is missing or invalid.", + "fieldName": "supplierId" +}, +{ + "errorCode": "core/1002", + "errorMessage": "The `productId` provided is missing or invalid.", + "fieldName": null +}, +{ + "errorCode": "core/1000", + "errorMessage": "A generic error has occured. Please review your request before trying again.", + "fieldName": null +}] +``` + + +## Error Codes: +### HTTP level (Global): +|HTTP Status|Retryable|Explanation| +|--|--|--| +|400|Non-Retryable|Invalid request such as missing required query parameters, invalid request model, missing `Timestamp` header that is used to generate HMAC key, etc.| +|401|Non-Retryable|Missing `Authentication` header with HMAC key or key could not be validated| +|403|Non-Retryable|The `Authentication` header was validated but requester does not have correct permissions| +|404|Non-Retryable|Invalid URL path| +|500|Retryable|The server has encountered an error or is otherwise incapable of performing the request. The server _should_ explain the error properly. In exceptional cases where it's unclear what went wrong and the server can't respond in a sensible way then there may not even be an error object in the response body.| +|502|Retryable|An upstream server encountered an error preventing the request from being fulfilled. | +|503|Retryable|The server cannot handle the request (because it is overloaded or down for maintenance). | + + +### Application Level +Application level errors have codes in different ranges. +* 1000 - 1499 range for field validation / input errors +* 1501 - 1999 range for business logic violations +* 9999 is for unhandled and unexpected situations +* Capabilities may use overlapping error codes but will be uniquely namespaced. See the capability's documentation for more details. + +## Complete Error Overview +### HTTP 400 +The errors outlined below are believed to be errors that one will want to act / alert on as they impact the ability to continue the logical call-flow. +They MUST be accompanied by an HTTP 400 status code + +**Generic** +- `ErrorCode` 1499: Something is wrong with the input. `errorMessage` should indicate what exactly. +- `ErrorCode` 1498: Unable to create entity. `errorMessage` should indicate what exactly. Use when unable to process a POST request and no more explicit error code is available. +- `ErrorCode` 1497: Unable to update entity. `errorMessage` should indicate what exactly. Use when unable to process a PUT/PATCH request and no more explicit error code is available. + +**Supplier** +No errors identified + +**Products** +- `ErrorCode` 1001: `supplierId` not found / invalid +- `ErrorCode` 1002: `productId` not found / invalid +- `ErrorCode` 1003: `optionId` not found / invalid + +**Availability** +- `ErrorCode` 1001: `supplierId` not found / invalid +- `ErrorCode` 1002: `productId` not found / invalid +- `ErrorCode` 1003: `optionId` not found / invalid +- `ErrorCode` 1004: `availabilityId` not found / invalid + +**Reservations** +- `ErrorCode` 1001: `supplierId` not found / invalid +- `ErrorCode` 1002: `productId` not found / invalid +- `ErrorCode` 1003: `optionId` not found / invalid +- `ErrorCode` 1004: `availabilityId` not found / invalid +- `ErrorCode` 1005: `uuid` of reservation/booking not found / invalid +- `ErrorCode` 1501: Reservation expired +- `ErrorCode` 1502: Hold period too long +- `ErrorCode` 1503: Cannot extend hold period +- `ErrorCode` 1504: Reservation ID not found +- `ErrorCode` 1505: No more availability + +**Bookings** +- `ErrorCode` 1001: `supplierId` not found / invalid +- `ErrorCode` 1005: `uuid` of reservation/booking not found / invalid +- `ErrorCode` 1501: Reservation expired + +**Cancellations** +- `ErrorCode` 1005: `uuid` of reservation/booking not found / invalid +- `ErrorCode` 1506: Cancellation not allowed + +### HTTP 500 +System errors that occur in the software that embeds the ICF may cause erratic behavior or cause hard-to-map errors. Those should be thrown with an HTTP 500 status code. +```javascript +[{ + "errorCode": 9999, + "errorMessage": "System.outOfMemoryException thrown in /some/file", + "fieldName": null +}] +``` +### Other guidelines +- An error during a "create" action such as placing a booking should be a guarantee that the booking was not finalized and shall not be charged. **ONE FOR THE SLA MAYBE?** + + + + +## NOT FOR PUBLISHING +### Error handling thoughts and context** +> * There could be many different errors which could be caused by an invalid request. Only the ones that are likely to be non-retryable have been assigned their own error code. +> * Errors that happen in the software that embeds the ICF specification are not easy to standardize. The standard response for that type of error should be of HTTP 500 as it suggests something more serious went wrong, such as: +> * Being unable to connect to a database +> * An error occurring in the booking software and it's not reasonably possible to return even a proper error object can be returned. +> * One is expected to use HTTP 400 often. +> * An exhaustive list of all possible request object fields that could be missing or invalid has been skipped, but _may_ be added in the future. +> * Per definition an error is non-retryable. Only non-retryable errors need to be explained and have an error code so that they can be handled programmatically +### Ground rules for extending this document +* New error codes MAY be introduced for "*non-retryable*" errors only. A retryable error suggests that it is actually a warning (which the API specification doesn't support) or it is an error due to a failure in an upstream system. (So not in the system you are communicating with directly, but another system further down the line that it relies upon.) +* Identical errors situations MUST be assigned a single error code where possible.