The content surface is your catalogue source: property headers, hero photos,
and per-room detail. It is read-only and is not metered, so you can sync and
refresh as often as you need. Both endpoints require the distribution:read
scope.
The recommended pattern is cache locally, then refresh by delta: pull the full catalogue once, persist it, and on each subsequent sync ask only for what changed.
Hotel id
Every hotel is addressed by its Adrasis hotel id: the hotel's entry in the
Adrasis catalog, written as a decimal string of digits such as "72259". It is
the same in every environment and for every seller on Adrasis, so the mapping
you build while certifying against the test environment holds in production,
and a hotel two sellers offer you carries one id. Store it as a string and use
it wherever the API names a hotel: a property's public_id here,
property_ids and property_id in search, property_public_id in the
availability check, prebook and the booking list, and property_id in a
webhook subscription. Other resource identifiers, including rooms, rate plans
and bookings, remain opaque strings; places and countries have their own ids
(see Place id). A property without a shared catalog entry has no
Adrasis hotel id and is not returned by this API.
Place id
Every place is addressed by its Adrasis place id: the place's entry in the
Adrasis place dictionary that GET /places publishes, written as a decimal
string of digits such as "87066". Like the hotel id it is the same in every
environment and never renumbered, so a place mapping built while certifying
holds in production. A country is addressed by its ISO 3166-1 alpha-2 code
such as "TR", wherever a place id is accepted or returned: a country row of
/countries and /places, a top-level place's parent_place_id, and a
country-wide search or property filter. Store both as strings. The fields that
carry them are named for it: a place's or a country's place_id and a place's
parent_place_id, the parent_place_id filter of /places, the place_id
filter of /properties, place_id, near.place_id and filters.place_ids
in search, the response place, the place_id of a property's
location.administrative_areas[] and location.destinations[], and of the
region and airport of a service zone or tour. A place facet's value
carries a place id as well.
List properties
GET /api/v1/properties
Returns a price-free keyset page from the resolved partner profile's explicit Channel Portfolio. The credential authenticates one partner account. Omit the profile selector to use that account's stable Default profile, or select another account-owned profile as described in Authentication.
Only active, published accommodation properties explicitly opened in that
profile are returned: an archived, closed, draft or in-review property is not
listed. A property published after the profile was created remains closed
until the account owner opens it. Enrollment, source market, destination rules,
traveler point of sale, rates, inventory, and availability do not change
Channel Portfolio membership. place_id optionally narrows the selected
portfolio to a country or a searchable place together with all descendants.
Query parameters
| Parameter | Type | Default | Notes |
|---|---|---|---|
place_id |
string | Not set | Adrasis place id from /places, or a country's ISO code; matches self and descendants. Airport and landmark IDs are rejected. An ID that is not in the /places dictionary, including an administrative-only administrative_areas[].place_id, answers 404 PLACE_NOT_FOUND. |
cursor |
string | Not set | Opaque, encrypted continuation token from pagination.next_cursor. |
limit |
integer | 50 |
Page size, 1–200. Out-of-range values are rejected. |
updated_since |
RFC 3339 instant | Not set | When present, returns only properties whose content_updated_at is strictly greater than this value. This is the delta cursor. |
language |
BCP 47 tag | en |
Resolves property-type and place names with deterministic fallback. A BCP 47 tag such as tr-TR; the server canonicalizes the casing (tr-tr becomes tr-TR) and reads an underscore as the hyphen (tr_TR becomes tr-TR). A malformed tag answers 400; a well-formed tag the catalogue does not carry falls back to the default, with regional then English fallback. Every language parameter in this API follows this rule. |
Example
curl -s \
'https://ari.console.adrasis.com/api/v1/properties?place_id=87093&limit=50' \
-H 'Authorization: Bearer <ACCESS_TOKEN>'
{
"properties": [
{
"public_id": "72259",
"name": "Grand Bosphorus Hotel",
"slug": "grand-bosphorus-hotel",
"star_rating": 5,
"property_type": {
"public_id": "propertyTypePublicId",
"code": "property_type.hotel",
"name": "Hotel"
},
"location": {
"country_code": "TR",
"latitude": "40.9906",
"longitude": "29.0274",
"address": "Caferağa, Kadıköy, İstanbul",
"administrative_areas": [
{
"place_id": "32937",
"place_type": "province_state",
"name": "Istanbul"
},
{
"place_id": "33481",
"place_type": "district",
"name": "Kadıköy"
},
{
"place_id": "33484",
"place_type": "neighborhood",
"name": "Caferağa"
}
],
"destinations": [
{
"place_id": "33481",
"place_type": "district",
"name": "Kadıköy"
},
{
"place_id": "87093",
"place_type": "city",
"name": "Istanbul"
},
{
"place_id": "32937",
"place_type": "province_state",
"name": "Istanbul"
}
]
},
"currency_code": "TRY",
"timezone_iana": "Europe/Istanbul",
"registry_number": "2022-48-2143",
"content_updated_at": "2026-06-01T08:30:00Z"
}
],
"pagination": { "limit": 50, "has_more": true, "next_cursor": "opaque" }
}
location carries two place lists with different jobs. administrative_areas
is the property's official address hierarchy, broadest to most specific
(province, district, neighborhood as the country defines them). An optional
area may follow those address levels as tourism context; always read an
entry's place_type instead of inferring it from the array position.
destinations is an ordered destination-membership list. Its first item is the
property's primary destination; remaining items follow increasing membership
depth, with genealogical ancestors before curated membership scopes. These are
the identifiers travellers search by and the ones to use as destination
selectors in availability calls. Adjacent entries are not a promise of direct
parent-child ancestry. To reconstruct the destination genealogy, read each
item's parent_place_id from GET /places; it identifies the direct
destination parent.
The two axes may classify or name the same real-world place differently, but they keep one identity:
| Place | In administrative_areas |
In destinations |
Identity rule |
|---|---|---|---|
| Kadıköy | district, Kadıköy |
district, Kadıköy |
The same place_id appears in both lists. |
| Afyonkarahisar Merkez | district, Merkez |
city, Afyonkarahisar |
The same place_id has an axis-specific type and display name. |
| Bodrum | district, Bodrum |
city, Bodrum |
The type divergence is intentional; do not create a second identity. |
Use the relevant entry from destinations when displaying or sending a
destination. Do not derive destination ancestry from administrative_areas or
normalize these axis-specific differences in client code.
Paging
pagination.has_more is true when a following page exists. Send the returned
opaque cursor unchanged; it is bound to the partner account, resolved profile,
place, language, delta filter, and profile catalogue revision. Repeat any
place_id, language, or updated_since value from the first request
unchanged on every continuation. You may repeat the same profile selector, but
it must identify the profile sealed by the cursor. Walk until has_more is
false:
# first page
curl -s 'https://ari.console.adrasis.com/api/v1/properties?limit=200' ...
# next page (only if has_more was true)
curl -s 'https://ari.console.adrasis.com/api/v1/properties?limit=200&cursor=<NEXT_CURSOR>' ...
The terminal page, including an empty first page, contains changes_cursor.
Persist it before replacing your local snapshot; it is the exact checkpoint for
the subsequent GET /catalog/changes poll. If Channel Portfolio membership
changes during the walk, the API returns 409 CATALOG_VERSION_CHANGED; discard
the partial snapshot and restart from the first page.
An invalid or obsolete property-list cursor returns 400 INVALID_REQUEST
with field=cursor. Restart from the first page. The change feed has its own
cursor recovery rules, described under Incremental catalogue changes.
Get one property
GET /api/v1/properties/{public_id}
Returns the full content document for one property: the summary header, the hero photo URLs, and the per-room catalogue. Detail applies exactly the same resolved profile and Channel Portfolio boundary as the unfiltered and place-filtered lists.
Detail query parameters
| Parameter | Type | Notes |
|---|---|---|
language |
BCP 47 tag | Resolves room display names to this language (e.g. tr-TR), with the same validation as on the list. Falls back to the default language (en) when a translation is missing. |
Detail example
curl -s \
'https://ari.console.adrasis.com/api/v1/properties/72259?language=tr-TR' \
-H 'Authorization: Bearer <ACCESS_TOKEN>'
Property list and detail calls deliberately accept no point-of-sale field.
Supply traveler country_code only when requesting availability; a property
that is visible as catalogue metadata may correctly have no sellable result for
that point of sale.
{
"property": {
"public_id": "72259",
"name": "Grand Bosphorus Hotel",
"star_rating": 5,
"property_type": {
"public_id": "propertyTypePublicId",
"code": "property_type.hotel",
"name": "Otel"
},
"location": {
"country_code": "TR",
"administrative_areas": [
{
"place_id": "32937",
"place_type": "province_state",
"name": "İstanbul"
},
{
"place_id": "33481",
"place_type": "district",
"name": "Kadıköy"
},
{
"place_id": "33484",
"place_type": "neighborhood",
"name": "Caferağa"
}
],
"destinations": [
{
"place_id": "33481",
"place_type": "district",
"name": "Kadıköy"
},
{
"place_id": "87093",
"place_type": "city",
"name": "İstanbul"
},
{
"place_id": "32937",
"place_type": "province_state",
"name": "İstanbul"
}
]
},
"currency_code": "TRY",
"timezone_iana": "Europe/Istanbul",
"registry_number": "2022-48-2143",
"content_updated_at": "2026-06-01T08:30:00Z",
"attributes": [
{
"definition": {
"public_id": "wifiAttributePublicId",
"code": "amenity.wifi_free",
"name": "Ücretsiz Wi-Fi"
},
"kind": "amenity",
"value_type": "none",
"value": null,
"unit": null,
"charge_option": "free"
}
],
"min_guest_age": 0,
"check_in_min_age": 21,
"check_in_from": "14:00",
"check_out_until": "12:00",
"important_information": [
{
"category": "DEPOSIT",
"text": "A refundable damage deposit of 5,000 TRY is taken at check-in.",
"stay_from": null,
"stay_to": null
},
{
"category": "FACILITY",
"text": "The outdoor pool is closed for maintenance from 1 to 30 November.",
"stay_from": "2026-11-01",
"stay_to": "2026-11-30"
}
],
"primary_photo_url": "https://cdn.adrasis.com/media/hero.jpg",
"primary_photo_medium_url": "https://cdn.adrasis.com/media/hero_medium.jpg",
"rooms": [
{
"public_id": "r3Tz9Lm0",
"name": "Deluxe Kral Oda",
"name_locale": {
"en-US": "Deluxe King Room",
"tr-TR": "Deluxe Kral Oda"
},
"type": {
"public_id": "suiteRoomTypePublicId",
"code": "room_type.suite",
"name": "Süit"
},
"max_occupancy": 3,
"max_adults": 2,
"size_sqm": 28,
"room_count": null,
"smoking_policy": "unknown",
"beds": [
{
"type": {
"public_id": "kingBedTypePublicId",
"code": "bed_type.king",
"name": "King Yatak"
},
"count": 1
}
],
"attributes": [
{
"definition": {
"public_id": "nonSmokingAttributePublicId",
"code": "room_amenity.non_smoking",
"name": "Sigara İçilmez"
},
"kind": "amenity",
"value_type": "none",
"value": null,
"unit": null,
"charge_option": "free"
}
],
"primary_photo_url": "https://cdn.adrasis.com/media/abc.jpg"
}
]
}
}
Resource references expose opaque public_id, stable locale-independent
code, and a name resolved to the requested language. Internal database IDs
are never returned. The full name_locale map is returned when translations
exist, so clients can re-resolve without a second call. A public_id that does
not resolve inside the selected Channel Portfolio, or names a property that is not
on sale (archived, closed, draft, in review or unpublished), returns
404 PROPERTY_NOT_FOUND.
Attribute bindings use the same rule. Branch on definition.code, interpret
value according to value_type, and use unit only when it is non-null.
Do not branch on localized names.
Guest age rule
The detail document carries min_guest_age: the youngest age, in whole years
at check-in, a guest may be. 0 accepts every age, 1 to 17 refuse younger
children, and 18 marks an adults-only property. List rows do not carry it;
read it from the detail document to show the rule before a traveller searches.
Search enforces the rule for you. A party with a child younger than
min_guest_age gets no allocation from the property, with
unavailable_reason GUEST_AGE_NOT_ACCEPTED, and prebook refuses it. Send the
real age of every child in occupancy[].child_ages. See
Currencies, occupancy & allocations.
Check-in minimum age
The detail document may carry check_in_min_age: the minimum age, in whole
years, of the guest who checks in. Show it to the traveller, for example as
"The guest checking in must be at least 21 years old." It is information only:
search and booking never refuse a party for it, and a booking does not carry the
lead guest's age. The field is absent when the property does not state one. It
is separate from min_guest_age, which says how young any guest may be.
Check-in and check-out times
The detail document may carry check_in_from, the time from which guests can
check in, and check_out_until, the time by which they check out. Both are
HH:MM on the 24-hour clock in the property's local time, the zone named by
timezone_iana, so "14:00" at a property in Europe/Istanbul is 14:00 in
Istanbul whatever your own time zone. Each field is absent when the property
does not state it. List rows do not carry them.
Tourism licence number
Property rows and the detail document may carry registry_number: the official
tourism licence number of the property. In Turkey it is the document number
issued by the Ministry of Culture and Tourism, which resellers must show or
check against the ministry's facility verification service for every listing.
The field is absent when the property states no licence number.
Important information
The detail document carries important_information: notes from the property
that the traveller should read before booking, in display order. Show them on
the property page and again at checkout. List rows do not carry them.
| Field | Meaning |
|---|---|
category |
CHECK_IN, GUEST_REQUIREMENT, DEPOSIT, FEE, PAYMENT, PETS, FACILITY or OTHER. Use it to group or label notes; do not branch on text. |
text |
The note in the requested language, or in a language the note has when the requested one is missing. |
stay_from, stay_to |
The stay dates the note applies to, as inclusive ISO dates. null on either side means no bound. |
A note whose last stay date has passed, in the property's time zone, is no
longer listed, so the array only holds notes that still apply to some future
stay. A note that only expires does not move content_updated_at or appear in
the change feed: if you store content, drop a stored note yourself once its
stay_to is in the past. A note with a window, such as
a closure in November, applies only to stays that overlap it: show it for those
dates, or show the window beside the text. The array is empty when the property
has no notes. Notes are information only: search and booking never refuse a
party for them.
Reference and vertical discovery
Use the authenticated reference endpoints to populate selectors and resolve every identifier required by search requests:
| Endpoint | Discovers |
|---|---|
GET /countries?language=tr-TR |
Countries by ISO code, with localized names |
GET /reference/categories?namespace=property_type |
Property types |
GET /reference/categories?namespace=room_type |
Room types |
GET /reference/categories?namespace=bed_type |
Bed types |
GET /reference/categories?namespace=meal_plan |
Meal plans (BB, RO, HB, ... as projected on offers) |
GET /reference/attributes?scope=room |
Attribute definitions and stable slugs |
GET /reference/place-types?country_code=TR |
All place kinds; searchable and current catalog available state |
GET /places?country_code=TR |
Complete destination dictionary across all available place types; use parent_place_id to reconstruct its genealogy |
GET /places/{place_id} |
One canonical place by its Adrasis place id; send If-None-Match for 304 |
GET /places?country_code=TR&place_type=city |
Only destination nodes classified as city; the filter does not return other hierarchy levels |
GET /service-zones |
Transfer route, tour pickup, and flight endpoint IDs |
GET /transfer-vehicle-types |
Private-transfer vehicle IDs and capacities |
GET /tours |
Tour and embedded modality IDs |
GET /flights |
Flight IDs, routes, cabins, and operating days |
List endpoints use limit (maximum 200) and an opaque cursor. Continue with
the returned next_cursor while has_more is true. Reference and vertical
catalog responses return ETag; send it in If-None-Match to receive 304
when the representation has not changed. Echo the value exactly as received,
including a W/ prefix if a proxy added one on the way to you: the comparison
treats W/"x" and "x" as the same representation. A page keeps the same
next_cursor, and so the same ETag, for up to five minutes while its content
is unchanged, and a cursor stays valid for at least 15 minutes after you
receive it.
GET /places exposes the destination axis only; it is the canonical dictionary
for partner place filters. place_type narrows the result set to exactly that
destination classification. parent_place_id describes direct genealogy in
that dictionary, while a property's location.destinations describes all
destination memberships relevant to that property.
Incremental catalogue changes
Use GET /catalog/changes as the checkpointed change feed for the resolved
partner profile. Each item contains a resource type, resource_public_id, an upsert or
delete operation, and the time the profile-visible change was recorded. For
resource_type: property, resource_public_id is the Adrasis hotel id. Other
resource types keep their opaque public IDs.
Bootstrap with a complete GET /properties cursor walk. When the terminal page
arrives, atomically replace your local property snapshot and save its
changes_cursor. Start the change feed with that cursor. The checkpoint was
captured when the snapshot began, so updates committed while you were paging
appear in the feed instead of falling into a snapshot-to-feed gap.
An upsert is an instruction to fetch the resource's current representation
from its typed endpoint. A delete is a tombstone: remove that public ID from
your local catalogue. A property that leaves sale (unpublished, archived,
closed, or sent back to draft or review) comes as a delete, and one that
returns to sale as an upsert. The feed deliberately does not copy full resource
documents into events, so replay always converges on the authoritative current
representation.
Page with limit (maximum 200). Persist next_cursor after processing a page
successfully and send it as cursor on the next request. The response always
returns a new checkpoint, including an empty page and a page where
has_more=false; continue polling from that checkpoint. Cursors are opaque,
encrypted, account-bound, profile-bound, filter-bound, and tamper-evident. Do
not parse or construct them. A cursor the feed can no longer continue answers
409 CATALOG_VERSION_CHANGED: rebuild the snapshot with a full
GET /properties walk and continue from its changes_cursor.
Repeat resource_type to maintain a filtered feed, for example
resource_type=property&resource_type=tour. Reuse exactly the same filter set
with the returned cursor. For a complete catalogue, omit the filter. An invalid
cursor is 400 INVALID_CURSOR and an unsupported resource_type is
400 INVALID_REFERENCE_FILTER.
Recommended cache-and-change-feed pattern
- Build a profile snapshot. Walk
GET /propertiesto its terminal page, then atomically store the snapshot and returnedchanges_cursor. - Continue from the handoff. Call
GET /catalog/changeswith that cursor and the same resolved profile. Do not start a second independent timeline. - Apply each page. Fetch every
upsertfrom its typed detail/list surface and apply everydeletetombstone locally. Apply operations in response order; repeated upserts are safe. - Commit atomically. Store the local catalogue changes and
next_cursorin one local transaction. If processing fails, retry the same cursor; the feed is replay-safe. - Drain and poll. While
has_more=true, request the next page immediately. When it becomes false, retain the returned cursor and poll it on your normal refresh interval.
The property-only updated_since parameter remains available as an optimization
for targeted refreshes. The complete snapshot plus terminal changes_cursor
handoff is the canonical bootstrap because it covers both updated content and
Channel Portfolio membership tombstones without reconstructing state on the
client. Content and reference reads are not metered.
Next steps
- The booking flow: Turn catalogue into bookings via
search → prebook → book. - Authentication: Review the
distribution:readscope these endpoints require.