Book
You can use this POSTMAN collection to test the API.
3. Create a cart
The first step to make a booking is to create a cart. It has to contain at least an outboundTripId
which you can retrieve with a search and minimal passenger information for it to work.
curl --location --request POST 'https://api.tictactrip.eu/booking/v3/carts' \
--header 'Authorization: Bearer token' \
--header 'Content-Type: application/json' \
--data-raw '{
"outboundTripId": "91c2b506-6667-46d4-ac7d-886b2a91cd01",
"passengers": [
{
"age": 30,
"discountCards": []
}
]
}
'
Response body example
{
"cart": {
"id": "bf58e6091bd04eb2",
"partnerId": null,
"partnerExternalId": null,
"paidPrice": 1420,
"rawPrice": 1320,
"trips": {
"outboundTrip": {
"id": "1165038",
"uuid": "91c2b506-6667-46d4-ac7d-886b2a91cd01",
"origin": {
"id": 12709,
"city": "Pralognan La Vanoise",
"country": "France",
"station": "PRALOGNAN LA VANOISE Airelles"
},
"destination": {
"id": 549,
"city": "Moûtiers",
"country": "France",
"station": "Moûtiers - Salins-les-Thermes - Brides-les-Bains"
},
"originOffset": "+0200",
"destinationOffset": "+0200",
"priceCents": 1420,
"feeCents": 0,
"paidPrice": 1420,
"departureUTC": 1663561920,
"arrivalUTC": 1663566300,
"durationMinutes": 73,
"companies": ["Altibus"],
"segments": [
{
"id": "1764105",
"company": "Altibus",
"mean": "bus",
"origin": {
"city": "Pralognan La Vanoise",
"country": "France",
"station": "PRALOGNAN LA VANOISE Airelles",
"lat": 45.3819,
"long": 6.72121
},
"destination": {
"city": "Moûtiers",
"country": "France",
"station": "Moûtiers - Salins-les-Thermes - Brides-les-Bains",
"lat": 45.4852,
"long": 6.5291
},
"isBookable": true,
"priceCents": 1420,
"feeCents": 100,
"includedProviderFeeCents": 120,
"departureUTC": 1663561920,
"arrivalUTC": 1663566300,
"originOffset": "+0200",
"destinationOffset": "+0200",
"durationMinutes": 73,
"redirectionLink": "/redirect?company=21&trip=MOG577&date=19/09/2022&env=production"
}
],
"mean": "BUS",
"co2g": 669
}
},
"passengers": [
{
"id": 1445775,
"lastName": null,
"firstName": null,
"title": null,
"isCustomer": false,
"discountCardId": null,
"category": "adults",
"discountCards": [],
"age": 30,
"birthdate": null
}
]
}
}
4. Update the cart
PATCH /booking/v3/carts/{cart_id}
Now that you have created a cart, you will have to update it with the information of the passengers and the customer.
You must pass the id of the cart previously created as a parameter in the url.
Some of our providers require governement issued IDs to be passed for each passenger in the identityDocument object.
Some of those carriers are the spanish companies RENFE and iryo.
On March 25 2024, Eurostar routes going to or departing from the UK will require passport details for each passenger.
A complete list can be found on this endpoint by looking at the requiresIdentityDocument
field.
curl --location --request PATCH 'https://api.tictactrip.eu/booking/v3/carts/bf58e6091bd04eb2' \
--header 'Authorization: Bearer token' \
--header 'Content-Type: application/json' \
--data-raw '{
"passengers": [
{
"lastName": "lastName",
"discountCards": [],
"isCustomer": true,
"title": "MR",
"firstName": "firstName",
"category": "adults",
"age": 30,
"birthdate": "1992-09-09"
}
],
"customer": {
"lastName": "lastName",
"mail": "mail@mail.com",
"isSubscribedToNewsletter": false,
"title": "MR",
"firstName": "firstName",
"birthdate": "1992-09-09"
}
}'
Response body example
{
"cart": {
"id": "bf58e6091bd04eb2",
"partnerId": null,
"partnerExternalId": null,
"paidPrice": 1180,
"rawPrice": 1080,
"trips": {
//...
},
"passengers": [
{
"id": 1445778,
"lastName": "lastName",
"firstName": "firstName",
"title": "MR",
"isCustomer": true,
"discountCardId": null,
"category": "adults",
"discountCards": [],
"age": 30,
"birthdate": "1992-09-09T00:00:00.000Z"
}
],
"customer": {
"mail": "mail@mail.com",
"title": "MR",
"lastName": "lastName",
"firstName": "firstName",
"birthdate": "1992-09-09T00:00:00.000Z"
}
},
"priceCentsDiff": 0,
"isAvailable": true
}
A price lookup is done everytime a cart is updated.
Select fare (optionnal)
PATCH /booking/v3/carts/{cart_id}
Fare selection is only available for Eurostar, Thalys, iryo, ITALO, Renfe, and SNCF brands on applicable journeys.
When a cart is patched, all the fares available for each segment are stored in the alternativeSegments object. By default the lowest price is always selected.
You can choose your fare, segment by segment by mapping each segment id
to its fare id
in the alternativeSegments object.
The following example is based on this first patch response:
Let's say you want to book your first segment in first class, and your last one in second class. You must do the following request:
curl --location --request PATCH 'https://api.tictactrip.eu/booking/v3/carts/bf58e6091bd04eb2' \
--header 'Authorization: Bearer token' \
--header 'Content-Type: application/json' \
--data-raw '
{
"segmentAlternatives": {
123: "6ad76a7aef53",
...
127: "1068928c"
}
}'
A price lookup is done everytime a cart is updated.
Response body example
{
"cart": {
"id": "e8fcd9aec36c489d",
"partnerId": "",
"partnerExternalId": null,
"paidPrice": 14200,
"rawPrice": 14200,
"totalPrice": 14200,
"trips": {
"outboundTrip": {
"id": "1523320",
"uuid": "c4c55711-2f93-4a21-b42a-87ab40f14833",
"origin": {
"id": 628,
"city": "Paris",
"country": "France",
"station": "Paris Gare de Lyon"
},
"destination": {
"id": 485,
"city": "Lyon",
"country": "France",
"station": "Lyon Part Dieu"
},
"originOffset": "+0200",
"destinationOffset": "+0200",
"priceCents": 14200,
"feeCents": 0,
"paidPrice": 14200,
"departureUTC": 1686590640,
"arrivalUTC": 1686597720,
"durationMinutes": 118,
"companies": [
"TGV Inoui"
],
"segments": [
{
"id": "2325470",
"company": "TGV Inoui",
"mean": "train",
"origin": {
"city": "Paris",
"country": "France",
"station": "Paris Gare de Lyon",
"lat": 48.8566,
"long": 2.3515
},
"destination": {
"city": "Lyon",
"country": "France",
"station": "Lyon Part Dieu",
"lat": 45.764043,
"long": 4.835659
},
"isBookable": true,
"priceCents": 14200,
"feeCents": 0,
"includedProviderFeeCents": 0,
"departureUTC": 1686590640,
"arrivalUTC": 1686597720,
"originOffset": "+0200",
"destinationOffset": "+0200",
"durationMinutes": 118,
"redirectionLink": null,
"bookingClass": "1ère classe",
"fareName": "BUSINESS PREMIERE",
"conditions": [
"Echange et remboursement sans frais jusqu'à 30 min après départ.",
"Dès 30 min avant départ, billet échangeable 2 fois max (même jour, même trajet) et non remboursable après 1 échange.",
"Si emprunt d’un autre train, échange obligatoire."
],
"vehicleIdentifier": "6669"
}
],
"mean": "TRAIN",
"co2g": 150
}
},
"passengers": [
{
"id": 1880413,
"lastName": "Doe",
"firstName": "John",
"title": "MR",
"isCustomer": true,
"discountCardId": null,
"category": "youths",
"discountCards": [],
"age": 24,
"birthdate": "1999-01-01T00:00:00.000Z"
}
],
"customer": {
"mail": "john.doe@tictactrip.eu",
"title": "MR",
"lastName": "Doe",
"firstName": "John",
"birthdate": "1999-01-01T00:00:00.000Z"
}
},
"priceCentsDiff": 0,
"isAvailable": true,
"segmentAlternatives": {
"2325470": {
"e1429d43-91a5-47ac-824a-1630f39cf076": {
"id": "e1429d43-91a5-47ac-824a-1630f39cf076",
"arrivalOffset": "+0200",
"arrivalUnixUtc": 1686597720,
"bookingClass": "2_ND_CLASS",
"brandName": "TGV INOUI",
"co2g": 150,
"conditions": [
"Billet échangeable (ajustement au tarif en vigueur) et remboursable uniquement avant départ : 19 € de frais dès 6 jours avant départ.",
"Dès 30 min avant départ, billet échangeable 2 fois max (même jour, même trajet) et non remboursable après 1 échange."
],
"departureOffset": "+0200",
"departureUnixUtc": 1686590640,
"destinationProviderStopId": "FRUQQ",
"fareName": "PREM'S",
"durationMinutes": 118,
"feeCents": 0,
"providerId": "18",
"includedProviderFeeCents": 0,
"isAvailable": true,
"isBookable": true,
"originProviderStopId": "FRPLY",
"priceCents": 2500,
"segmentProviderBrandId": "VSC",
"segmentProviderId": 10,
"transportType": "train",
"vehicleIdentifier": "6669",
"segmentProviderSegmentRefId": "ea4642de-aab7-4cce-a85d-7fb7256564b4",
"includedBaggage": true,
"notAvailableReason": null
},
"4052b766-94c4-4e1d-851b-c0e0b312ea28": {
"id": "4052b766-94c4-4e1d-851b-c0e0b312ea28",
"arrivalOffset": "+0200",
"arrivalUnixUtc": 1686597720,
"bookingClass": "1_ST_CLASS",
"brandName": "TGV INOUI",
"co2g": 150,
"conditions": [
"Echange et remboursement sans frais jusqu'à 30 min après départ.",
"Dès 30 min avant départ, billet échangeable 2 fois max (même jour, même trajet) et non remboursable après 1 échange.",
"Si emprunt d’un autre train, échange obligatoire."
],
"departureOffset": "+0200",
"departureUnixUtc": 1686590640,
"destinationProviderStopId": "FRUQQ",
"fareName": "BUSINESS PREMIERE",
"durationMinutes": 118,
"feeCents": 0,
"providerId": "18",
"includedProviderFeeCents": 0,
"isAvailable": true,
"isBookable": true,
"originProviderStopId": "FRPLY",
"priceCents": 14200,
"segmentProviderBrandId": "VSC",
"segmentProviderId": 10,
"transportType": "train",
"vehicleIdentifier": "6669",
"segmentProviderSegmentRefId": "6266132b-fe0c-421c-a821-9cef60c71fbf",
"includedBaggage": true,
"notAvailableReason": null
}
}
}
}
5. Create an order
The creation of an order is done from a cart, so you will not be able to create an order if you have not created and updated the cart before.
You must pass the cartId
in the body of the request.
curl --location --request POST 'https://api.tictactrip.eu/booking/v3/orders' \
--header 'Authorization: Bearer token' \
--header 'Content-Type: application/json' \
--data-raw '{
"cartId": "bf58e6091bd04eb2"
}'
Response body example
{
"order": {
"id": "fde73760a5354d18",
"orderDate": "2022-09-13T13:59:06.070Z",
"cartId": "bf58e6091bd04eb2",
"orderStatus": "CREATED",
"trips": {
//...
},
"passengers": [
{
//...
}
],
"customer": {
//...
},
"paymentStatus": "ACCEPTED"
},
"priceCentsDiff": 0,
"isAvailable": true,
"expiresAt": "2022-09-13T14:14:06.070Z"
}
When creating an order an option is placed on the ticket(s). Depending of the company selling the ticket(s), the time at which the availability and price is guaranted may vary.
6. Book an order
POST /booking/v3/orders/{order_id}/book
This environment is directly connected to our production. This means that any book will result in the generation of REAL tickets.
Before making a BOOK, you must:
- Notify us in advance, using the email address care@tictactrip.eu
- Check the trip is fully CANCELLED and fully REFUNDABLE
The booking is done asynchronously from an order.
You must pass the id of the order previously created as a parameter in the url.
curl --location --request POST 'https://api.tictactrip.eu/booking/v3/orders/fde73760a5354d18/book' \
--header 'Authorization: Bearer token'
Response body example
{
"order": {
"id": "fde73760a5354d18",
"rawPrice": 1080,
"paidPrice": 1180,
"totalPrice": 1180,
"orderDate": "2022-09-13T15:54:21.975Z",
"cartId": "bf58e6091bd04eb2",
"orderStatus": "PENDING_BOOKING",
"trips": {
//...
},
"passengers": [
{
//...
}
],
"customer": {
//...
},
"paymentStatus": "ACCEPTED"
}
}
Check the full description of this request.
7. Get an order status
GET /booking/v3/orders/{order_id}
As the booking is done asynchronously, you have to call this endpoint to know if the booking was successful or not.
If the orderStatus
is set to SUCCESS
, then the booking went well.
curl --location --request GET 'https://api.tictactrip.eu/booking/v3/orders/fde73760a5354d18' \
--header 'Authorization: Bearer token'
Response body example
{
"order": {
"id": "fde73760a5354d18",
"rawPrice": 1080,
"paidPrice": 1180,
"totalPrice": 1180,
"orderDate": "2022-09-13T15:54:21.975Z",
"cartId": "bf58e6091bd04eb2",
"orderStatus": "SUCCESS",
"trips": {
//...
},
"passengers": [
{
//...
}
],
"customer": {
//...
},
"paymentStatus": "ACCEPTED"
}
}
A booking can be long, it may be necessary to call this endpoint several times.
8. Download tickets
GET /booking/v3/orders/{order_id/ticket?filename={file_name}
Once you have validated that the booking was successful by getting the order you can download the tickets of the order. You must pass the id of the order and the file name you want to give to the file as a parameter in the url.
curl --location --request GET 'https://api.tictactrip.eu/booking/v3/orders/fde73760a5354d18/ticket?filename=ticketName' \
--header 'Authorization: Bearer token'
Response body example
If there are several tickets in the order, they will all be in the same pdf.
Ticket cancellation
Get cancellation conditions
GET /booking/v3/orderTickets/{order_id}/cancellation/conditions
We offer ticket cancellation on a per segment basis, for all the passengers. This is two step process, you will first ask for cancellations conditions and then cancel the ticket if you wish.
To retrieve the cancellation conditions, you must pass the id of the segment you want to cancel. It can be found in the order summary.
This endpoint will provide:
- A
refundable
field which is a boolean indicating if the ticket is cancellable or not. - A
feeCents
field which corresponds to the fees the carrier will apply if you cancel the ticket. - A
cutoff
field providing the date and time until which you can cancel the ticket with the given conditions. - An
isRefundedByVoucher
field indicating if the refund will be done by voucher or cash. Be aware that those vouchers aren't usable on Tictactrip's inventory but only for the carrier that issued them.
Request example for cancellation conditions retrieval
Request:
curl --location 'https://api.tictactrip.eu/booking/v3/orderTickets/496315/cancellation/conditions' \
--header 'Authorization: Bearer' \
--header 'Content-Type: application/json'
Response:
{
"isCancellable": true,
"feeCents": 0,
"cutoff": "2023-05-26T14:00:00.000Z",
"bookingId": "Q6NX4V",
"isRefundedByVoucher": false
}
If the provided conditions are acceptable, you can then cancel the ticket by calling the next endpoint.
Check the full description of this request.
Request a ticket cancellation
PUT /booking/v3/orderTickets/{order_id}/cancellation
Request example for a segment cancellation
Request:
curl --location --request PUT 'https://api.tictactrip.eu/booking/v3/orderTickets/496315/cancellation' \
--header 'Authorization: Bearer' \
--header 'Content-Type: application/json' \
--data ''
Response:
{
"createdAt": "2023-05-26T14:33:28.338Z",
"id": 3184,
"orderTicketCancellationId": 2153,
"refundedAmountCents": null,
"message": null,
"status": "INPROGRESS",
"refundType": null,
"refundExpirationDateUtc": null,
"updatedAt": "2023-05-26T14:33:28.335Z",
"deletedAt": null
}
As for bookings cancellations are treated asynchronously, you can check the order summary endpoint to know if the cancellation was successful or not.
The order summary returns the current status of an Order with the orderStatus
property. In case of cancellation, the orderStatus
may either be CANCELED
or CANCELED_PARTIAL
. The CANCELED_PARTIAL
status means that at least one segment of the order has been canceled but not all of them. The orderStatus
is CANCELED
only when all segments of the order have been canceled and/or refunded.
On the other hand, the current status of a segments is given by the bookingStatus
property. In case of cancellation, the status of a segments may either be CANCELED
or REFUNDED
. The CANCELED
status means that the cancellation of the segments is successful. At this point, the cancellation may lead to a refund with a voucher or a refund with a wire transfer. Only in case of wire transfer and only if the call for a wire transfer is successful, the bookingStatus
will be REFUNDED
.
In case of cancellation, another property is available on the segments. cancellationStatus
may also be used to keep track of the current status of the cancellation. Its possible values are INPROGRESS
, ACCEPTED
, REFUNDED
and ERRORED
:
INPROGRESS
when the cancellation is still being processed.ACCEPTED
when the cancellation is successful. Terminal status in case of refund with a voucher.REFUNDED
when the cancellation is successful and refunded with a wire transfer.- If ever an error occurs during the cancellation process, the
bookingStatus
will stay at its latest know status and thecancellationStatus
will be set toERRORED
.