Integration flow
Treat the API as a selection flow. A product tells you what can be sold, a departure tells you when it runs, departure detail tells you which price and room options are valid, price preview confirms the selected total and coupon, and only then should you create a contact or reservation.
-
1Find a product
GET /productsorPOST /products -
2Choose a departure
GET /products/{id}/departures -
3Choose price and rooms
GET /products/{product_id}/departures/{departure_id} -
4Preview price and coupon
POST .../price-preview -
5Create contact or reservation
POST .../contactsorPOST .../reservations
"rooms":{"double":1} means one double room for two passengers, so reservation creation must send exactly two passenger objects.
Not sure where to start with products?
If the catalog is large and you have no obvious first product to show, do not page through the raw /products list hoping to find something. Start with discovery: /product-groups rolls every visible product up by destination or by category, so you can let the traveller pick a place or a theme first and then drill into just the products that match.
Say someone is planning a trip to Europe. Group products by destination at the continent level, find the Europe group in the response, and follow its products_url — that link is a ready-made /products drill-down that returns every product travelling there. From that shorter list you are right back on the main flow: choose a departure, load its detail to pick id_price and rooms, preview the selected price or coupon if needed, then create a contact or reservation.
curl -sS 'https://api.deturista.com/v1/product-groups?by=destination&group=continent&include_top_ranked=1' \
-H 'X-API-Key: YOUR_API_KEY'
Prefer browsing by interest rather than by map? Swap by=destination for by=category to group the same catalog by theme instead. Either way each group comes back with a product total and its own products_url, and setting include_top_ranked=1 adds a few real product previews so you have something to display before the traveller has even picked. See Product groups for the full set of grouping options.
Quick start
Ask DeTurista for an API key, then send it in the X-API-Key header. Keep the key private and do not put it in browser-side JavaScript.
Your first call
Got your key? Paste this into a terminal (swap in your key) for a successful 200 right away. It lists one product so you can confirm auth and see the response envelope before building anything.
curl -sS 'https://api.deturista.com/v1/products?limit=1' \
-H 'X-API-Key: YOUR_API_KEY'
Success looks like { "success": 1, "data": { "total": …, "results": [ … ] } }. A 401 unauthorized means the key is missing or wrong — check the X-API-Key header. From here, follow the Integration flow to turn a product into a contact or reservation.
GET example
curl -sS 'https://api.deturista.com/v1/products?limit=2&page=1' \
-H 'X-API-Key: YOUR_API_KEY'
| Rule | How to use this response |
|---|---|
| Authenticate every call | Send X-API-Key from your server. Do not expose the key in browser-side JavaScript. |
| Store the product id | Use data.results[].id as the product_id for product detail and departure calls. |
| Read sale flags | can_buy enables payment_option=buy. can_deposit enables payment_option=deposit when the selected departure price also has deposit values; it is false when the product has deposit configured but no applicable installments. |
POST example
curl -sS 'https://api.deturista.com/v1/products' \
-X POST \
-H 'Content-Type: application/json' \
-H 'X-API-Key: YOUR_API_KEY' \
-d '{"limit":2,"page":1,"id_destination":410,"id_category":154,"month":6,"year":2026}'
| Rule | How to use this response |
|---|---|
| Same search contract | POST accepts the same filters as GET. Use it when a JSON body is easier for your integration. |
| Use 1-based pagination | page=1 is the first page. limit is capped at 100. |
| Images come ready to use | Every image is an absolute URL: both the single image field — products, categories, destinations, hotels, circuits — and the url of each photo in a gallery. Do not build image paths yourself. The base URL follows the configuration for the request host, including local and test hosts. Use the returned URL as-is. |
Exact IDs example
curl -sS 'https://api.deturista.com/v1/products?id_products=1924,2158' \
-H 'X-API-Key: YOUR_API_KEY'
| Rule | How to use this response |
|---|---|
| Strict ids | All requested product ids must be visible to the authenticated consumer. If any one is missing or hidden, the request returns 404 not_found. |
| Limited filters | Do not combine id_products with listing filters or pagination. |
| Preserved order | The result order follows the requested id order, so this mode is useful for refreshing products already selected by your app. |
Detail example
curl -sS 'https://api.deturista.com/v1/products/2158' \
-H 'X-API-Key: YOUR_API_KEY'
| Rule | How to use this response |
|---|---|
| Confirm current visibility | Call product detail before showing a saved product. A product that is no longer visible returns 404 not_found. |
| Decide available actions | If can_buy and can_deposit are both false, present inquiry/contact actions instead of a reservation checkout. |
| Fetch departures next | Product detail describes the offer. Use /products/{id}/departures to show concrete dates and availability. |
Departure example
curl -sS 'https://api.deturista.com/v1/products/2158/departures?limit=100&page=1' \
-H 'X-API-Key: YOUR_API_KEY'
| Rule | How to use this response |
|---|---|
| Show date choices | Present start_date, end_date, nights, itinerary_days, inventory fields, last_seats, route, luggage, and lowest price so the end user can choose a departure. Format dates on the client side. |
| Compare activities before choosing | data.product_description is the package's base itinerary. When a row has a non-null description_override, use that value instead for that departure; it replaces the base description completely. A null override means the row inherits the base description. Never concatenate the two. |
| Use filters for UI | data.filters describes all matching departures, even when data.results is paginated. |
| Do not select price here | The departure list gives the departure id and display summary. Call departure detail to get the valid id_price values. |
Departure detail example
curl -sS 'https://api.deturista.com/v1/products/2158/departures/9876' \
-H 'X-API-Key: YOUR_API_KEY'
| Rule | How to use this response |
|---|---|
| Save the selected price | Use data.prices[].id as id_price for contact or reservation creation. |
| Match room types | Each selected room type must exist in the selected price's amounts[].room_type. Do not send a room type that is not priced. |
| Present travel details | Show price, hotels, circuits, transfers, assistance, flight legs, luggage, flexible options, and deposit values before checkout. |
Price preview example
curl -sS 'https://api.deturista.com/v1/products/2158/departures/9876/price-preview' \
-X POST \
-H 'Content-Type: application/json' \
-H 'X-API-Key: YOUR_API_KEY' \
-d '{"id_price":4321,"rooms":{"double":1},"coupon_code":"HOTSALE10"}'
| Rule | How to use this response |
|---|---|
| Preview before submit | Use this response to show the selected total and the discounted new_total_ars / new_total_usd before creating a contact or reservation. |
| Suggested is not applied | default_code can prefill a coupon field, but the discount is only applied when that code is submitted as coupon_code. |
| Reuse the applied code | When applied=true, send the same code as coupon_code in the contact or reservation request. The create endpoint recalculates the coupon again server-side. |
| Use an exact code | The API accepts coupon codes, not campaign names, and does not provide a coupon listing or name search. Use a code supplied by the customer or the applicable default_code suggestion. |
Destination contact example
curl -sS 'https://api.deturista.com/v1/contacts' \
-X POST \
-H 'Content-Type: application/json' \
-H 'X-API-Key: YOUR_API_KEY' \
-d '{"firstname":"Carla","lastname":"Gomez","email":"carla@example.com","phone":"1155667788","id_destination":412,"channel":"whatsapp","id_conversation":"wa-9f21c","conversation_url":"https://crm.example.com/c/wa-9f21c","start_date":"2027-01-10","pax_count":2,"message":"Quiero ir en enero"}'
| Name | Type | Description |
|---|---|---|
firstname |
string | Required. Lead first name. |
lastname |
string | Optional lead last name. |
email |
string | Required. Get it from the customer before creating the contact. |
phone |
string | Required. Lead phone number. |
id_destination |
integer | Required. Destination id from GET /destinations. It resolves the sales area of the contact, which reporting depends on. |
channel |
whatsapp, call, instagram, facebook, email, or web |
Required. Channel the conversation came in through. |
id_conversation |
string | Required. Your conversation id, up to 80 characters. Idempotency key: see above. |
conversation_url |
string | Optional deep link to the conversation, so a seller can open it from the contact. |
start_date / end_date |
string | Optional tentative trip dates, YYYY-MM-DD. |
pax_count |
integer | Optional passenger count, at least 1. |
message |
string | Optional free text from the customer. |
utm_source, utm_medium, utm_campaign, utm_content, utm_term |
string | Optional campaign attribution. Send them when the lead came from an ad: advertising performance reporting is built on these. |
assign |
ai_agent or rotation |
Optional. Defaults to ai_agent when your consumer has an AI agent, otherwise to rotation. See above for what each one does. Requesting ai_agent without an AI agent configured is a 400. |
{
"success": 1,
"data": {
"id_contact": 554401,
"id_seller": 900,
"creation_date": "2027-01-02 11:04:37",
"status": "PENDING",
"channel": "PUBLIC_API",
"id_product": null,
"id_departure": null,
"id_price": null,
"id_conversation": "wa-9f21c",
"conversation_url": "https://crm.example.com/c/wa-9f21c",
"conversation_owner": "AI",
"ai_score": 0,
"seller_name": "Agente IA",
"customer": {
"firstname": "Carla",
"lastname": "Gomez",
"email": "carla@example.com",
"phone": "1155667788"
},
"trip": {
"id_destination": 412,
"destination": "Punta Cana",
"start_date": "2027-01-10",
"end_date": null,
"pax_count": 2,
"message": "Quiero ir en enero"
}
}
}
id_seller and seller_name are your AI agent user until a seller is assigned. conversation_owner tells you who is handling the conversation right now: it starts at AI. ai_score is the conversation qualification from 0 to 100 and starts at 0. Every contact endpoint — both creations, the lookup and the update — returns this same receipt.
Departure contact example
curl -sS 'https://api.deturista.com/v1/products/2158/departures/9876/contacts' \
-X POST \
-H 'Content-Type: application/json' \
-H 'X-API-Key: YOUR_API_KEY' \
-d '{"firstname":"Ana","lastname":"Gomez","email":"ana@example.com","phone":"1155550000","origin":"contact","id_price":4321,"rooms":{"double":1},"coupon_code":"HOTSALE10"}'
| Rule | How to use this response |
|---|---|
| Use for leads | Create a contact when the user asks for information, WhatsApp, or a call instead of completing a reservation. |
| Respect action availability | Without an AI agent, origin=whatsapp and origin=call are only accepted while advisors are currently available and the area meets minimum rotation coverage. If not, the API returns 400 invalid_request; use origin=contact for the “quiero que me contacten” fallback. With an AI agent, both checks are skipped. |
| Use valid selection ids | product_id, departure_id, and id_price must come from the previous product and departure calls. |
| Rooms imply pax | "rooms":{"double":1} means one double room, which is two passengers. Send rooms; each selected room type must exist in the selected price's amounts[].room_type. |
| Coupons are recalculated | If a coupon was applied in price preview, send the applied code as coupon_code. Contact creation validates and recalculates the coupon again before saving. |
Departure reservation example
curl -sS 'https://api.deturista.com/v1/products/2158/departures/9876/reservations' \
-X POST \
-H 'Content-Type: application/json' \
-H 'X-API-Key: YOUR_API_KEY' \
-d '{"id_price":4321,"payment_option":"buy","payment_selection":"TRANSFER","rooms":{"double":1},"customer":{"firstname":"Ana","lastname":"Gomez","email":"ana@example.com","phone":"1155550000","cuil":"20987333015"},"passengers":[{"firstname":"Ana","lastname":"Gomez","document":"30999111","nationality":"Argentina","birth_date":"1990-01-01"},{"firstname":"Luis","lastname":"Gomez","document":"30999112","nationality":"Argentina","birth_date":"1988-05-10"}]}'
| Rule | How to use this response |
|---|---|
| Check product flags | payment_option=buy requires can_buy=true. payment_option=deposit requires effective can_deposit=true and a deposit value for the selected departure price. |
| Redirect MercadoPago payments | For MercadoPago, send payment_selection=MERCADOPAGO. Deturista uses the MercadoPago account configured for the selected product payment option. The reservation is created as PENDING_PAYMENT; redirect the customer to data.payment.url. |
| Match passengers to rooms | Send one passenger object per room occupant. One double room requires exactly two passengers; two double rooms require four. |
| Do not send ownership or totals | The API key owns the reservation. Deposit totals and sale values are computed server-side from the selected product, departure, price, and rooms. |
Contact receipt example
curl -sS 'https://api.deturista.com/v1/contacts/12345' \
-H 'X-API-Key: YOUR_API_KEY'
| Rule | How to use this response |
|---|---|
| Use API-owned ids | Only contacts created by the authenticated API consumer can be read back by id. |
| Status can change | The receipt returns the current workflow status, not only the status from creation time. |
| Handle 404 carefully | 404 not_found can mean the id does not exist, is outside your entity/site, or belongs to a different consumer. |
Reservation receipt example
curl -sS 'https://api.deturista.com/v1/reservations/765432' \
-H 'X-API-Key: YOUR_API_KEY'
| Rule | How to use this response |
|---|---|
| Use API-owned ids | Only reservations created by the authenticated API consumer can be read back by id. |
| Read persisted fields | Read-back returns persisted reservation receipt fields. id_price and payment_option are creation-response fields only. |
| Status can change | The receipt returns the current reservation workflow status. |
Destination example
curl -sS 'https://api.deturista.com/v1/destinations?limit=100&page=1' \
-H 'X-API-Key: YOUR_API_KEY'
| Rule | How to use this response |
|---|---|
| Use for navigation | Destinations help build filters and browsing UI. Use a destination id as id_destination in product search. |
| Default product scope is tree | Filtering products by destination includes child destinations unless you send destination_scope=linked. |
| Keep names display-only | Use destination ids for API calls; names, URLs, images, parents, and children are for presentation. |
Category example
curl -sS 'https://api.deturista.com/v1/categories?limit=100&page=1' \
-H 'X-API-Key: YOUR_API_KEY'
| Rule | How to use this response |
|---|---|
| Use visible categories | The API returns website-visible categories. Hidden or disabled categories and categories under hidden ancestors are not returned. |
| Default product scope is tree | Filtering products by category includes child categories unless you send category_scope=linked. |
| Use ids for filters | Use category ids in product searches. Treat active as a menu-state field for display. |
Grouped discovery example
curl -sS 'https://api.deturista.com/v1/product-groups?by=destination&group=continent&include_top_ranked=1&top_ranked_limit=3' \
-H 'X-API-Key: YOUR_API_KEY'
| Rule | How to use this response |
|---|---|
| Use drill-down URLs | Each group includes a products_url that points to the matching product search. |
| Top ranked is a preview | top_ranked is a compact preview for display. Call products_url for the full listing. |
| Read linked counts precisely | For category linked_parent, total is the exact one-level-up count, while its products_url uses tree scope and may return deeper products. |
Home section examples
type is required: request one section per call. There is no combined home response.
curl -sS 'https://api.deturista.com/v1/home?type=slider' \
-H 'X-API-Key: YOUR_API_KEY'
curl -sS 'https://api.deturista.com/v1/home?type=list' \
-H 'X-API-Key: YOUR_API_KEY'
curl -sS 'https://api.deturista.com/v1/home?type=popup' \
-H 'X-API-Key: YOUR_API_KEY'
curl -sS 'https://api.deturista.com/v1/home?type=banners' \
-H 'X-API-Key: YOUR_API_KEY'
| Rule | How to use this response |
|---|---|
| One section per call | type is required (slider, list, popup, banners). A request without type returns 400 invalid_request. Call once per section to build the home. |
| Render blocks in order | Block responses (slider/list/popup) return blocks sorted by order. Render in that order to reproduce the home layout. |
| Product cards are compact | Product resources and LIST products are compact home cards (image, title, price, discount, nights, service flags) using the same money/flag conventions as /products (price_from_ars/price_from_usd pairs, boolean has_*). They are not the full product row: load /products/{id} for full detail, checkout flags, and relations. |
| LIST products are slider-sized | A LIST block returns at most 20 products (enough for a slider). For the full list of a category use /products?id_category=<id>. |
| Category and destination tiles are summaries | For resource_type of category or destination, resource is a compact tile (id, name, url, image). Use id to drill into /products. |
| Only one popup | The response includes at most one popup block, the first by order, matching the single popup the home page shows. Other block types can repeat. |
| Banners are a section | Request the banners strip with ?type=banners; it returns up to two banners ordered by order under banners. Block sections never include banners. |
Site metadata example
curl -sS 'https://api.deturista.com/v1/site' \
-H 'X-API-Key: YOUR_API_KEY'
| Rule | How to use this response |
|---|---|
| Build footer and contact chrome | Use phone, email, address, social, legal_info, and offices to build the site header and footer. |
| Null-check display fields | Most fields are nullable. Only render what is present rather than assuming every field is set. |
| Analytics keys are not exposed | Pixel, analytics, and verification keys are intentionally omitted; the consuming site manages its own analytics. |
Newsletter subscription example
curl -sS -X POST 'https://api.deturista.com/v1/newsletter' \
-H 'X-API-Key: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"firstname": "Ana",
"lastname": "Gomez",
"email": "ana@example.com",
"destination": "EUROPA"
}'
| Rule | How to use this response |
|---|---|
| firstname and email are required | lastname is optional and destination defaults to OTRO (one of OTRO, CARIBE, EUROPA, SUDAMERICA, MUNDO). |
| Duplicate emails are rejected | An already-subscribed email or an invalid field returns 400 invalid_request; a new subscription returns 201 with a receipt. |
Endpoints
Products
GET POST /products
Both methods perform the same listing/search operation. Use GET for simple query-string searches and POST when sending a JSON search body is easier for your client.
Product rows are the first decision point for checkout. Use can_buy to decide whether the full-payment reservation path can be shown, and effective can_deposit to decide whether a deposit reservation path can be shown. If neither action is available, keep the product as an inquiry/contact flow.
Use GET /products/{id} to retrieve one public product visible to the authenticated API consumer.
Use GET /products/{id}/departures to retrieve active future departures and filters for one public product.
Departure rows are meant for the end user's date-selection screen. Present raw dates, duration, remaining seats, lowest displayed price, origin city, hotel stars, luggage, route, and flight inventory hints when present.
Use GET /products/{product_id}/departures/{departure_id} to retrieve the selected departure detail payload, including prepared price rows, included services, and flight details when available.
Use POST /products/{product_id}/departures/{departure_id}/price-preview to preview the selected total and optionally apply a public coupon code before creating a contact or reservation.
Use POST /products/{product_id}/departures/{departure_id}/contacts to create a lead for a selected price and room selection. Leads are persisted with channel set to PUBLIC_API. Assignment follows the same assign rule as /contacts: with an AI agent configured the lead is owned by the agent and not rotated unless you send assign: "rotation"; without one it rotates as before.
Use POST /products/{product_id}/departures/{departure_id}/reservations to create an operational reservation for a selected price, room selection, customer, and passenger list. Reservations are persisted with channel and origin set to PUBLIC_API.
Users
GET /users
The roster to build your own users from, so there is a single list of people and it never drifts: when a seller is added on our side they appear in your app without anyone doing anything. Match on email, the corporate address they log in with, and keep id_user — that is what you send back on any call made on behalf of that person.
Who is on the list: sellers and supervisors always, by their role; administrators only when they have been marked as users of the app, because not all of them are. Every other role we have — marketing, product, operations, extranet — is never exposed, and neither is the AI agent, which is not a person.
The three profiles mean different things in your app. A SELLER handles their own conversations. A SUPERVISOR also oversees the conversations of customers of their own office — that is what office is for, and you need it to scope what they are allowed to see. An ADMIN works across the board.
Deactivated people stay on the list with active: false, and must lose access. They are not removed on purpose: that way a deactivation is unambiguous, instead of a person quietly disappearing from the list. Re-read the roster often enough that a deactivation reaches you the same day.
{
"success": 1,
"data": {
"total": 2,
"results": [
{
"id_user": 118,
"firstname": "Carla",
"lastname": "Gomez",
"email": "carla.gomez@deturista.com",
"profile": "SELLER",
"id_group": 5,
"area": "Multidestino",
"id_office": 1,
"office": "Casa Central",
"active": true
},
{
"id_user": 92,
"firstname": "Diego",
"lastname": "Ferrari",
"email": "diego.ferrari@deturista.com",
"profile": "SUPERVISOR",
"id_group": 6,
"area": "Caribe + Sudamérica",
"id_office": 1,
"office": "Casa Central",
"active": true
}
]
}
}
area is for display; some are combined areas such as Multidestino or Caribe + Europa. id_group is there if you ever need a stable key rather than a label.
GET /sellers/availability
Per sales area, how many sellers can take a contact right now — they rotate in that area, they are active, not paused, and it is a working day and hour for them — and how many of those still have room in their daily quota. This is what a supervisor needs before assigning by hand: see the quota before choosing, instead of after the assignment bounces. It comes from the same source as our own dashboard, so the two can never disagree.
with_quota: false does not mean the seller cannot be assigned automatically. The daily quota spreads the load; it does not block while the area has enough coverage. automatic_assignment is the area-level switch: when it is false, ordinary contacts enter without a seller until the rotating roster reaches the configured minimum.
day tells you which day the numbers are for. During the evening window the rotation already looks at the next working day, so the counts are about that day and not about this instant; null means today. Seller names are not repeated here — resolve them from /users.
{
"success": 1,
"data": {
"day": "monday",
"areas": [
{
"id_group": 5,
"area": "Multidestino",
"available": 3,
"with_quota": 2,
"automatic_assignment": false,
"sellers": [
{ "id_user": 118, "with_quota": true },
{ "id_user": 92, "with_quota": true },
{ "id_user": 140, "with_quota": false }
]
}
]
}
}
Hotels
GET /hotels/{id}
Everything we have loaded about a hotel or a circuit: description, address, its own website, chain, destination and the full photo gallery. Use it to talk about the hotel without pulling a whole departure — the ids come from the hotels and circuits lists in the departure detail. Deleted hotels, and hotels still pending approval, return 404 not_found.
The same fields are now included inline in the departure detail, so a single call already gives you what you need to describe every hotel of a package. Galleries everywhere in the API — hotels, circuits and products — are a list of photos, each one with its absolute url and the caption written when it was loaded, and the single image field is an absolute URL too. Use them as they come.
{
"success": 1,
"data": {
"id": 812,
"name": "Hotel Playa Azul",
"stars": 5,
"description": "Frente al mar, con pileta y kids club.",
"address": "Av. del Mar 1200",
"url": "https://hotelplayaazul.example.com",
"tag": "PLAYA",
"is_circuit": false,
"destination": "Punta Cana",
"chain": "Caribe",
"images": [
{
"url": "https://www.deturista.com/entity/deturista/images/Hotel/812/original/playa.jpg",
"description": "Vista a la playa"
},
{
"url": "https://www.deturista.com/entity/deturista/images/Hotel/812/original/lobby.jpg",
"description": null
}
]
}
}
Contacts
POST /contacts
Creates a lead that is not tied to a product or a departure — the destination enquiry a conversational agent creates once it has the customer's details. Use it when there is no package selected yet; when there is one, use /products/{product_id}/departures/{departure_id}/contacts instead.
How the contact is assigned follows the optional assign field, and the rule is the same for every contact created through the API, with or without a package. By default, a consumer with an AI agent user configured gets the contact owned by that agent and with no seller assigned: seller rotation does not run and no seller's daily quota is consumed. Assigning a seller is a separate, later call. A consumer without an AI agent gets the contact through seller rotation, as the website does.
Send assign: "rotation" to hand the lead to a person right away — the customer asked for a human from the first message. The contact then goes through seller rotation at creation and its conversation_owner is SELLER; route the conversation to the returned id_seller. If rotation finds nobody available (night time, everyone at their daily limit) the contact falls back to the AI agent instead of failing, so no lead is lost: check id_seller and conversation_owner in the response rather than assuming.
id_conversation is the idempotency key. Sending the same one twice does not create a second contact: the first call answers 201 and every retry answers 200 with the contact that already exists. Retry safely.
GET /contacts/{id}
Returns the contact as the conversation needs it: assigned seller (id_seller and seller_name, the name the admin shows), workflow status, the conversation fields, a customer object and a trip object. It is the same receipt every contact endpoint returns. Contacts owned by another consumer, outside the authenticated entity or site, or created outside the public API return 404 not_found.
POST /contacts/{id}/assign
Decides who handles a contact, in four modes.
mode |
What it does | Needs |
|---|---|---|
rotation |
The hand-off. You do not choose the seller: Deturista picks one with its own rules — rotation by sales area, daily quota, working days and hours, pauses. This is the default. | Nothing. Nobody is choosing, so no person is acting. |
manual |
A supervisor picks a seller from their own office for a contact that does not have one. An administrator can pick across offices. | id_actor and id_user |
self |
A seller takes an unassigned contact for themselves. This one does consume their daily quota, which is what stops anyone from filling up on their own. | id_actor |
reassign |
A supervisor moves a contact between sellers in their own office. Both the current and destination seller must be in scope. An administrator can move it across offices. | id_actor and id_user |
id_actor is the id_user of the person making the change, from /users. We validate what that person is allowed to do, so your app does not have to: a seller can only take a contact for themselves and against their own quota; a supervisor can assign and reassign only within their office; and an administrator can operate across offices. For reassign, the current seller and destination seller must both be in the supervisor's scope. Ask, and we answer with the result or the reason.
Send an optional reason so hand-offs can be measured later.
If nobody is available — night time, outside working hours, everyone paused — the call still succeeds with assigned: false, the contact keeps the AI agent and conversation_owner stays AI. The conversation is yours to continue: no lead is lost. Check assigned rather than assuming.
A contact that already has a seller returns 400. Moving a contact from one seller to another is a supervisor decision and does not go through here.
curl -sS 'https://api.deturista.com/v1/contacts/554401/assign' \
-X POST \
-H 'Content-Type: application/json' \
-H 'X-API-Key: YOUR_API_KEY' \
-d '{"reason":"asked_human"}'
| Name | Type | Description |
|---|---|---|
reason |
no_quote, cooldown, or asked_human |
Optional. Why the conversation is being handed over: nothing in the catalogue matches, the customer stopped answering, or they asked for a person. It is stored on the contact and comes back as handoff_reason. |
mode |
rotation, manual, self, or reassign |
Optional; defaults to rotation. Every mode except rotation requires id_actor. Manual and reassign also require id_user. |
POST /contacts/{id}/handover
Changes who is talking to the customer right now, without changing who owns the contact. Once a contact belongs to a seller it stays with them: the live conversation can go back and forth as often as needed — the seller hands it to your agent to collect a balance or send documents, your agent hands it back when a person is needed — and the assigned seller does not change.
Moving a contact between sellers is a different thing: that is mode: "reassign" on the assign endpoint, and only a supervisor or administrator can do it. A hand-over does not go through rotation and does not consume any quota — the seller already paid that cost when the contact reached them.
Send to: "ai" with the id_actor of the person letting go. That actor must be the assigned seller, a supervisor from the seller's office, or an administrator. Send to: "seller" to hand it back, which your agent can do on its own without an actor.
GET /contacts
Find contacts you created. This is the answer to the question your agent asks whenever someone writes again: does this person already have an open contact? The idempotency of contact creation does not cover it — that recovers one conversation by its id, not a person by phone or email.
Search by phone, email, id_conversation or status; at least one is required. Paged with limit (up to 50) and page. Contacts that were not created through the API are not visible here.
PATCH /contacts/{id}
Updates what the conversation is learning about the lead. Send only the fields to change; at least one is required. Customer details, trip details, the package once it is chosen, the qualification (ai_score) and the workflow status can all be updated. The response is the full contact receipt after the change.
A status change follows the same path as the admin: it is written to the contact history with your AI agent user as the author, and it feeds the sales statistics like any seller's change. It does not send reminder emails to the customer nor create seller alerts — the conversation is yours. Two rules: RESERVED cannot be set here, it is set by the reservation; and SPAM unassigns the contact.
id_product can be set once. A contact that already has a product answers 400: advertising attribution reads that field as the package the lead came from, so it is never overwritten from here. Correcting it is done in the admin.
curl -sS 'https://api.deturista.com/v1/contacts/554401' \
-X PATCH \
-H 'Content-Type: application/json' \
-H 'X-API-Key: YOUR_API_KEY' \
-d '{"status":"QUOTE","ai_score":60,"pax_count":3,"start_date":"2027-01-10","id_product":2158}'
| Name | Type | Description |
|---|---|---|
firstname, lastname, email, phone |
string | Customer details. |
id_destination |
integer | Destination id from GET /destinations. Changing it also moves the contact to that destination's sales area. |
start_date / end_date |
string | Tentative trip dates, YYYY-MM-DD. |
pax_count |
integer | Passenger count, at least 1. |
message |
string | Free text from the customer. |
id_product |
integer | The package the conversation settled on. Once only. |
id_departure |
integer | A selectable departure of the contact's product. Requires id_product, sent now or already set. |
status |
string | Any workflow status except RESERVED: PENDING, RECONTACT, INITIAL_CALL, QUOTE, FOLLOW, FOLLOW_2, FOLLOW_3, ANSWER, UNANSWERED, SPAM. What each one means is in the workflow statuses below. |
ai_score |
integer | Conversation qualification, 0 to 100. |
conversation_url |
string | Deep link to the conversation. |
The contact workflow statuses
The status is the sales funnel a seller works in the admin, so it answers "how far did this conversation get". The middle column is the label the seller reads on screen, in Spanish, exactly as it appears there. A status your agent sets looks the same to the seller as one a colleague set.
| Status | Admin label | What it means |
|---|---|---|
PENDING |
Sin Atender | Every contact is created with this one, yours included: nobody has worked it yet. It is also the status that takes a contact back out of SPAM. |
RECONTACT |
Recontactar | The customer went quiet and will be chased again. This is how you park a conversation without closing it. |
INITIAL_CALL |
Llamado Inic. | First real contact with the customer happened. |
QUOTE |
Cotización | A quote or a price reached the customer. |
FOLLOW |
Seguimiento | The conversation was picked up again after the quote. |
FOLLOW_2 |
Seguimiento 2 | Second follow-up. |
FOLLOW_3 |
Seguimiento 3 | Third and last follow-up. |
ANSWER |
Respondido | Closed without a sale. Archives the contact. |
RESERVED |
Reservado | The reservation was confirmed and paid. Archives the contact. DeTurista sets this one, never you: the PATCH answers 400. You see it on the receipt. |
UNANSWERED |
No atendida | The customer never answered at all. Archives the contact. DeTurista already sets it on its own after 60 days in PENDING (the window is configurable), so you rarely need to send it. |
SPAM |
SPAM | Junk. Unassigns the seller and blocks that email address. |
| Rule | What it means for your agent |
|---|---|
| The order is not validated | You can go straight from PENDING to FOLLOW_3, or back, without passing through the ones in between. Send the status that matches the conversation instead of walking the funnel step by step. |
| Sending the status it already has does nothing | No history row and no change to the statistics, and the response is still the contact receipt. Retrying is safe. |
| Three statuses archive the contact | ANSWER, UNANSWERED and RESERVED mark the contact as handled and move it to the admin's archive. Sending PENDING afterwards changes the status but does not bring it back out of the archive, so do not use them to park a conversation — that is what RECONTACT is for. |
| Filtering is not restricted | GET /contacts?status= accepts RESERVED too: the restriction is on setting it, not on searching for it. |
Reservations
GET /reservations/{id}
Where the reservation stands money-wise, for the moment the customer says "I already paid" or before you re-send a payment link:
payment_status |
What it means |
|---|---|
unpaid |
Nothing paid yet. |
deposit_paid |
Some money in, a balance still outstanding — typically the deposit. Ask for owed, not for the total. |
paid |
Nothing owed. payed_date tells you when. |
cancelled |
Cancelled, or the customer is not travelling. Stop selling, whatever the balance says. |
Alongside it come total, paid and owed, as the usual ARS/USD pairs. This is a snapshot: it tells you how things stand right now, which is what you need mid-conversation. Knowing when a payment lands is what the notification is for.
Reservation receipt lookup returns persisted receipt fields for reservations created by the authenticated API consumer. Reservations owned by another consumer, outside the authenticated entity or site, or created outside the public API return 404 not_found. The read-back receipt does not include id_price or payment_option; those fields are returned only by the creation response.
Destinations
GET /destinations
Destinations return the active destination tree visible to the authenticated API consumer. Use GET /destinations/{id} to retrieve one destination.
Categories
GET /categories
Categories return the website-visible category tree for the authenticated API consumer. Use GET /categories/{id} to retrieve one category.
Product groups
GET /product-groups
Product groups return destination or category group totals with drill-down URLs for /products. Set include_top_ranked=1 to include compact previews from real public products.
Home
GET /home
Home returns one section of the home for the authenticated consumer's site. type is required and selects the section: slider, list and popup return the ordered blocks of that type; banners returns the banners strip (up to two). There is no combined response — call once per section. Each block references a product, category, or destination; product resources and LIST products are compact home cards (not the full product row — load /products/{id} for full detail). At most one popup block is returned (the first by order), matching the single popup shown on the home page.
Site
GET /site
Site returns public site and footer metadata for the authenticated consumer's site: contact fields, social links, legal text, and the public office list. Analytics, pixel, and verification keys are not exposed.
Newsletter
POST /newsletter
Newsletter creates an email subscription for the authenticated consumer's site. It reuses the public newsletter validation and duplicate-email guard, so an invalid field or an already-subscribed email returns 400 invalid_request and a new subscription returns 201 with a receipt.
Parameters
Product listing parameters
GET accepts these as query parameters. POST accepts the same keys in a JSON object.
| Name | Type | Description |
|---|---|---|
limit |
integer | Maximum number of products to return. Defaults to 100 and is capped at 100. |
page |
integer | 1-based page number. page=1 is the first page. |
id_products |
array | Exact product ids. Use a comma-separated value in GET or an array in POST. The request fails if any id is not visible to the API consumer. Do not combine with listing filters or pagination. |
id_destination |
integer | Destination id. Child destinations are included by default. |
destination_scope |
tree or linked |
Use tree to include child destinations, or linked to match only direct product-destination links. |
id_category |
integer | Category id. Child categories are included by default. |
category_scope |
tree or linked |
Use tree to include child categories, or linked to match only direct product-category links. |
month |
integer | Departure month from 1 to 12. |
year |
integer | Departure year. Use it together with month to select a specific month in a specific year. |
Product currency is the package base currency used by its prices, not a display conversion hint.
Product departure parameters
| Name | Type | Description |
|---|---|---|
limit |
integer | Maximum number of departures to return. Defaults to 100 and is capped at 100. |
page |
integer | 1-based page number. page=1 is the first page. |
Product departure filters describe the full matching departure set for the product. results is paginated.
Product departure detail uses product_id and departure_id from the path. data.currency uses the product package base currency. The selected departure must belong to the product and must be active, future-visible, web-visible, and priced like rows returned by the departure listing endpoint.
Product departure price preview uses product_id and departure_id from the path and requires this JSON body:
| Name | Type | Description |
|---|---|---|
id_price |
integer | Selected price id from the departure detail prices list. |
rooms |
object | Room selection as a map of room type to room count, for example {"double": 1}. Valid room types are the same as contact and reservation creation. |
coupon_code |
string | Optional alphanumeric coupon code to apply to the selected product. An unavailable or inapplicable code returns 400 invalid_request with coupon code is not available for this product. Internal eligibility reasons are not exposed. |
The preview response always includes total_ars / total_usd, new_total_ars / new_total_usd, pax_count, applied, and default_code. The suggested default_code is only returned when no coupon was applied in the request; when a coupon is applied it is null and the response also includes code, original_total_*, amount_discount_*, discount_type, and configured_value.
Product departure contacts use product_id and departure_id from the path and require this JSON body:
Ownership is always taken from the authenticated API key. A request body field named consumer is ignored.
| Name | Type | Description |
|---|---|---|
firstname |
string | Lead first name. |
lastname |
string | Optional lead last name. |
email |
string | Lead email address. |
phone |
string | Lead phone number. |
origin |
contact, whatsapp, or call |
Selected contact action. The lead is stored using the matching legacy promo origin, which is what advertising attribution reads. Without an AI agent, whatsapp and call require current advisor availability and minimum rotation coverage — otherwise send contact. With an AI agent configured neither restriction applies: the lead falls back to the agent. |
assign |
ai_agent or rotation |
Optional. Same rule as /contacts: defaults to ai_agent when your consumer has an AI agent, otherwise to rotation. |
id_conversation |
string | Optional conversation id from your CRM, up to 80 characters. When sent it is the idempotency key: a retry returns the contact created the first time with 200. |
conversation_url |
string | Optional deep link to the conversation, so a seller can open it from the contact. |
id_price |
integer | Selected price id from the departure detail prices list. |
rooms |
object | Room selection as a map of room type to room count, for example {"double": 1}. Valid room types: single, double, double_shared, triple, quadruple, sextuple, family_1, family_2. At least one count must be greater than zero, and each selected room type must appear in the selected price's amounts[].room_type. |
coupon_code |
string | Optional alphanumeric coupon code to apply to the selected product. An unavailable or inapplicable code returns 400 invalid_request with coupon code is not available for this product. |
Room selection pax counts
The rooms object always counts rooms. The API expands those rooms into passenger slots for reservations.
| Room key | Passengers per room | Example |
|---|---|---|
single |
1 | {"single": 1} requires 1 passenger. |
double |
2 | {"double": 1} requires 2 passengers. |
double_shared |
1 | {"double_shared": 1} requires 1 passenger. |
triple |
3 | {"triple": 1} requires 3 passengers. |
quadruple |
4 | {"quadruple": 1} requires 4 passengers. |
sextuple |
6 | {"sextuple": 1} requires 6 passengers. |
family_1 |
3 | {"family_1": 1} requires 2 passengers who have reached kids_age and 1 passenger under kids_age at trip end. |
family_2 |
4 | {"family_2": 1} requires 2 passengers who have reached kids_age and 2 passengers under kids_age at trip end. |
kids_age is an exclusive child-age limit evaluated at the end of the trip: when it is 12, a child slot requires a passenger who is still under 12 on that date. Infants under 2 also use a child room slot. The price preview is the fixed total for the selected room occupancy and is not recalculated from passenger birth dates. Infant airline handling or charges are finalized by the sales team and are not added by price-preview.
Product departure reservations use the same path ids and require a structured reservation body:
Ownership is always taken from the authenticated API key. A request body field named consumer is ignored. Passenger count must match the pax count implied by rooms.
| Name | Type | Description |
|---|---|---|
id_price |
integer | Selected price id from the departure detail prices list. |
payment_option |
buy or deposit |
Selected payment option. The product must support the requested option. |
payment_selection |
TRANSFER or MERCADOPAGO |
Optional; defaults to TRANSFER. Send MERCADOPAGO to create the reservation pending payment and receive a Checkout Pro redirect URL. |
rooms |
object | Room selection as a map of room type to room count, for example {"double": 1}. Valid room types match the contact endpoint, and every selected room type must be priced by the selected id_price. |
customer |
object | Required customer fields: firstname, lastname, email, phone, and cuil. The CUIL is used to resolve or create the linked client. |
passengers |
array | One object per room occupant. Each passenger requires firstname, lastname, document, nationality, and birth_date in YYYY-MM-DD format. Passengers are assigned in the room-key order shown above; within each family room send the two adult-age passengers first, followed by its one or two child-age passengers. Invalid family composition returns 400 invalid_request. |
coupon_code |
string | Optional alphanumeric coupon code to apply to the selected product. An unavailable or inapplicable code returns 400 invalid_request with coupon code is not available for this product. |
viajeflex_id |
integer | Optional ViajeFlex service id for flexible confirmed-flight departures. The id must be available for the selected price and room selection. |
id_contact |
integer | Optional. The contact this reservation closes (from POST /contacts or GET /contacts/{id}). It must belong to your consumer, otherwise 404 not_found. Linking it moves the contact to RESERVED and lets us report the sale and its payments to your CRM. |
Destination listing parameters
| Name | Type | Description |
|---|---|---|
name |
string | Optional partial-name filter. Matching is case-insensitive and accent-insensitive, so ?name=brasil can be used directly from free-text search. Filtering is applied before total and pagination. |
limit |
integer | Maximum number of destinations to return. Defaults to 100 and is capped at 100. |
page |
integer | 1-based page number. page=1 is the first page. |
The destination detail endpoint uses the destination id from the path and has no query parameters.
Category listing parameters
| Name | Type | Description |
|---|---|---|
limit |
integer | Maximum number of categories to return. Defaults to 100 and is capped at 100. |
page |
integer | 1-based page number. page=1 is the first page. |
The category detail endpoint uses the category id from the path and has no query parameters.
Product group parameters
| Name | Type | Description |
|---|---|---|
by |
destination or category |
Required grouping dimension. |
group |
string | For destinations use continent, country, or linked. For categories use root, linked_parent, or linked. Category linked_parent means exactly one level above the exact linked category; if the linked category is four levels deep, the product groups under the category at level three. For linked_parent the group total is exact, but the products_url drill-down uses tree scope on that parent and may include more deeply nested products than the count. |
include_top_ranked |
0 or 1 |
Optional. Set to 1 to include compact top-ranked product previews for each group. |
top_ranked_limit |
integer | Optional. Maximum previews per group when top-ranked products are included. Defaults to 3; values above 3 are rejected. |
Responses
Successful listing responses include success: 1 and a data object with total and results. Detail responses include the resource object directly under data.
{
"success": 1,
"data": {
"total": 69,
"results": [
{
"id": 2158,
"title": "Mundial de Futbol 2026 - 16avos de Final",
"subtitle": "03 noches de alojamiento...",
"summary": "03 noches de alojamiento...",
"url": "2158/mundial_de_futbol_2026.html",
"image": "img_web-03.jpg",
"discount": 20,
"transport": "Aereo",
"meal_plan": "Desayuno",
"currency": "ARS",
"price_from_ars": 5835,
"price_from_usd": 990,
"deposit_from_ars": 1200,
"deposit_from_usd": 190,
"num_of_payments": 5,
"nights": [3, 4],
"has_hotel": false,
"can_deposit": true,
"can_buy": false,
"destinations": [
{ "id": 410, "name": "Miami" }
],
"categories": [
{ "id": 154, "name": "Mundial" }
]
}
]
}
}
Product detail responses return the product object directly under data and replace the listing image field with the ordered images gallery plus the base package description and public payment summaries. A departure can carry a different description. After a departure is selected, treat the departure detail description as authoritative: it replaces the base package description completely and the two values must not be merged.
{
"success": 1,
"data": {
"id": 2158,
"title": "Mundial de Futbol 2026 - 16avos de Final",
"subtitle": "03 noches de alojamiento...",
"summary": "03 noches de alojamiento...",
"description": "Día 1: llegada a Miami.\r\nDía 2: excursión incluida...",
"url": "2158/mundial_de_futbol_2026.html",
"discount": 20,
"transport": "Aereo",
"meal_plan": "Desayuno",
"currency": "ARS",
"price_from_ars": 5835,
"price_from_usd": 990,
"deposit_from_ars": 1200,
"deposit_from_usd": 190,
"num_of_payments": 5,
"nights": [3],
"has_hotel": false,
"can_deposit": true,
"can_buy": false,
"destinations": [
{ "id": 410, "name": "Miami" }
],
"categories": [
{ "id": 154, "name": "Mundial" }
],
"images": [
{
"name": "img_web-03.jpg"
}
],
"payment_options": {
"deposit": [
{
"name": "Tarjeta Visa",
"title": "Reserva con tarjeta",
"description": "Pago de seña en cuotas disponibles.",
"payment_selection": "TRANSFER"
},
{
"name": "MercadoPago",
"title": "Pagá con Mercado Pago",
"description": "Checkout Pro para pagar online.",
"payment_selection": "MERCADOPAGO"
}
]
}
}
}
When the authenticated API consumer is scoped to a USD display site, the same money fields use only the USD suffix.
{
"success": 1,
"data": {
"id": 3150,
"title": "Caribe 2026",
"description": "Día 1: llegada a Punta Cana...",
"url": "3150/caribe_2026.html",
"discount": null,
"transport": "Aereo",
"meal_plan": "All inclusive",
"currency": "USD",
"price_from_ars": null,
"price_from_usd": 990,
"deposit_from_ars": null,
"deposit_from_usd": null,
"num_of_payments": null,
"nights": [7],
"can_deposit": false,
"can_buy": true,
"destinations": [
{ "id": 410, "name": "Miami" }
],
"categories": [
{ "id": 154, "name": "Caribe" }
],
"images": [],
"payment_options": {
"buy": []
}
}
}
| Field | Rule for the next API call |
|---|---|
description |
This is the base package description, including its itinerary and excursion details. Once the user chooses a departure, use the departure detail description instead: a departure-specific value replaces this text completely rather than extending it. Never concatenate or merge the two descriptions. |
currency |
This is the package base currency (ARS or USD) used by the product prices. Money field suffixes identify the currency of each numeric amount. |
discount |
Informational package-level percentage, useful for badges or commercial labels. It does not replace departure or price calculations. |
transport / meal_plan |
Public labels from the package configuration. Internal ids are not exposed. |
can_buy |
When true, the product can support a reservation with payment_option=buy. Still fetch departure detail and choose a valid id_price before creating the reservation. |
can_deposit |
When true, the product can support a reservation with payment_option=deposit. This is effective availability, so it is false when installments do not apply even if deposit is configured internally. The selected departure price must also return deposit values. |
nights |
Display these numeric durations as nights available for the product. |
images |
Only present on product detail. Items are ordered by the admin gallery order, and each one carries its absolute url and its caption. |
payment_options |
Only present on product detail. Includes buy and/or deposit arrays when those actions are available. Send the chosen option's payment_selection; MercadoPago account routing is resolved internally by Deturista. |
Product departure responses include active future departures for one product. The root product_description carries the base package itinerary once. Each row's description_override is either a complete replacement for that departure or null when the row inherits the base description, so clients can compare activities without loading every departure detail. The root currency is the package base currency for the product. The filters object is calculated from all matching departures, even when results is paginated. filters.months lists departure start months; each row omits month summaries because start_date and end_date carry the date range. Inventory fields distinguish confirmed flight seats from regular flight departures and no-flight departures.
{
"success": 1,
"data": {
"product_id": 2158,
"product_description": "Day 1: city tour. Day 2: free day.",
"currency": "USD",
"total": 24,
"filters": {
"price": { "min": 5835, "max": 8720 },
"stars": [5, 4],
"months": ["2026-06", "2026-07"],
"itinerary_days": [5, 8],
"luggage": ["Carry on"],
"route": ["DIRECT"]
},
"results": [
{
"id": 9876,
"start_date": "2026-06-12",
"end_date": "2026-06-16",
"nights": 4,
"itinerary_days": 5,
"assistance_days": 5,
"last_seats": 6,
"available_prices_count": 1,
"inventory_type": "allotment",
"available_seats": 6,
"price_from_ars": null,
"price_from_usd": 5835,
"deposit_from_ars": null,
"deposit_from_usd": null,
"num_of_payments": null,
"city_from": "Buenos Aires",
"is_hook": false,
"hotel_stars": [5, 4],
"luggage": ["Carry on"],
"route": "DIRECT",
"flexible": false,
"description_override": "Day 1: city tour. Day 2: full-day excursion."
}
]
}
}
| Field | Why it matters to the end user |
|---|---|
start_date, end_date, nights, itinerary_days |
These are raw travel dates and the passenger-facing trip duration. Format dates on the client side. |
product_description, description_override |
Show the row's override when it is not null; otherwise show the root product description. The override replaces the base itinerary completely rather than adding to it. The base text is returned once to keep packages with many departures compact. |
assistance_days |
This is the coverage duration used for assistance and ViajeFlex calculations, which can differ from passenger-facing itinerary days. |
last_seats |
Use it to show remaining availability pressure when present. It is only populated for confirmed flights with few seats left. |
available_prices_count, inventory_type, available_seats |
available_prices_count counts enabled price rows for the departure. inventory_type is allotment, regular, or no_flight. available_seats is the real remaining seat count only for confirmed flight inventory, otherwise null. |
price_from_ars, price_from_usd, deposit_from_ars, deposit_from_usd |
These rounded integer money fields are always present; currencies that do not apply are null. The deposit_from_* fields are previews derived from the departure's lowest available price. Use departure detail to select the actual id_price. |
city_from, route, luggage, hotel_stars |
These fields help users compare departure quality before choosing one. hotel_stars is an aggregate of all available price options; precise hotel stars remain in detail at prices[].hotels[].stars. |
Product departure detail responses include the selected departure price payload. The root currency is the package base currency for the product. description is authoritative for the selected departure: a departure-specific description replaces the base package description completely; only when the departure has no description does this field fall back to the product description. Never merge both descriptions. Deposit values are exposed on each selectable prices[].amounts[] row, not at the detail root. Use flight for flight details; it is null when the departure has no flight.
{
"success": 1,
"data": {
"product_id": 2158,
"currency": "USD",
"description": "<p>Departure details...</p>",
"prices": [
{
"id": 4321,
"amounts": [
{
"price_ars": null,
"name": "amount_double",
"price_usd": 990,
"deposit_ars": null,
"deposit_usd": 190,
"num_of_payments": 5,
"max_pax": 12,
"room_type": "DOUBLE",
"has_kids": false
}
],
"hotels": [
{
"id": 10,
"name": "Hotel Plaza",
"stars": 4,
"destination": "Madrid",
"image": "hotel.jpg",
"nights": 3,
"check_in_date": "2026-06-13",
"check_out_date": "2026-06-16",
"meal_plan": {
"code": "BREAKFAST",
"label": "Con Desayuno"
}
}
],
"circuits": [
{
"id": 20,
"name": "Circuito Andalucia",
"nights": 4,
"start_date": "2026-06-16",
"end_date": "2026-06-20",
"meal_plan": null
}
],
"transfers": [
{
"id": 30,
"name": "Traslado aeropuerto-hotel"
}
],
"count": 1,
"price_from_ars": null,
"price_from_usd": 990,
"price_from_type": "DOUBLE"
}
],
"has_amounts": true,
"price_from_type": "DOUBLE",
"discount": null,
"kids_age": 12,
"start_date": "2026-06-12",
"end_date": "2026-06-20",
"nights": 7,
"itinerary_days": 8,
"assistance_days": 9,
"flight": {
"inventory_type": "ALLOTMENT",
"available_seats": 6,
"last_seats": 6,
"route": "DIRECT",
"luggage": ["CARRY_ON", "CHECKED"],
"departure_date": "2026-06-12",
"arrival_date": "2026-06-20",
"flight_legs": [
{
"leg_type": "OUTBOUND",
"airport_from": "EZE",
"city_from": "Buenos Aires",
"airport_to": "MIA",
"city_to": "Miami",
"airline_name": "Aerolinea",
"flight_number": "AR1302",
"departure_date": "2026-06-12 22:00:00",
"arrival_date": "2026-06-13 06:00:00"
}
]
},
"id_departure": 9876
}
}
| Field | Rule for contact or reservation creation |
|---|---|
prices[].id |
Send one of these values as id_price. Do not reuse a price id from another departure. |
amounts[].room_type, prices[].price_from_type, price_from_type |
Room types use SINGLE, DOUBLE, DOUBLE_SHARED, TRIPLE, QUADRUPLE, SEXTUPLE, FAMILY_1, or FAMILY_2. Lowercase an amounts[].room_type to obtain the matching rooms key: DOUBLE maps to {"double":1} and FAMILY_1 maps to {"family_1":1}. |
amounts[].deposit_ars, amounts[].deposit_usd, amounts[].num_of_payments |
These are the calculated deposit values and installment count for the selected room and departure date. Use the suffix that matches the selected money field. Do not send deposit totals in the reservation request; the reservation endpoint recomputes and persists them server-side. |
has_amounts |
true when at least one price has a selectable room in amounts. When it is false, every prices[].amounts list is empty. |
amounts[].has_kids |
Read this as a JSON boolean. For family rooms, use the product's kids_age as the exclusive child-age limit at trip end. |
flight, prices[].hotels, prices[].circuits, prices[].transfers, viajeflex_options |
Present these before checkout so the user confirms the operational travel details and included services attached to the selected departure. |
product.has_assistance |
Use this product boolean to tell the user whether the package includes travel assistance. Assistance selection and accounting details are internal and are not exposed by departure prices. |
Product departure contact responses return the same contact receipt as /contacts. The persisted contact uses the submitted product, departure, price, and room selection, with channel set to PUBLIC_API. The id_seller field is the assigned seller id, or null when the lead is not assigned.
{
"success": 1,
"data": {
"id_contact": 654321,
"id_seller": 103,
"creation_date": "2026-06-03 18:59:05",
"status": "PENDING",
"channel": "PUBLIC_API",
"id_product": 2158,
"id_departure": 9876,
"id_price": 4321
}
}
Product departure reservation responses return a reservation receipt. The persisted reservation uses authenticated API ownership, with channel and origin set to PUBLIC_API. Transfer reservations are created in the regular reservation flow. MercadoPago reservations are created as PENDING_PAYMENT and include a Checkout Pro redirect under payment.url.
{
"success": 1,
"data": {
"id_reservation": 765432,
"creation_date": "2026-06-03 19:11:02",
"status": "IN_PROCESS",
"channel": "PUBLIC_API",
"origin": "PUBLIC_API",
"id_product": 2158,
"id_departure": 9876,
"id_price": 4321,
"payment_option": "buy",
"payment_selection": "TRANSFER",
"price_usd": 200,
"deposit_usd": null,
"currency_to": "USD"
}
}
{
"success": 1,
"data": {
"id_reservation": 765433,
"creation_date": "2026-06-03 19:14:10",
"status": "PENDING_PAYMENT",
"channel": "PUBLIC_API",
"origin": "PUBLIC_API",
"id_product": 2158,
"id_departure": 9876,
"id_price": 4321,
"payment_option": "deposit",
"payment_selection": "MERCADOPAGO",
"price_usd": 200,
"deposit_usd": 50,
"currency_to": "USD",
"payment": {
"type": "redirect",
"provider": "MERCADOPAGO",
"url": "https://www.mercadopago.com.ar/checkout/v1/redirect?pref_id=123"
}
}
}
Contact receipt lookup returns the same data fields for contacts owned by the authenticated API consumer. The status field is the current contact workflow status and can change after creation.
{
"success": 1,
"data": {
"id_contact": 654321,
"id_seller": 103,
"creation_date": "2026-06-03 18:59:05",
"status": "FOLLOW",
"channel": "PUBLIC_API",
"id_product": 2158,
"id_departure": 9876,
"id_price": 4321
}
}
Reservation receipt lookup returns the persisted reservation receipt for reservations owned by the authenticated API consumer. The status field is the current reservation workflow status and can change after creation.
{
"success": 1,
"data": {
"id_reservation": 765432,
"creation_date": "2026-06-03 19:11:02",
"status": "DEBT",
"channel": "PUBLIC_API",
"origin": "PUBLIC_API",
"id_product": 2158,
"id_departure": 9876,
"payment_selection": "TRANSFER",
"price_usd": 200,
"deposit_usd": null,
"currency_to": "USD"
}
}
Destination listing responses include active destinations, their parent summary, and direct child summaries. Destination detail responses use the same fields, but data is the destination object directly.
{
"success": 1,
"data": {
"total": 3,
"results": [
{
"id": 410,
"name": "Miami",
"description": "Miami",
"parent_id": 23,
"url": "america-10/estados_unidos-23/miami-410",
"image": "miami.jpg",
"active": true,
"parent": { "id": 23, "name": "Estados Unidos" },
"children": []
}
]
}
}
Category listing responses include website-visible categories, their parent summary, and direct child summaries. The active field describes menu visibility state; disabled categories and categories under disabled parents are not returned. Category detail responses use the same fields, but data is the category object directly.
{
"success": 1,
"data": {
"total": 3,
"results": [
{
"id": 154,
"name": "Mundial",
"parent_id": null,
"url": "mundial-154",
"image": "mundial.jpg",
"active": true,
"parent": null,
"children": [
{ "id": 155, "name": "Final" }
]
}
]
}
}
Product group responses include the selected dimension, grouping level, product totals, drill-down URLs, and optional compact top-ranked products. In each top-ranked product, currency is the package base currency.
{
"success": 1,
"data": {
"by": "destination",
"group": "continent",
"groups": [
{
"id": 10,
"name": "Europe",
"parent_id": null,
"url": "europe-10",
"total": 42,
"products_url": "/api/v1/products?id_destination=10",
"top_ranked": [
{
"id": 1001,
"title": "Madrid escapada",
"url": "1001/madrid_escapada.html",
"image": "madrid.jpg",
"price_from_usd": 1200,
"currency": "USD"
}
]
}
]
}
}
Home responses return one section. A block section (slider/list/popup) returns blocks; ?type=banners returns banners. Each block carries its display metadata plus a resource; product resources and LIST products are compact home cards using the same money/flag conventions as /products, while category and destination resources are compact tile summaries. The example below shows a type=list response.
{
"success": 1,
"data": {
"blocks": [
{
"id": 1356,
"type": "list",
"order": 1,
"link": true,
"show_text": true,
"image_name": null,
"resource_type": "category",
"resource": {
"id": 126,
"name": "Playas",
"url": "playas-126",
"image": "playas.jpg"
},
"products": [
{
"id": 1684,
"title": "Paquete a Playas de Rep. Dominicana",
"url": "1684/paquete_rep_dominicana.html",
"image": "punta_cana.jpg",
"discount": 20,
"transport": "Aéreos",
"meal_plan": "All Inclusive",
"nights": [7, 8, 9, 10],
"has_hotel": true,
"has_transfer": false,
"has_assistance": false,
"has_tour": false,
"price_from_ars": 1903828,
"price_from_usd": 1200,
"deposit_from_ars": null,
"deposit_from_usd": null,
"num_of_payments": null
}
]
}
]
}
}
The ?type=banners section returns the banner strip under banners instead of blocks.
{
"success": 1,
"data": {
"banners": [
{
"name": "banner_mundial.png",
"description": "Mundial",
"link": "https://www.deturista.com/categoria/mundial-154"
}
]
}
}
Site responses return the site display fields, social links, legal text, and the public office list. Most fields are nullable, so null-check before rendering.
{
"success": 1,
"data": {
"id_site": 1,
"name": "deTurista.com",
"site_name": "Argentina",
"phone": "0810-333-8748",
"email": "info@deturista.com",
"working_hours": "Lunes a Viernes de 9 a 18 hs",
"address": "Av. Córdoba 1801/07, CABA, Buenos Aires",
"address_link": "https://maps.app.goo.gl/example",
"address2": null,
"address2_link": null,
"legal_info": "Razon social: deTurista.com de Aterrizando S.A - Leg. 16952",
"social": {
"facebook": "https://facebook.com/deturista",
"twitter": null,
"instagram": "https://instagram.com/deturista"
},
"offices": [
{
"id": 1,
"label": "Pilar",
"address": "Colectora Panamericana (Km 42.5) y Las Camelias, Pilar",
"working_hours": null,
"map_url": "https://www.google.com/maps/place/deTurista.com",
"image": "pilar_.png"
}
]
}
}
Newsletter creation returns 201 with the subscription receipt.
{
"success": 1,
"data": {
"id_newsletter": 472238,
"email": "ana@example.com",
"destination": "EUROPA",
"creation_date": "2026-07-21 11:42:29"
}
}
Working with the data
These conventions hold across every response, so handle them once instead of per endpoint.
| Topic | What to do |
|---|---|
| Dates and times | Timestamps such as creation_date and flight leg departure_date / arrival_date are in the API account's timezone, formatted YYYY-MM-DD HH:MM:SS. They are not ISO-8601 and carry no UTC offset, so do not assume one when parsing. Plain dates such as start_date, end_date, hotel check-in/check-out dates, and passenger birth_date are YYYY-MM-DD. |
| Nullable fields | Many non-money fields can be null or absent — for example subtitle, image, discount, route and luggage, and parent objects. Public money pairs are always present, with the unavailable currency set to null. |
| Money numbers | All public money fields (price_from_ars, price_from_usd, deposit_from_ars, deposit_from_usd, price_ars, price_usd, deposit_ars, deposit_usd) are emitted as ARS/USD pairs. Values are rounded integer JSON numbers without currency symbols, thousands separators, or decimals; an unavailable currency is null. Product currency and departure data.currency name the package base currency. Format amounts for display on the client side. |
| Deposit amounts | Preview fields named deposit_from_* are derived from the lowest available child price. The selectable deposit values are amounts[].deposit_ars and amounts[].deposit_usd in departure detail. num_of_payments is calculated server-side from the departure payment plan, including payment visibility, departure date, final-payment deadline, and increase rules. The create reservation endpoint never accepts client-supplied deposit totals; it recomputes them from the selected price and rooms. |
| Booleans drive the UI | Flags such as can_buy, effective can_deposit, and the product has_* flags are real JSON booleans (true / false), not 0 / 1. Gate buttons on them; do not infer them. |
Error shape
{
"success": 0,
"error": "unauthorized",
"message": "missing or invalid API key"
}
{
"success": 0,
"error": "invalid_request",
"message": "month must be between 1 and 12"
}
{
"success": 0,
"error": "not_found",
"message": "price not found"
}
For contact and reservation receipt lookup, 404 not_found also means the id is not owned by the authenticated API consumer.
{
"success": 0,
"error": "rate_limited",
"message": "rate limit exceeded"
}
429 with error: "rate_limited"; pause and retry with exponential backoff instead of hammering the endpoint. The exact limit is configured per consumer, so design clients to react to a 429 rather than assuming a fixed rate.
Every error carries a message. For invalid_request and not_found it is specific so you can fix the call (it names the offending field or the missing resource). For unauthorized and rate_limited it is intentionally generic and never reveals which key or limit failed — do not parse it, switch on the error code instead.
Swagger and OpenAPI schema
Swagger UI is bundled with this beta documentation and loads the local openapi.json schema. Use the Swagger Authorize button to enter your own X-API-Key; the docs do not include prototype credentials.
Beta and versioning
The contract is versioned under /v1. During beta it evolves additively: new fields and endpoints can appear without notice, but an existing /v1 field will not be removed or repurposed without a new version. Build defensively — read the fields you need and ignore any you do not recognise — so an additive change never breaks your integration.
test.deturista.com, Swagger selects the development host by default. Browser calls may still be blocked unless the selected API host allows your environment. The curl examples in this guide always work from a terminal or backend.
Public API (beta)