Connecting to us

Build on the product your clients have contracted

The Contracting API lets your booking platform, your agents and third parties sell the product a client has loaded straight into the engine: accommodation, car hire, transfers, attractions, flights, tours, coaches and trains. You build against it. We stay the source of truth for product, price and availability.

  • REST and JSON
  • Bearer keys, server to server
  • Read-only test keys from day one
cURLYour first call
curl https://contracts.travelify.io/api/v1/properties?property_type=hotel \
  -H "Authorization: Bearer sk_test_demo"
200 OKA page of products your key can sell
The shape of it

Three moves from search to a booking

  1. 01

    Browse

    List and read the product a client has contracted: accommodation, car hire, transfers, attractions, flights, tours, coaches and trains, with full content.

  2. 02

    Price

    Ask for real dates and guests, across many products in one call. Every result is complete: all the bookable options priced with a rate reference, the extras and the full product detail.

  3. 03

    Book

    Quote, hold, then confirm. The confirmed booking lands read-only on the client's calendar.

Request to result

The call on the left. The web page on the right.

Every endpoint returns clean JSON that drops straight into your interface. Here is the same data as your customer would see it.

You send
POST/api/v1/properties/{property_id}/availability
{
  "check_in": "2026-07-10",
  "check_out": "2026-07-17",
  "occupancy": {
    "adults": 2,
    "children": [6]
  }
}
Your customer sees

Vale do Sol Beach Hotel

Twin Garden View · B&B
Early booking 10%
€1,0437 nights · 2 adults, 1 child
Book

Booking more than one room

The same endpoint takes a group two other ways. Tell it who is in each room, or just how many people there are and let it work out what fits. Either way you get back one price and one reference that books the whole group in a single call.

You know the rooms
POST/api/v1/properties/{property_id}/availability
{
  "check_in": "2026-07-10",
  "check_out": "2026-07-17",
  "rooms": [
    { "adults": 2, "children": [6] },
    { "adults": 2, "children": [] }
  ]
}
You only know the group
POST/api/v1/properties/{property_id}/availability
{
  "check_in": "2026-07-10",
  "check_out": "2026-07-17",
  "party": { "adults": 8, "children": [] }
}
You send
POST/api/v1/bookings/{id}/confirm
{
  "payment_reference": "your_pmt_ref_123",
  "payment_status": "paid",
  "amount_paid": { "amount": 1043.00, "currency": "EUR" }
}
Your customer sees
ConfirmedTGC-7F3K9Q

Vale do Sol Beach Hotel

Twin Garden View · B&B
Dates
10–17 Jul 2026 · 7 nights
Lead guest
Sarah Hughes
Total paid
€1,043.00
Status
Live on the calendar
Now a read-only reservation on the client's calendar
Recently added

New on the search surface

Recent additions to how you search and build your booking flow. All live now, and existing calls keep working unchanged.

FlightsSeptember 2026

Search by city, not just by airport

Flight searches now take IATA city codes. LON searches LHR, LGW, STN, LTN, LCY and SEN; NYC searches JFK, EWR, LGA and SWF. Before this a city code matched nothing at all, because contracted inventory is always loaded against the airport a flight really uses, so no contract ever carries LON. Origin and destination resolve independently, so LON to NYC covers 24 airport pairs in one call, and every result names the real airport rather than the city code. Cities whose code is not an airport at all now work for the first time, such as TCI for Tenerife and YEA for Edmonton. One thing to check: many city codes are also airport codes, and the city wins, so asking for IST now also returns Sabiha Gokcen and LAX also returns Ontario. Send airport_only true, or origin_airport_only and dest_airport_only for one side, to take the code literally. Send include_secondary_airports false to search a city's main airports only, dropping Beauvais from Paris and Ontario from Los Angeles. An unrecognised code still matches nothing: there is no fuzzy or nearest airport matching.

See the reference
TransfersSeptember 2026

The breakdown says which supplement applied

Each transfer breakdown line can now carry pickup_time, a surcharges array naming every rule that applied to that leg, and surcharge_total. Before this you could see the leg home cost 50 more and had no way to say why. The surcharge fields explain the line rather than adding to it: the line's amount already includes them, and summing every amount still gives the option price, so do not add surcharge_total on top. Watch the difference between the two numbers: surcharges[].amount is the rule as the operator set it, surcharge_total is what it actually added, and they differ whenever there is a markup because a supplement lifts the base before markup. A fixed 50 on a net rate marked up 20% reads amount 50 and surcharge_total 60. On a per person basis surcharge_total covers the whole party. Both keys are absent when nothing applied.

See the reference
TransfersSeptember 2026

The journey direction is worked out for you

POST /api/v1/transfers/availability/search no longer makes you restate which way a journey runs when you have already said it. Name an airport at one end and a place at the other and it is taken from those: an origin of GVA with a destination near Chamonix is an arrival, and the reverse is a departure. The response echoes direction and direction_source so you can see which happened, and an explicit direction always wins. You still only ever send ONE direction, for the outbound leg, because the leg home is flipped for you. Airport to airport, and two ends that name no airport, stay any: a flight_time cannot become a pickup until the direction is resolved, and read the wrong way round a 09:00 flight home would be priced as a 09:00 pickup when the vehicle actually collects hours earlier, so a night supplement would be missed. Those are still refused rather than guessed at.

See the reference
CoachesSeptember 2026

Every sailing carries its cruise line

line now sits alongside ship on sailings[] and on every sailing option, so a picker can lead with the cruise line and the ship, which is what a passenger recognises. Many do not know the cruise's marketing name at all, so name is still there but as the thing that tells two cruises on one ship and one date apart rather than the label to show. POST /api/v1/coaches/availability takes line: with no date it lists that line's sailings so you can build the ship dropdown from one call, and with a date it narrows that date. It is matched on the line alone, never as a substring, and combines with ship. The loose sailing filter now searches the line too. Note that operator on a route is the coach company, not the cruise line. No sailing_id changes: the line is descriptive rather than identity, so it is deliberately left out of the id and naming it on an existing sailing invalidates nothing.

See the reference
CoachesSeptember 2026

Search a cruise feeder by ship

Most cruise passengers know their ship and their sailing date, not the cruise's marketing name, so POST /api/v1/coaches/availability now takes ship. Send it with no date and you get that ship's sailings instead of one day's search: one row per outbound coach date, in date order, each with the cruise name, the day the coach brings them home, the seats left and the sailing_id to send straight back for both priced legs. That is the second dropdown in a ship-first search box; build the first from GET /v1/coaches?include=departures. Send ship with a date and it narrows that date instead. Ship plus date is not always unique, because one ship can run two cruises fed by the same coach that come home on different days, so show the return date and always carry the sailing_id. A sold out or closed date comes back with bookable false rather than missing, so grey it rather than dropping it. Note ship is now matched on the ship alone and never as a substring; use sailing for a loose match across the name, ship and voyage reference.

See the reference
CoachesSeptember 2026

Ask for the cruise, not the date

POST /api/v1/coaches/availability now takes a sailing_id instead of a date, and answers with exactly two results: that sailing's outbound coach and its inbound coach, each priced on its own with its own rate_reference and labelled leg. No other cruises, no other dates, no other routes. Every departure's sailings now carry a sailing_id, and passing the same one to /bookings/quote takes the coach home from the sailing itself. This matters when one run feeds two cruises that come home on different days: a return_date names a coach, not a cruise, so it could not tell them apart. Add sailing to a normal date search to narrow it to one cruise.

See the reference
TransfersAugust 2026

Point-to-point search

Send an origin and a destination to the transfer availability search to get only the routes that run between a specific pickup and drop-off, instead of everything around one place. Each end is an airport code, e.g. origin: { "airport_code": "GVA" }, or a place with a small radius, e.g. destination: { "near": { "lat": 45.79, "lon": 6.97, "radius_km": 5 } }. Aliases from / pickup and to / dropoff are accepted.

See the reference
CoachesAugust 2026

Destination coordinates

Coach routes now return origin_location and destination_location, resolved by direction, so you can place the drop-off on a map or search by it. On an outbound run destination_location is the terminal (e.g. the airport); on a return it flips. The pick-up towns keep their own coordinates on pick_up_points.

See the reference
CoachesAugust 2026

Sailings on each departure

For a coach that feeds a scheduled service (a cruise-port transfer, say), every departure now carries a sailings array with the sailing name, ship and reference. Build the search box in the natural order: pick the sailing, then its date, then the pick-up. A run can list more than one sailing when two services leave on the same day. A plain point-to-point coach simply omits the field, so the same code handles both.

See the reference
CoachesAugust 2026

List coaches in one call

Add include to GET /v1/coaches to embed the full route detail on every result, so you no longer list and then call Get a coach route per route. Use include=all, or pick from pick_up_points, passenger_types, seasons and departures (departures carry their sailings). Only the current page is expanded, so page through large catalogues.

See the reference
The surface

Every endpoint in v1

Eight product families on one surface, sharing one auth, one error model and one booking flow. Every family has a bulk priced search that returns complete results in one call: all the bookable options with their rate references, the extras and the full product detail, so you never make follow-up calls per result.

Read

  • GET/api/v1/propertiesSearch accommodation (filters, id basket, map radius)
  • POST/api/v1/properties/searchSame search as a JSON body
  • POST/api/v1/properties/availability/searchPriced availability across many properties
  • GET/api/v1/properties/{id}Full property content
  • POST/api/v1/properties/{id}/availabilityPrice a property for dates and guests
  • GET/api/v1/carsSearch car hire (filters, id basket, map radius)
  • GET/api/v1/cars/{id}Full car product content
  • POST/api/v1/cars/{id}/availabilityPrice a vehicle for dates and driver age
  • POST/api/v1/cars/searchPriced availability across many car products
  • GET/api/v1/transfersSearch transfers (filters, id basket, map radius)
  • POST/api/v1/transfers/searchSame search as a JSON body
  • POST/api/v1/transfers/availability/searchPriced availability across many transfers, or point-to-point with origin and destination
  • GET/api/v1/transfers/{id}Full transfer product content
  • POST/api/v1/transfers/{id}/availabilityPrice a transfer for a journey
  • GET/api/v1/ticketsSearch attractions and events (filters, id basket, map radius)
  • POST/api/v1/tickets/searchSame search as a JSON body
  • POST/api/v1/tickets/availability/searchPriced availability across many attractions
  • GET/api/v1/tickets/{id}Full attraction content (options, ticket types, extras)
  • POST/api/v1/tickets/{id}/availabilityPrice an attraction for a visit date
  • GET/api/v1/flightsSearch flight deals (airline, origin, destination)
  • POST/api/v1/flights/searchSame search as a JSON body
  • POST/api/v1/flights/availability/searchPriced availability across many deals for a route
  • GET/api/v1/flights/{id}Full deal content (airports, routes, flights, aircraft, fares)
  • POST/api/v1/flights/{id}/availabilityPrice a route for a date and passengers
  • POST/api/v1/flights/{id}/seatmapSeat map for the seat picker
  • GET/api/v1/toursSearch tours (filters, id basket, map radius)
  • POST/api/v1/tours/searchSame search as a JSON body
  • POST/api/v1/tours/availability/searchPriced departures across many tours for a date window
  • GET/api/v1/tours/{id}Full tour content (itinerary, room bases, departures, spaces)
  • POST/api/v1/tours/{id}/availabilityPrice a departure for a traveller basket
  • GET/api/v1/coachesSearch coach and minibus services (filters, id basket, map radius)
  • POST/api/v1/coaches/searchSame search as a JSON body
  • POST/api/v1/coaches/availabilityPriced availability across routes by pick-up, destination and date
  • GET/api/v1/coaches/{id}Full coach route content (pick-up points, passenger types, departures)
  • POST/api/v1/coaches/{id}/availabilityPrice a departure by pick-up point
  • POST/api/v1/coaches/{id}/seatmapSeat map for the seat picker (when the route has a seat plan)
  • GET/api/v1/trainsSearch train services (filters, id basket, map radius)
  • POST/api/v1/trains/searchSame search as a JSON body
  • POST/api/v1/trains/availabilityPriced availability across services by boarding, destination and date
  • GET/api/v1/trains/{id}Full train service content (stations, passenger types, departures)
  • POST/api/v1/trains/{id}/availabilityPrice a departure by boarding station
  • POST/api/v1/trains/{id}/seatmapSeat map for the seat picker (when the service has a seat plan)

Trains mirror coaches. Booking is identical across products; for search, copy your coach calls, change the path to /v1/trains, and rename a few fields: pick_up_points to stations, pick_up to station (boarding_station on the booking), vehicle to service_class, coach_id to train_id, and the bulk pick_up filter to boarding. A train rate_reference starts with train_.

Booking

  • POST/api/v1/bookings/quoteLock a price from a rate reference
  • POST/api/v1/bookings/holdReserve inventory and attach the guests
  • POST/api/v1/bookings/{id}/confirmCommit once you have taken payment
  • GET/api/v1/bookingsList bookings (filter by reference, external ref or status)
  • GET/api/v1/bookings/{id}Retrieve a booking
  • PATCH/api/v1/bookings/{id}Amend guest details
  • POST/api/v1/bookings/{id}/cancelCancel and apply the policy
  • GET/api/v1/bookings/{id}/voucherBranded PDF voucher for the booking

Ask the API assistant

Stuck on something? Ask in plain English and get a step-by-step answer grounded in these docs.

Two keys, one API

A sk_test_ key is read only: it searches, prices and reads, against the same data a live key sees, so the prices it quotes are the real ones. It cannot create a booking, and a hold returns 403. There is no separate sandbox to write into, and we would rather refuse than let a test consume a client's actual seats. To test a booking end to end, ask us for a live key on a demo account, which exists for exactly that.

test sk_test_…live sk_live_…
Everything you need

Get the docs

Experienced developers can self-serve from here. If you would rather be walked through it, the written reference reads top to bottom.