Developer Tools
API keys, embeddable widgets, external system integrations, webhooks, data exports, and usage analytics.
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:
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_calltool on the Ananas MCP server takestoken,endpoint(facts|photos|stop-sale|invoices|rooming), and optionalcountries/property_idsfilters.
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.
<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>
<!-- 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>
<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 (byexternal_ref) is updated. A PMS reservation never creates a rooming entry -- ledgers are tour-operator-owned; an unmatched check-in is recorded asskipped. -
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'sresource_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
passwordin 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:
-
Developer connects their AI agent (Claude Code, Codex, etc.) to
POST /mcp/using their Ananas GDS session token. -
AI calls
ananas_dev_setup_guideto understand prerequisites and steps. -
AI calls
ananas_dev_get_sample_responsewithendpoint=facts— receives the exact JSON structure with all field names. -
Developer provides their internal system's field names. AI maps them:
rating → cx_rating_,zip_code → cx_zipCode_,pets_allowed → cx_petsAllowed_, etc. - 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
{
"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
.