DeTurista Public API (beta)
Public JSON API (beta)

Product listings for partners and integrations

Use the DeTurista public API to search active public products, choose a departure and price, and then create a lead or reservation from that selected travel option.

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.

  1. 1
    Find a product GET /products or POST /products
    Keep
    id, can_buy, can_deposit
    Rule
    Show only visible products returned by the API. Use can_buy for full-payment reservations and can_deposit only to decide whether the deposit path can be offered; can_deposit already accounts for whether installments apply, and the payable deposit comes from the selected departure price.
  2. 2
    Choose a departure GET /products/{id}/departures
    Keep
    Departure id, dates, lowest price, seats, route, luggage
    Rule
    Present future active departures to the end user. The list is for choosing dates; it does not contain the final id_price.
  3. 3
    Choose price and rooms GET /products/{product_id}/departures/{departure_id}
    Keep
    prices[].id, amounts[].room_type, amounts[].deposit_ars / amounts[].deposit_usd, amounts[].num_of_payments, flight, included services
    Rule
    The selected id_price must come from this response, and the requested room types must exist in that price's amounts.
  4. 4
    Preview price and coupon POST .../price-preview
    Keep
    total_ars / total_usd, new_total_ars / new_total_usd, applied code
    Rule
    Use the same id_price and rooms the user selected. A returned default_code is only a suggestion; send it as coupon_code only after the user applies it.
  5. 5
    Create contact or reservation POST .../contacts or POST .../reservations
    Keep
    Receipt id, creation_date, current status, and payment.url when MercadoPago is selected
    Rule
    Create a contact for a lead or call/WhatsApp handoff. Create a reservation only after the user has selected a valid payment option, payment method, price, rooms, customer, and passengers.
Room counts are room counts, not passenger counts. A request with "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.

curlDiscover by destination
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.

curlFirst call
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

curlQuery string search
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

curlJSON body search
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

curlExact product list
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

curlSingle product
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

curlProduct departures
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

curlProduct departure detail
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

curlProduct departure price preview
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

curlDestination contact
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.
JSONDestination contact response
{
  "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

curlProduct departure contact
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

curlProduct departure reservation
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

curlContact receipt
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

curlReservation receipt
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

curlDestination list
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

curlCategory list
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

curlDestination groups
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.

curlHero slider blocks
curl -sS 'https://api.deturista.com/v1/home?type=slider' \
  -H 'X-API-Key: YOUR_API_KEY'
curlProduct-list carousels
curl -sS 'https://api.deturista.com/v1/home?type=list' \
  -H 'X-API-Key: YOUR_API_KEY'
curlPopup
curl -sS 'https://api.deturista.com/v1/home?type=popup' \
  -H 'X-API-Key: YOUR_API_KEY'
curlBanner strip
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

curlSite and footer metadata
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

curlSubscribe an email
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.
For development, use https://test.deturista.com/api/v1 instead of https://api.deturista.com/v1.

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.

JSONUsers
{
  "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.

JSONSeller availability
{
  "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.

JSONHotel detail
{
  "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.

curlAsk for a seller
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.

curlUpdate a contact
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.

Success 200 OK
Create success 201 Created
Auth failure 401 Unauthorized
Invalid request 400 Bad Request
Not found 404 Not Found
Rate limit 429 Too Many Requests
The default rate limit is 10 requests per second per API consumer. A configured consumer limit overrides that default.

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.

JSONSuccess shape
{
  "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.

JSONProduct detail
{
  "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.

JSONUSD display site product
{
  "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.

JSONProduct 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.

JSONProduct departure detail
{
  "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.

JSONProduct departure contact
{
  "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.

JSONProduct departure reservation
{
  "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"
  }
}
JSONMercadoPago reservation
{
  "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.

JSONContact receipt
{
  "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.

JSONReservation receipt
{
  "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.

JSONDestinations
{
  "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.

JSONCategories
{
  "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.

JSONProduct groups
{
  "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.

JSONHome list section (type=list)
{
  "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.

JSONHome banners section (type=banners)
{
  "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.

JSONSite metadata
{
  "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.

JSONNewsletter 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

JSONUnauthorized
{
  "success": 0,
  "error": "unauthorized",
  "message": "missing or invalid API key"
}
JSONInvalid request
{
  "success": 0,
  "error": "invalid_request",
  "message": "month must be between 1 and 12"
}
JSONNot found
{
  "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.

JSONRate limited
{
  "success": 0,
  "error": "rate_limited",
  "message": "rate limit exceeded"
}
Requests are rate limited per API key. When you exceed your limit you get HTTP 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.

During beta, treat Swagger as a reference and call the API from your server. Swagger's Try it out runs from your browser against the selected server; when this page is opened from 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.