Appearance
Best Practices & SDKs
Caching
Cache responses to reduce API calls and improve performance:
- Cache search results for 5–15 minutes
- Cache individual content items for 1–24 hours
- Use
modifiedSincefor incremental updates instead of refetching everything
Rate Limit Handling
Implement retry logic with exponential backoff:
javascript
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
console.log(`Rate limited, waiting ${retryAfter} seconds...`);
await new Promise(r => setTimeout(r, retryAfter * 1000));
continue;
}
return response;
}
throw new Error('Max retries exceeded');
}Efficient Pagination
- Default to smaller page sizes (20–50) for faster responses
- Use
includeBody=falsefor listing pages, then fetch full content on detail view - Track
hasMoreto know when to stop paginating - For a full sync, use
size=100(the maximum) to minimize round trips
Incremental Sync Strategy
For keeping content in sync with your system:
- Initial full sync: Paginate through all content with
size=100 - Store timestamps: Record
lastModifiedfor each item and the sync timestamp - Subsequent syncs: Use
modifiedSincewith your last sync timestamp - Detect changes: Compare
lastModifiedvalues to identify updated items
bash
# Initial sync (page through everything)
curl "https://app.flyfruition.com/api/v1/partner/DEN/content?size=100&from=0" \
-H "x-partner-api-key: YOUR_KEY"
# Incremental sync (only changes since last sync)
curl "https://app.flyfruition.com/api/v1/partner/DEN/content?modifiedSince=2026-02-01T00:00:00Z&size=100" \
-H "x-partner-api-key: YOUR_KEY"Error Handling
Always handle these scenarios in your client:
- Network timeouts — use a 30-second timeout
- 401 Unauthorized — re-check your API key
- 403 Forbidden — verify airport and scope permissions
- 429 Rate Limited — implement backoff and retry
- 500 Server Error — retry with exponential backoff
TypeScript SDK Example
A complete client class you can use or adapt:
typescript
interface PartnerContentOptions {
airport: string;
apiKey: string;
baseUrl?: string;
}
class PartnerContentClient {
private baseUrl: string;
private apiKey: string;
private airport: string;
constructor(options: PartnerContentOptions) {
this.baseUrl = options.baseUrl || 'https://app.flyfruition.com';
this.apiKey = options.apiKey;
this.airport = options.airport;
}
async getContent(params: {
q?: string;
size?: number;
from?: number;
contentType?: string;
modifiedSince?: string;
includeBody?: boolean;
} = {}) {
const url = new URL(
`${this.baseUrl}/api/v1/partner/${this.airport}/content`
);
if (params.q) url.searchParams.set('q', params.q);
if (params.size) url.searchParams.set('size', String(params.size));
if (params.from) url.searchParams.set('from', String(params.from));
if (params.contentType)
url.searchParams.set('contentType', params.contentType);
if (params.modifiedSince)
url.searchParams.set('modifiedSince', params.modifiedSince);
if (params.includeBody === false)
url.searchParams.set('includeBody', 'false');
const response = await fetch(url.toString(), {
headers: { 'x-partner-api-key': this.apiKey },
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error?.message || 'Request failed');
}
return response.json();
}
async *getAllContent(params: { contentType?: string } = {}) {
let from = 0;
const size = 100;
while (true) {
const response = await this.getContent({ ...params, size, from });
for (const item of response.results) {
yield item;
}
if (!response.pagination.hasMore) break;
from += size;
}
}
}
// Usage
const client = new PartnerContentClient({
airport: 'den',
apiKey: 'flyfru_pk_live_your_key',
});
// Get a page of content
const page = await client.getContent({ size: 20 });
console.log(`Total items: ${page.pagination.total}`);
// Search for content
const results = await client.getContent({
q: 'parking',
contentType: 'faq',
size: 10,
});
// Iterate through all dining content
for await (const item of client.getAllContent({ contentType: 'dining' })) {
console.log(item.title);
}Python SDK Example
python
import requests
from typing import Iterator, Optional
from dataclasses import dataclass
from datetime import datetime
@dataclass
class PartnerContentClient:
airport: str
api_key: str
base_url: str = "https://app.flyfruition.com"
def get_content(
self,
q: Optional[str] = None,
size: int = 20,
from_offset: int = 0,
content_type: Optional[str] = None,
modified_since: Optional[datetime] = None,
include_body: bool = True,
) -> dict:
url = f"{self.base_url}/api/v1/partner/{self.airport}/content"
params = {"size": size, "from": from_offset}
if q:
params["q"] = q
if content_type:
params["contentType"] = content_type
if modified_since:
params["modifiedSince"] = modified_since.isoformat()
if not include_body:
params["includeBody"] = "false"
response = requests.get(
url,
params=params,
headers={"x-partner-api-key": self.api_key},
timeout=30,
)
response.raise_for_status()
return response.json()
def iter_all_content(self, **kwargs) -> Iterator[dict]:
from_offset = 0
size = 100
while True:
response = self.get_content(
size=size, from_offset=from_offset, **kwargs
)
for item in response["results"]:
yield item
if not response["pagination"]["hasMore"]:
break
from_offset += size
# Usage
client = PartnerContentClient(
airport="den",
api_key="flyfru_pk_live_your_key",
)
# Get a page of content
page = client.get_content(size=20)
print(f"Total items: {page['pagination']['total']}")
# Search for content
results = client.get_content(q="parking", content_type="faq", size=10)
# Iterate through all dining content
for item in client.iter_all_content(content_type="dining"):
print(item["title"])