Overview
The izi.TRAVEL Mobile SDK is available for four platforms. Each SDK exposes the same two core calls and the same model shapes, so the integration pattern is identical regardless of platform.
| Platform | Language | Distribution | Min Version |
|---|---|---|---|
| Flutter | Dart | pub.dev | Flutter 3 / Dart 3 |
| Android | Kotlin | Maven Central / JitPack | API 21 (Android 5) |
| iOS | Swift | Swift Package Manager / CocoaPods | iOS 15 / macOS 12 |
| React Native | TypeScript | npm | React Native 0.70 |
Prerequisites
- A valid API Key (
apiKey) issued by izi.TRAVEL - A valid Partner Key (
partnerKey) issued by izi.TRAVEL
401 error.How It Works
The SDK handles all authentication headers automatically on every request:
| Header | Value | Source |
|---|---|---|
X-IZI-API-KEY | Your API key | Passed at init |
HTTP-X-PARTNER-KEY | Your partner key | Passed at init |
HTTP-X-APPLICATION-ID | App bundle/package ID | Auto-detected |
HTTP-X-PARTNER-USER-ID | Per-device UUID | Auto-generated & persisted |
HTTP-X-USER-DEVICE-ID | Hardware device ID | Auto-detected |
HTTP-X-USER-AGENT | IPHONE / ANDROID | Auto-detected |
The partner user ID is generated once on first launch and stored locally. It persists across app restarts and is used for per-user charging and rate-limit tracking.
All UUIDs returned by the API are obfuscated before being handed to your app — see UUID Obfuscation.
Flutter SDK
Installation
Add the package to pubspec.yaml:
dependencies:
izi_travel: ^1.0.0
Then fetch dependencies:
flutter pub get
Android — internet permission
Add to android/app/src/main/AndroidManifest.xml inside <manifest>:
<uses-permission android:name="android.permission.INTERNET"/>
Initialization
Create a single client instance and reuse it throughout your app.
import 'package:izi_travel/izi_travel.dart';
final client = IziTravelClient(
apiKey: 'YOUR_API_KEY',
partnerKey: 'YOUR_PARTNER_KEY',
);
final client = IziTravelClient(
apiKey: 'YOUR_API_KEY',
partnerKey: 'YOUR_PARTNER_KEY',
isDebug: true, // staging API + request/response logs
);
client-private-api-stage.izi.travel) and enables console logging. Always use false (or omit it) in production.Fetch Partner Tours Not Charged
Returns all tours available to your partner account. This call is not charged.
final result = await client.getPartnerTours();
for (final tour in result.tours) {
print('${tour.title} — ${tour.coverImage}');
}
// Pagination
print('Page ${result.pagination.currentPage} of ${result.pagination.totalCount}');
print('Has more: ${result.pagination.hasMore}');
// Fetch next page
if (result.pagination.hasMore) {
final next = await client.getPartnerTours(
TourQueryOptions(page: 2, perPage: 20),
);
}
Filter by language:
final result = await client.getPartnerTours(
TourQueryOptions(
languages: ['en', 'de'],
page: 1,
perPage: 20,
),
);
Fetch Tour Detail Charged
Fetches the full content of a single object. This call is charged — one credit is consumed per unique user per day.
// tour.uuid is an opaque token — pass it as-is, do not decode it.
final detail = await client.getTourDetail(tour.uuid, languages: ['en']);
print(detail.title);
print(detail.type); // "tour", "museum", "stop", etc.
print(detail.tourDescription); // full HTML description
print(detail.city);
print(detail.country);
print(detail.location); // "lat,lng" e.g. "30.449,30.317"
print(detail.coverImage); // URL of first image
print(detail.audios); // list of audio URLs
// Children and collections
print('${detail.children.length} stops');
print('${detail.collections.length} collection sections');
Fetch Child / Collection Detail Charged
Each item in children and collections is a TourChild with its own uuid. Pass that uuid back to getTourDetail to load the full content of that stop.
final tour = await client.getTourDetail(tourUuid);
for (final stop in tour.children) {
final stopDetail = await client.getTourDetail(stop.uuid, languages: ['en']);
print('${stop.title}: ${stopDetail.tourDescription}');
}
// Same pattern for collections
for (final section in tour.collections) {
final detail = await client.getTourDetail(section.uuid, languages: ['en']);
}
Caching
Responses from getTourDetail are cached in memory for 24 hours keyed by (uuid, languages). Subsequent calls with the same arguments return instantly without hitting the API.
// Force a fresh fetch — e.g. after pull-to-refresh
client.clearCache();
Error Handling
try {
final result = await client.getPartnerTours();
} on IziCreditLimitException {
print('Credit limit reached');
} on IziAuthException {
print('Invalid API key');
} on IziPartnerInactiveException {
print('Partner account inactive');
} on IziServerException catch (e) {
print('Server error: ${e.message}');
} on IziTravelException catch (e) {
print('API error ${e.statusCode}: ${e.message}');
}
A Android SDK
Installation
Add JitPack to your root settings.gradle.kts:
dependencyResolutionManagement {
repositories {
maven { url = uri("https://jitpack.io") }
}
}
Add the dependency in your module's build.gradle.kts:
dependencies {
implementation("com.github.izi-travel:izi-android:1.0.0")
}
dependencies {
implementation("travel.izi:izi-travel-android:1.0.0")
}
Required permissions
Add to AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET"/>
Initialization
IziTravelClient requires an Android Context to persist the partner user ID and read device info.
import travel.izi.sdk.IziTravelClient
// In your Application or Activity
val client = IziTravelClient(
context = applicationContext,
apiKey = "YOUR_API_KEY",
partnerKey = "YOUR_PARTNER_KEY"
)
Development mode (staging + logs):
val client = IziTravelClient(
context = applicationContext,
apiKey = "YOUR_API_KEY",
partnerKey = "YOUR_PARTNER_KEY",
isDebug = true
)
Fetch Partner Tours Not Charged
All SDK calls are suspend functions — call them from a coroutine scope.
import travel.izi.sdk.models.TourQueryOptions
import kotlinx.coroutines.launch
viewModelScope.launch {
val result = client.getPartnerTours()
result.tours.forEach { tour ->
println("${tour.title} — ${tour.coverImage}")
}
println("Has more: ${result.pagination.hasMore}")
}
// With options
val filtered = client.getPartnerTours(
TourQueryOptions(
languages = listOf("en", "de"),
page = 1,
perPage = 20
)
)
Fetch Tour Detail Charged
viewModelScope.launch {
// tour.uuid is an opaque token — pass it as-is
val detail = client.getTourDetail(tour.uuid, languages = listOf("en"))
println(detail.title)
println(detail.tourDescription) // full HTML
println(detail.city)
println(detail.location) // "lat,lng"
println(detail.coverImage)
detail.children.forEach { stop ->
val stopDetail = client.getTourDetail(stop.uuid, languages = listOf("en"))
println("${stop.title}: ${stopDetail.tourDescription}")
}
}
Caching
Detail responses are cached in memory for 24 hours by (uuid, languages).
// Invalidate all cached entries
client.clearCache()
Error Handling
import travel.izi.sdk.exceptions.*
viewModelScope.launch {
try {
val result = client.getPartnerTours()
} catch (e: IziAuthException) {
// 401 — invalid API key
} catch (e: IziPartnerInactiveException) {
// 403 — partner inactive
} catch (e: IziCreditLimitException) {
// 429 — credit limit reached
} catch (e: IziServerException) {
// 500 — server error: e.message
} catch (e: IziTravelException) {
// other: e.statusCode, e.message
}
}
iOS SDK
Installation
In Xcode: File → Add Package Dependencies, enter the repository URL, and choose version 1.0.0.
Or in your Package.swift:
dependencies: [
.package(url: "https://github.com/izi-travel/izi-ios.git", from: "1.0.0")
],
targets: [
.target(
name: "YourApp",
dependencies: ["IziTravel"]
)
]
Add to your Podfile:
pod 'IziTravel', '~> 1.0'
Then run:
pod install
Initialization
import IziTravel
// Production
let client = IziTravelClient(
apiKey: "YOUR_API_KEY",
partnerKey: "YOUR_PARTNER_KEY"
)
Development mode (staging + logs):
let client = IziTravelClient(
apiKey: "YOUR_API_KEY",
partnerKey: "YOUR_PARTNER_KEY",
isDebug: true
)
Fetch Partner Tours Not Charged
All SDK calls use Swift async/await.
Task {
let result = try await client.getPartnerTours()
for tour in result.tours {
print("\(tour.title) — \(tour.coverImage ?? "")")
}
print("Has more: \(result.pagination.hasMore)")
}
// With options
let result = try await client.getPartnerTours(
TourQueryOptions(languages: ["en", "de"], page: 1, perPage: 20)
)
Fetch Tour Detail Charged
let detail = try await client.getTourDetail(tour.uuid, languages: ["en"])
print(detail.title)
print(detail.tourDescription) // full HTML
print(detail.city ?? "")
print(detail.location ?? "") // "lat,lng"
print(detail.coverImage ?? "")
// Children
for stop in detail.children {
let stopDetail = try await client.getTourDetail(stop.uuid, languages: ["en"])
print("\(stop.title): \(stopDetail.tourDescription)")
}
// Collections
for section in detail.collections {
let sectionDetail = try await client.getTourDetail(section.uuid)
}
Caching
Detail responses are cached in memory for 24 hours by (uuid, languages).
client.clearCache()
Error Handling
do {
let result = try await client.getPartnerTours()
} catch IziTravelError.auth {
// 401 — invalid API key
} catch IziTravelError.partnerInactive {
// 403 — partner account inactive
} catch IziTravelError.creditLimit {
// 429 — credit limit reached
} catch IziTravelError.server(let message) {
print("Server error: \(message)")
} catch IziTravelError.notFound(let uuid) {
print("Not found: \(uuid)")
} catch IziTravelError.unexpected(let code, let body) {
print("HTTP \(code): \(body)")
} catch {
print("Unexpected error: \(error)")
}
RN React Native SDK
Installation
npm install izi-travel-react-native
# or
yarn add izi-travel-react-native
Install required peer dependencies:
npm install @react-native-async-storage/async-storage
Optional — for automatic device ID detection:
npm install react-native-device-info
iOS — run pod install
cd ios && pod install
Android — internet permission
Add to android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET"/>
Initialization
import { IziTravelClient } from 'izi-travel-react-native';
// Production
const client = new IziTravelClient({
apiKey: 'YOUR_API_KEY',
partnerKey: 'YOUR_PARTNER_KEY',
});
Development mode (staging + logs):
const client = new IziTravelClient({
apiKey: 'YOUR_API_KEY',
partnerKey: 'YOUR_PARTNER_KEY',
isDebug: true,
});
Fetch Partner Tours Not Charged
const result = await client.getPartnerTours();
result.tours.forEach(tour => {
console.log(`${tour.title} — ${tour.coverImage}`);
});
console.log('Has more:', result.pagination.hasMore);
// With options
const filtered = await client.getPartnerTours({
languages: ['en', 'de'],
page: 1,
perPage: 20,
});
Fetch Tour Detail Charged
// tour.uuid is an opaque token — pass it as-is
const detail = await client.getTourDetail(tour.uuid, ['en']);
console.log(detail.title);
console.log(detail.tourDescription); // full HTML
console.log(detail.city);
console.log(detail.location); // "lat,lng"
console.log(detail.coverImage);
// Children
for (const stop of detail.children) {
const stopDetail = await client.getTourDetail(stop.uuid, ['en']);
console.log(`${stop.title}: ${stopDetail.tourDescription}`);
}
// Collections
for (const section of detail.collections) {
const sectionDetail = await client.getTourDetail(section.uuid);
}
Caching
Detail responses are cached in memory for 24 hours by (uuid, languages).
client.clearCache();
Error Handling
import {
IziAuthError,
IziPartnerInactiveError,
IziCreditLimitError,
IziServerError,
IziTravelError,
} from 'izi-travel-react-native';
try {
const result = await client.getPartnerTours();
} catch (e) {
if (e instanceof IziAuthError) {
// 401 — invalid API key
} else if (e instanceof IziPartnerInactiveError) {
// 403 — partner inactive
} else if (e instanceof IziCreditLimitError) {
// 429 — credit limit reached
} else if (e instanceof IziServerError) {
console.error('Server error:', e.message);
} else if (e instanceof IziTravelError) {
console.error(`HTTP ${e.statusCode}: ${e.message}`);
}
}
Models Reference
All SDKs expose the same logical model shape. Names are adapted to each platform's naming convention.
MTGObject
Represents any izi.TRAVEL content object returned by the detail endpoint — a tour, museum, exhibit, stop, or collection entry. The type field indicates which kind.
| Field | Type | Description |
|---|---|---|
uuid | String | Opaque token — pass to getTourDetail |
title | String | Display title |
type | String | "tour", "museum", "stop", "exhibit", etc. |
languages | String[] | Available language codes |
language | String | Language of this response |
id | Int | Internal numeric ID |
images | TourImage[] | Ordered list of images |
coverImage | String? | URL of first image (convenience getter) |
audios | String[] | Audio file URLs |
tourDescription | String | Full HTML description |
tourRatingAverage | Double? | Average rating |
city | String? | City name |
country | String? | Country name |
location | String? | Lat/lng string e.g. "30.449,30.317" |
affiliateLink | String? | Affiliate URL |
purchase | TourPurchase? | Pricing info for paid tours |
children | TourChild[] | Child stops / exhibits |
collections | TourChild[] | Collection sections |
TourChild
A child stop or collection entry within an MTGObject. Pass uuid to getTourDetail to load its full content.
| Field | Type | Description |
|---|---|---|
uuid | String | Opaque token |
title | String | Display title |
images | TourImage[] | Ordered list of images |
coverImage | String? | URL of first image |
TourImage
| Field | Type | Description |
|---|---|---|
url | String | Remote image URL |
position | Int | Display order (0-based) |
TourListResponse
| Field | Type | Description |
|---|---|---|
tours | MTGObject[] | List of tours for this page |
pagination | Pagination | Page metadata |
success | Bool | Whether the request succeeded |
Pagination
| Field | Type | Description |
|---|---|---|
currentPage | Int | Current page number (1-based) |
perPage | Int | Results per page |
totalCount | Int | Total number of tours |
hasMore | Bool | true when more pages exist |
TourPurchase
| Field | Type | Description |
|---|---|---|
currency | String | ISO 4217 currency code (e.g. "EUR") |
price | Double | Purchase price |
Error Reference
All SDKs map HTTP status codes to typed exceptions so you never need to inspect raw status codes.
| HTTP | Flutter | Android (Kotlin) | iOS (Swift) | React Native | Cause |
|---|---|---|---|---|---|
| 401 | IziAuthException |
IziAuthException |
IziTravelError.auth |
IziAuthError |
Invalid or missing API key |
| 403 | IziPartnerInactiveException |
IziPartnerInactiveException |
IziTravelError.partnerInactive |
IziPartnerInactiveError |
Partner account inactive |
| 429 | IziCreditLimitException |
IziCreditLimitException |
IziTravelError.creditLimit |
IziCreditLimitError |
Daily credit limit reached |
| 500 | IziServerException |
IziServerException |
IziTravelError.server(msg) |
IziServerError |
Server-side error |
| other | IziTravelException |
IziTravelException |
IziTravelError.unexpected(code,body) |
IziTravelError |
Unexpected response |
UUID Obfuscation
The SDK automatically obfuscates all UUIDs before returning them to your application. The uuid field on MTGObject and TourChild is an opaque token, not the raw backend UUID.
The obfuscation algorithm is identical across all four SDKs, so a token produced by the Android SDK is accepted by the iOS SDK and vice versa.
- Algorithm: XOR each UTF-8 byte with a cycling key, then base64url-encode without padding.
- This is obfuscation, not encryption — its purpose is to prevent raw backend IDs from leaking into client-side logs and analytics.
Charging
| Call | Charged? |
|---|---|
getPartnerTours | No |
getTourDetail | Yes |
For full details on pricing, billing cycles, and invoice management, please contact izi.TRAVEL.
Rate Limits
Daily Limits
| Dimension | Cap |
|---|---|
ip_address × user_id | 500 / day |
user_agent × user_id | 500 / day |
device × user_id | 500 / day |
user_id | 500 / day |
Monthly Limits (unique identifier collections)
| Collection | Limit |
|---|---|
| Unique IP addresses per partner | 5 / month |
| Unique user agents per partner | 5 / month |
When a limit is exceeded, the API returns 429 and the SDK throws the credit-limit exception for that platform.
Supported Languages
Pass any of the following ISO 639-1 codes to the languages parameter. Use "any" to return content in all available languages.