Overview

Developer Tools is the integration hub of Ananas GDS. It provides four distinct capability areas: token-based API access for partners, embeddable HTML/JS widgets for displaying fact sheets on external sites, outbound webhooks for real-time event delivery, connections to external GDS/OTA/PMS platforms, and a bulk data export engine. All capabilities are scoped to the authenticated owner; sub-users require the developer_tools permission flag.

API Keys

API keys (model ApiKey in the settings app) give external systems — tour operators, channel managers, and partner integrations — token-based read access to your data. Each key is tied to a whitelisted domain and carries per-resource permission flags.

Field Description
title Human-readable label for the key.
description Optional usage note.
domain FK to Whitelist — restricts where the token may be used. For browser widgets this is matched against the embedding page's domain; for the server-to-server v1 pull API it is matched against the caller's source IP. See Domain & IP Whitelist .
token Auto-generated 30-character dash-delimited token. Shown once on creation.
fact_sheet Grants access to the facts API endpoint.
stop_sale Grants access to the stop-sale API endpoint.
hotel_photos Grants access to the photos API endpoint.
invoices Grants access to the invoices API endpoint (B2B invoices the account is a party to; received drafts are never exposed).
rooming Grants access to the rooming-list API endpoint (ledgers + reservation entries, both TO and hotel side).
last_called_at Timestamp of the most recent authenticated request.

The token is passed in the URL path of v1 API requests:

Example
GET https://app.ananas-gds.com/api/v1/facts/{token}/
GET https://app.ananas-gds.com/api/v1/photos/{token}/
GET https://app.ananas-gds.com/api/v1/stop-sale/{token}/
GET https://app.ananas-gds.com/api/v1/invoices/{token}/     ?direction=issued|received &status= &from= &to=
GET https://app.ananas-gds.com/api/v1/rooming/{token}/      ?status= &from= &to= &property_id=1,2,3

Pulls are personalized per tour operator

The v1 API applies the calling account's own override and mapping layers, so two TOs pulling the same hotel get different, personalized payloads. Facts : the TO's Override Facts (location texts, distances, check-in/out, pools, notes) are merged over the hotel's published sheet, and the TO's mapping is applied: a mapping block carries the hotel code / internal / external / supplier name, each mapped room gets its room_code and overridden name, and deactivated rooms are removed from the export. Photos : hotel photos the TO hid are removed and the TO's own uploads are appended. Stop sale : room/hotel codes and event actions come from the TO's HotelMapping / RoomMapping, and properties where the TO switched the stop-sale API off are excluded. Rooming : each ledger's hotel block carries the TO's identity codes for that property. Invoices : an optional property reference names the invoiced hotel plus the caller's identity codes for it. Accounts without overrides get the hotel's original data unchanged.

Domain & IP Whitelist

Each API key links to one Whitelist entry. A single entry can list multiple values separated by commas, spaces, or semicolons , and each value may be a domain, an IP address, or a CIDR range. How a value is matched depends on how the token is used:

Transport Matched against Behaviour
Browser widget (iframe / JS embed) The embedding page's domain , read from the Origin / Referer header. Forgiving: https:// , paths, ports, and a leading www. are stripped before comparison, so https://hotel.com/page.php , hotel.com , and www.hotel.com all match the same entry. A whitelisted apex ( hotel.com ) also covers its subdomains ( book.hotel.com ).
v1 pull API (server-to-server) The caller's source IP ( REMOTE_ADDR ). Exact match against IP or CIDR entries; a domain entry is forward-resolved (DNS A/AAAA) and the source IP must be one of its addresses. The spoofable X-Forwarded-For header is intentionally ignored.

What to whitelist

For a widget on a hotel website (e.g. hotel-cabana.com/factsheet.php ), whitelist the site domain hotel-cabana.com . For a server pulling the v1 API , whitelist that server's static IP (or its domain, which is DNS-resolved). You can list both in one entry. An empty whitelist denies the v1 pull API (contracted data fails closed). A widget with an empty whitelist is unrestricted, since it only serves already-published, public fact-sheet data.

Widgets without a referrer

Some browsers and privacy settings strip the Referer header. Because the widget shows only public data, it still renders in that case and the request is logged server-side rather than blocked. The domain whitelist is therefore a hot-link deterrent for widgets, and a strict gate for the v1 data API.

Endpoint Description
GET /api/settings/whitelists/ List all whitelisted domains.
POST /api/settings/whitelist/create/ Add a new whitelisted domain.
PUT /api/settings/whitelist/update/{id}/ Update domain name or label.
DELETE /api/settings/whitelist/delete/{id}/ Remove a whitelisted domain.

API Key Endpoints

Method Path Description
GET /api/settings/api-keys/ List all API keys for the account.
POST /api/settings/api-key/create/ Create a new API key. Token is returned in this response only.
PUT /api/settings/api-key/update/{id}/ Update key metadata (title, description, permissions).
DELETE /api/settings/api-key/delete/{id}/ Revoke and delete a key. Existing requests using the token will immediately fail.

Testing Your Integration (free test lane)

The live v1 endpoints are rate limited (free tier: one call per token every 3 hours; the Premium API feature lifts this, and hotel-set per-contract pull rates always apply). Development and field-mapping work should therefore use the test lane , which runs the exact same data path and returns the exact same response shape, but is capped and never counts against the rate limit or API statistics :

  • In the app: Dev Tools → API Integration → Endpoint Builder → Send Test Request . Pick a token, endpoint, and filters; the preview response renders inline and can be downloaded as JSON to develop against offline.
  • Via MCP: the ananas_dev_test_call tool on the Ananas MCP server takes token , endpoint ( facts | photos | stop-sale | invoices | rooming ), and optional countries / property_ids filters.

Test responses are capped at 10 properties (stop-sale additionally limited to the next 30 days instead of 365) and carry "preview": true in the meta object. Only tokens belonging to your own account can be tested, the caller must be authenticated in the app (session) or on the MCP server, and test traffic is lightly paced (one call every couple of seconds). The public /api/v1/ endpoints are unaffected: production systems keep exactly one rule set.

Method Path Description
POST /api/v1/test-call/ Session-authenticated preview of a v1 endpoint: body {token, endpoint, countries, property_ids} . Capped, unthrottled, not logged to API statistics.

Embeddable Widgets

Widgets let you embed a styled fact sheet directly on any external website. Each widget is bound to a specific property and API key. You configure the visual appearance — fonts, colours, sections, layout — in the widget editor, then copy the generated embed code.

Field Description
key UUID used in the embed URL. Auto-generated, never changes.
property The property whose data is displayed.
selected_token The API key token used to authenticate the widget data fetch.
font_size_base Base font size in px (default 14).
font_select Font family override.
background_color / border_color Hex colours for the widget container.
content_type bullet | text | rich | minimal — controls how facts are rendered.
dark_mode Enables dark background styling.
sections Ordered list of WidgetSection objects defining the layout.

Widget Sections

A widget is composed of one or more ordered sections. Each section has a type and optional style overrides:

Type Description
hero Full-width header with property name and optional subtitle.
bullets Bullet-point list of fact sheet entries.
paragraph Free-text block with optional title.
photo_grid Photo gallery grid (uses photos_per_row config).
text_photo Side-by-side text and image layout.
custom_html Raw HTML block for custom content.

Embed Code

Three embed options are available from the widget editor. All three render the same fact sheet and honour the domain whitelist of the bound API key — copy one into the target page. The {key} is the widget's UUID; the key-based options are recommended.

Key iframe (recommended)
<iframe
  src="https://app.ananas-gds.com/api/external/widget/view/?key={key}"
  width="100%" height="700" frameborder="0" scrolling="yes"
  style="border:none; width:100%;"></iframe>
JS embed (injects into a div)
<!-- Hotel Facts Widget -->
<div id="hotel-facts-widget-{key}"></div>
<script src="https://app.ananas-gds.com/api/external/widget/js-loader/{key}/" async></script>
Legacy token iframe
<iframe
  src="https://app.ananas-gds.com/api/external/widget/?token={token}&pro={property_id}"
  width="100%" height="700" frameborder="0" scrolling="yes"
  style="border:none; width:100%;"></iframe>

Setup order

1. Add the embedding site's domain to a Whitelist . 2. Bind that whitelist and enable fact_sheet on the API key. 3. In the widget editor, set the widget's selected_token to that API key. 4. Publish the property's fact sheet. 5. Paste one snippet above into the page. If a snippet returns Blocked Domain , the server log shows the exact host it saw versus the allowed list.

Widget Endpoints

Method Path Description
GET /api/settings/widget/{property_id}/ Get widget config for a property.
POST /api/settings/widget/create/ Create a new widget for a property.
PUT /api/settings/widget/edit/ Update widget appearance or section configuration.

External Connections

The Integrations section lets you connect Ananas GDS to external platforms. Supported system types cover GDS (Amadeus, Sabre, Travelport), OTA (Booking.com, Expedia, Airbnb, TripAdvisor), PMS (Oracle Opera Cloud, Mews, Cloudbeds, Apaleo, SiteMinder), and custom endpoints.

Field Description
name Friendly name for the connection.
system_type gds | ota | pms | custom
provider Specific provider (amadeus, sabre, oracle_opera, mews, etc.).
status active | inactive | error | pending
credentials JSON of provider-specific auth credentials (encrypted at rest).
config Provider configuration, including the per-connection mapping : a resource_map (external ids -> Ananas ids, e.g. an Apaleo unit-group -> a Room) and optional field_rules (declarative transforms: rename / default / enum-map / date-format / const). Onboarding a partner variant is config, not code.
inbound_token UUID used as the inbound webhook path for this connection.
last_synced_at Timestamp of the most recent successful sync.

Inbound Ingestion Pipeline

External systems push data to /api/v1/integrations/inbound/{inbound_token}/ . Each request is: verified (per-provider signature), deduplicated (recorded as an InboundEvent , unique per (connection, provider event id) -- a re-delivered event is acknowledged but not re-processed), acknowledged fast (HTTP 200 within the provider's timeout), then processed asynchronously by a worker that maps the payload through the connection's mapping and writes it into the right module with idempotent upserts. Each InboundEvent ends as processed , skipped (e.g. an unmapped resource -- logged, never guessed), or failed .

Generic CSV / JSON Import (any system)

For any system without a bespoke connector, POST /api/v1/integrations/import/{inbound_token}/?format=csv|json&target=availability accepts a CSV or JSON file and maps its rows through the connection's resource_map + field_rules into the target module. The availability target writes room-category open/closed status (a count of 0, or a status of open/closed/request/blocked) into the StopSale calendar, idempotent per room+date. This is the universal inbound path: any channel manager, PMS, or spreadsheet that can export a file can push data into Ananas. Returns {processed, warnings} ; unmapped rooms or bad dates are reported as warnings, never guessed.

Generic CSV / JSON Export (any system)

The mirror of the generic import: GET /api/v1/integrations/export/{inbound_token}/?target=availability&format=csv|json&from=&to= returns the connection owner's StopSale calendar as CSV or JSON, with Ananas room ids reverse-mapped back to the external room codes from the connection's resource_map so the receiving system gets its own identifiers. The status labels it emits ( open / closed / request / blocked ) are exactly what the generic import accepts, so any system can round-trip availability in and out of Ananas over two symmetric endpoints.

Apaleo Connector (PMS)

The first flagship connector. Apaleo webhooks are "thin" (they carry the entity id), so the connector fetches the full reservation from the Apaleo API using OAuth2 client-credentials. Inbound effects:

  • reservation.created / amended -> the stay nights are Blocked on the StopSale availability calendar (idempotent per room+date).
  • reservation.checked-in / checked-out -> the matching rooming-list entry (by external_ref ) is updated. A PMS reservation never creates a rooming entry -- ledgers are tour-operator-owned; an unmatched check-in is recorded as skipped .
  • reservation.canceled -> the group-hold blocks for those nights are cleared.

Each sync attempt is also recorded in a ConnectionSyncLog with status, records processed, records failed, and a message for debugging.

Cloudbeds Connector (PMS)

Same thin-webhook + OAuth2-fetch pattern as Apaleo, sharing the canonical reservation effects. A Cloudbeds webhook ( object / action + reservation id) is deduped and processed asynchronously: reservation.created / modified block the stay nights on the StopSale calendar (idempotent per room+date, room category mapped via resource_map ); canceled / deleted clear those blocks; a status_changed to checked-in/out updates the matching rooming entry (by external_ref , never auto-created). Point the Cloudbeds webhook at the connection's inbound URL. Live-verify pending a Cloudbeds account.

Mews Connector (PMS)

Same shared reservation effects, adapted to Mews specifics: its Connector API authenticates with ClientToken + AccessToken in the request body (not a bearer header), webhooks arrive as a batch (an Events array), and routing is state-driven -- the reservation is fetched and its State read. Confirmed blocks the nights; Started blocks and marks the rooming entry checked-in; Processed marks checked-out; Canceled clears the blocks. Live-verify pending a Mews account.

AlpineBits Server (DACH hotels & channel managers)

Ananas speaks AlpineBits HotelData -- the free OpenTravel-based standard most German/Austrian/Italian hotels and channel managers already use. A client connects to POST /api/v1/alpinebits/ (multipart action + request , HTTP Basic auth, X-AlpineBits-ClientProtocolVersion header). Per-connection credentials are a username + a hashed password stored on the connection.

  • Handshake ( OTA_Ping ): returns the capability intersection between the client and what Ananas supports.
  • FreeRooms ( OTA_HotelInvCountNotif:FreeRooms ): per room-category availability counts are written to the StopSale calendar -- Count=0 -> closed (Stop Sale), Count>0 -> open (Free Sale), across the inclusive date range, idempotent per room+date. Room categories map via the connection's resource_map ( InvTypeCode -> Room); an unmapped category returns an OTA warning, never a guess. Responses are OTA XML ( <Success/> or <Errors> ).

Inventory (descriptive content -> Facts) and outbound serving are planned follow-on AlpineBits actions.

Verifying connectivity

Run python manage.py connectivity_selftest for a one-command PASS/FAIL report over every wired option (adapter registration, generic import/export, each PMS webhook parse, AlpineBits parse + handshake). It uses built-in sample payloads, writes nothing, and needs no external accounts -- the quick check before the per-connector live demo-data testing.

Integrations Hub UI

Everything above is driveable from the app at Dev Tools → Connectivity . The Integrations Hub page is organised in five tabs:

  • Connections -- create/edit connections with per-provider credential and config hints. Each connection card shows an adapter badge ( Live adapter for Apaleo/Cloudbeds/Mews, AlpineBits server , or Generic import/export ) and an Endpoints dialog exposing its inbound webhook URL, generic import URL, and generic export URL with copy buttons. These URLs contain the connection's secret token; deleting and re-creating the connection rotates it.
  • Data In -- upload or paste a CSV/JSON file and run the generic availability import against a connection directly from the browser; shows the {processed, warnings} result.
  • Data Out -- download the generic availability export (CSV/JSON, date-filtered) for a connection; links to Outbound Webhooks and the Export Center.
  • E-Invoicing -- import an EN 16931 XML (UBL/XRechnung or CII/ZUGFeRD), browse the received-e-invoice inbox with a full detail dialog (parties, lines, raw XML), and download the accounting CSV.
  • AlpineBits -- the server endpoint URL, supported actions, and setup steps. When creating an AlpineBits connection, enter a plain password in the credentials; it is hashed server-side on save and never stored in plain text.

Integration Endpoints

Method Path Description
GET /api/dev/integrations/connections/ List all configured external connections.
POST /api/dev/integrations/connections/ Create a new connection.
GET/PUT/DELETE /api/dev/integrations/connections/{id}/ Retrieve, update, or delete a connection.
POST /api/dev/integrations/connections/{id}/sync/ Trigger a manual sync for a connection.
GET /api/dev/integrations/connections/{id}/sync-logs/ List recent sync logs for a connection.
POST /api/dev/integrations/inbound/{inbound_token}/ Inbound webhook receiver for a specific connection.

Outbound Webhooks

Register HTTPS endpoints to receive real-time notifications when data changes in Ananas GDS. Each webhook endpoint subscribes to one or more event types. Delivery is reliable by design: an event row is written in the same database transaction as the domain change (a transactional outbox), so a webhook is emitted if and only if the change actually committed -- no missed or phantom events. A background relay drains pending events roughly every 45 seconds and POSTs them to your subscribed endpoints.

Verifying a delivery

Every request carries these headers:

Header Meaning
X-Ananas-Event The event type (e.g. facts.published ).
X-Ananas-Event-Id Stable UUID for this event. Dedupe on it -- the same event id may arrive more than once (at-least-once delivery).
X-Ananas-Delivery Per-attempt delivery id.
X-Ananas-Timestamp ISO-8601 time the delivery was signed.
X-Ananas-Signature sha256=<hex> HMAC-SHA256 of the raw body using your endpoint secret. Recompute and compare to authenticate.

Supported Events

Phase 0 emits the following events. More (invoice, availability/allotment, reservation, partner) arrive as later connectivity phases ship.

Event Trigger Payload
facts.published A property's fact sheet is published or updated. property_id , property_name , published_at
stopsale.updated The availability / stop-sale calendar is saved. property_id , created , updated , deleted
rooming.updated A rooming-list entry is added or modified. rooming_list_id , entry_id , status

Delivery attempts are logged in WebhookDelivery records. A failed delivery is retried with exponential backoff (plus jitter) up to 8 attempts; after that the event is dead-lettered and can be replayed on demand. Each delivery record stores the event id, response code, response body, and attempt count.

Webhook Endpoints

Method Path Description
GET /api/dev/integrations/webhooks/ List all registered webhook endpoints.
POST /api/dev/integrations/webhooks/ Register a new webhook endpoint.
GET/PUT/DELETE /api/dev/integrations/webhooks/{id}/ Retrieve, update, or delete a webhook endpoint.
POST /api/dev/integrations/webhooks/{id}/test/ Send a test ping event to the endpoint.
GET /api/dev/integrations/webhooks/{id}/deliveries/ List recent delivery attempts for an endpoint.
POST /api/dev/integrations/webhooks/deliveries/{id}/retry/ Manually retry a failed delivery.

Data Exports

The Export Center generates bulk data files in multiple formats. An export request is created asynchronously — a Celery task processes the request and stores the resulting file.

Field Options
format json | xml_ota | octo | csv | xero | quickbooks
scope facts | photos | stopsale | invoices
status pending | processing | done | failed
filters JSON of scope-specific filters (date range, property IDs, etc.).
file File path once export is complete — download via the list endpoint.
Method Path Description
GET /api/dev/integrations/exports/ List past export requests with download links.
POST /api/dev/integrations/exports/create/ Queue a new export request. Body: {format, scope, filters} .

Usage Statistics

API usage is tracked in the APIStats model (reports app) per token per endpoint per day. The statistics dashboard provides per-token breakdowns with ApexCharts visualisations: total hits, successful hits, errors, bytes transferred, and active tokens over time. A pre-aggregated APIStatsSummary model powers fast dashboard reads without querying the raw stats table.

Monitoring key usage

Use the statistics dashboard to spot unusual spikes or errors per API key — an error rate above 5% usually indicates a misconfigured partner integration.

AI Integration (MCP)

Ananas GDS exposes a Model Context Protocol (MCP) server at POST /mcp/ , compatible with any MCP-capable AI agent — Claude Code, Cursor, Codex, and others. The MCP server has two distinct tool groups serving different audiences.

Purpose

The MCP does not bypass the contract and token gates. Live data is still only accessible via the v1 API with a valid token and whitelisted domain. The developer tools help client IT teams reach that point quickly — understanding the API, configuring tokens, and mapping fields to their internal systems using AI.

MCP Endpoint

Detail Value
URL POST /mcp/
Protocol MCP 2025-03-26, Streamable HTTP transport
Authentication Authorization: Bearer <ananas_session_token>
Methods supported initialize , ping , tools/list , tools/call

Tool Groups

Tools are split into two groups by prefix:

Prefix Audience Purpose
ananas_* Accommodation & TO account users Query operational data — facts, stop sales, photos, contracts, partners, API stats, notifications.
ananas_dev_* Client IT teams / system integrators Integration setup assistant — configure API keys, understand field schemas, validate tokens, map fields to internal systems.

Developer Integration Tools ( ananas_dev_* )

These tools guide a developer through the complete process of connecting an external system (PMS, CMS, reservation engine) to the Ananas GDS v1 API.

Tool Description
ananas_dev_setup_guide Step-by-step integration guide tailored to account type and use case ( pms , website , cms , brochure ). The recommended starting point.
ananas_dev_api_reference Full endpoint documentation: URLs, scopes, query params, response envelope, error codes, rate limits.
ananas_dev_list_api_keys Lists all API tokens with their scopes and linked whitelist domains.
ananas_dev_list_whitelists Lists all whitelisted IP/domain entries. Required before creating an API key.
ananas_dev_validate_token Validates a specific token: scopes enabled, whitelist domain, API setting status, last used timestamp.
ananas_dev_get_sample_response Returns real data from the account in the exact API format for a given endpoint ( facts , photos , stop_sale ). Used for AI-assisted field mapping.

AI-Assisted Field Mapping

The primary value of the MCP for external developers is automated field mapping. The flow:

  1. Developer connects their AI agent (Claude Code, Codex, etc.) to POST /mcp/ using their Ananas GDS session token.
  2. AI calls ananas_dev_setup_guide to understand prerequisites and steps.
  3. AI calls ananas_dev_get_sample_response with endpoint=facts — receives the exact JSON structure with all field names.
  4. Developer provides their internal system's field names. AI maps them: rating → cx_rating_ , zip_code → cx_zipCode_ , pets_allowed → cx_petsAllowed_ , etc.
  5. AI generates the integration code (mapping layer, HTTP client, scheduled sync) for the developer's tech stack.

This replaces hours of manual field-by-field mapping with a few minutes of AI-guided setup.

Connecting an AI Agent

Claude Code — .mcp.json
{
  "mcpServers": {
    "ananas-gds": {
      "type": "http",
      "url": "https://app.ananas-gds.com/mcp/",
      "headers": {
        "Authorization": "Bearer YOUR_ANANAS_SESSION_TOKEN"
      }
    }
  }
}

Once connected, your AI agent can call ananas_dev_setup_guide to begin the integration workflow.

Sub-User Access

Sub-users require developer_tools = True in UserPermission to access any Developer Tools endpoint. Without this flag all dev tools API calls return HTTP 403 .