Skip to content

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 modifiedSince for 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=false for listing pages, then fetch full content on detail view
  • Track hasMore to 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:

  1. Initial full sync: Paginate through all content with size=100
  2. Store timestamps: Record lastModified for each item and the sync timestamp
  3. Subsequent syncs: Use modifiedSince with your last sync timestamp
  4. Detect changes: Compare lastModified values 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"])

FlyFruition Admin Documentation