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
- Paginate through all records from each endpoint
- Store in your local database
- Record the latest
updated_attimestamp
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.createdandcustomer.updatedwebhooks - 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.paidandpayment.refundedwebhooks - 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
On this page