Adrasis™

Property content

Build and refresh a profile-scoped Channel Portfolio with opaque keyset cursors, a gap-free change-feed handoff, and language selection.

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.

  1. Build a profile snapshot. Walk GET /properties to its terminal page, then atomically store the snapshot and returned changes_cursor.
  2. Continue from the handoff. Call GET /catalog/changes with that cursor and the same resolved profile. Do not start a second independent timeline.
  3. Apply each page. Fetch every upsert from its typed detail/list surface and apply every delete tombstone locally. Apply operations in response order; repeated upserts are safe.
  4. Commit atomically. Store the local catalogue changes and next_cursor in one local transaction. If processing fails, retry the same cursor; the feed is replay-safe.
  5. 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:read scope these endpoints require.