Skip to main content

REST API Integration

This guide covers common integration patterns and best practices for working with the Otesse REST API.

Authentication Pattern

Every request must include authentication. We recommend creating a base client that handles this automatically:

class OtesseClient {
  private apiKey: string;
  private baseUrl: string;

  constructor(apiKey: string, baseUrl = 'https://api.otesse.com/v1') {
    this.apiKey = apiKey;
    this.baseUrl = baseUrl;
  }

  async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
    const url = `${this.baseUrl}${endpoint}`;

    const response = await fetch(url, {
      ...options,
      headers: {
        'Authorization': `Bearer ${this.apiKey}`,
        'Content-Type': 'application/json',
        ...options.headers,
      },
    });

    // Handle rate limiting
    if (response.status === 429) {
      const retryAfter = parseInt(response.headers.get('Retry-After') || '30');
      await this.sleep(retryAfter * 1000);
      return this.request<T>(endpoint, options);
    }

    if (!response.ok) {
      const error = await response.json();
      throw new OtesseError(error.error);
    }

    return response.json();
  }

  private sleep(ms: number) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  // Convenience methods
  async getCustomers(params?: Record<string, string>) {
    const query = new URLSearchParams(params).toString();
    return this.request(`/customers?${query}`);
  }

  async createBooking(data: CreateBookingPayload) {
    return this.request('/bookings', {
      method: 'POST',
      body: JSON.stringify(data),
    });
  }
}

Pagination

All list endpoints return paginated results. Iterate through all pages:

async function getAllCustomers(client: OtesseClient) {
  const allCustomers = [];
  let page = 1;
  let hasMore = true;

  while (hasMore) {
    const response = await client.getCustomers({
      page: String(page),
      per_page: '100',
    });

    allCustomers.push(...response.data);

    hasMore = page < response.pagination.total_pages;
    page++;
  }

  return allCustomers;
}

Error Handling

Build error handling into your client:

class OtesseError extends Error {
  type: string;
  code: string;
  param?: string;

  constructor(error: { type: string; code: string; message: string; param?: string }) {
    super(error.message);
    this.type = error.type;
    this.code = error.code;
    this.param = error.param;
  }

  get isRetryable(): boolean {
    return ['rate_limit_exceeded', 'server_error'].includes(this.code);
  }
}

// Usage
try {
  const booking = await client.createBooking(data);
} catch (error) {
  if (error instanceof OtesseError) {
    if (error.code === 'missing_required_field') {
      console.error(`Missing field: ${error.param}`);
    } else if (error.isRetryable) {
      // Retry with backoff
    }
  }
}

Data Synchronization

For syncing Otesse data to your system:

Initial Sync

  1. Paginate through all records from each endpoint
  2. Store in your local database
  3. Record the latest updated_at timestamp

Incremental Sync

Option A: Polling — Periodically fetch records updated since the last sync:

GET /v1/customers?updated_after=2026-02-26T00:00:00Z

Option B: Webhooks (recommended) — Set up webhook subscriptions for real-time updates. This is more efficient and provides lower latency than polling.

Common Integration Scenarios

CRM Sync

Sync customer records between Otesse and your CRM:

  • Subscribe to customer.created and customer.updated webhooks
  • Map Otesse fields to your CRM fields
  • Handle conflicts with last-write-wins or manual merge

Accounting Integration

Export financial data to your accounting software:

  • Subscribe to invoice.paid and payment.refunded webhooks
  • Map invoices to your chart of accounts
  • Reconcile payments against Stripe settlements

Scheduling Integration

Sync bookings with external calendars:

  • Subscribe to booking.* webhooks
  • Create/update/delete calendar events based on booking status
  • Handle timezone conversion between Otesse zones and calendar time zones