# @hermanl02/hostex-api

TypeScript client library for [Hostex API v3](https://docs.hostex.io/reference/api-overview) - Property management, reservations, and channel integrations.

## Features

- **Type-safe** - Full TypeScript support with types generated from OpenAPI specification
- **Complete API coverage** - All Hostex API v3 endpoints
- **Automatic retry** - Configurable retry logic with exponential backoff
- **Error handling** - Detailed error types and validation
- **Easy to use** - Clean, intuitive API

## Installation

```bash
npm install @hermanl02/hostex-api
```

## Quick Start

```typescript
import { HostexApiClient } from '@hermanl02/hostex-api';

// Initialize the client
const client = new HostexApiClient({
  accessToken: 'your-hostex-access-token',
});

// Query properties
const properties = await client.getProperties({ limit: 10 });
console.log(properties);

// Query reservations
const reservations = await client.getReservations({
  start_check_in_date: '2025-01-01',
  end_check_in_date: '2025-01-31',
});
console.log(reservations);
```

## Configuration

```typescript
const client = new HostexApiClient({
  // Required: Your Hostex access token
  accessToken: 'your-access-token',

  // Optional: API base URL (default: https://api.hostex.io/v3)
  baseUrl: 'https://api.hostex.io/v3',

  // Optional: Request timeout in milliseconds (default: 30000)
  timeout: 30000,

  // Optional: Retry configuration
  retry: {
    maxAttempts: 3,
    initialDelay: 1000,
    maxDelay: 10000,
    backoffMultiplier: 2,
  },
});
```

## API Reference

### Properties

```typescript
// Get properties with pagination
const properties = await client.getProperties({
  offset: 0,
  limit: 20,
  id: 123, // Optional: filter by property ID
});

// Get all properties (handles pagination automatically)
const allProperties = await client.getAllProperties();

// Create a property
const { data } = await client.createProperty({ title: 'Sea View Loft' });
```

### Room Types

```typescript
// Get room types with pagination
const roomTypes = await client.getRoomTypes({
  offset: 0,
  limit: 20,
});

// Get all room types
const allRoomTypes = await client.getAllRoomTypes();

// Create a room type
const { data } = await client.createRoomType({ title: 'Deluxe King', property_ids: [1, 2] });
```

### Reservations

```typescript
// Query reservations
const reservations = await client.getReservations({
  property_id: 123,
  status: 'accepted',
  start_check_in_date: '2025-01-01',
  end_check_in_date: '2025-01-31',
  offset: 0,
  limit: 20,
});

// Create a reservation (direct booking)
await client.createReservation({
  property_id: '123',
  custom_channel_id: 456,
  check_in_date: '2025-01-15',
  check_out_date: '2025-01-20',
  guest_name: 'John Doe',
  email: 'john@example.com',
  currency: 'USD',
  rate_amount: 500,
  commission_amount: 50,
  received_amount: 500,
  income_method_id: 1,
});

// Cancel a reservation
await client.cancelReservation('reservation-code');

// Update reservation basic info (targets the stay)
await client.updateReservationBasicInfo({
  stay_code: 'stay-code',
  rate_amount: 0,
  commission_amount: 0,
});

// Update check-in details
await client.updateCheckInDetails({
  stay_code: 'stay-code',
  lock_code: '1234',
  arrival_at: { hour: 14, minute: 0 },
  departure_at: { hour: 11, minute: 0 },
  deposit: 100,
});

// Update stay status
await client.updateStayStatus({
  stay_code: 'stay-code',
  stay_status: 'in_house',
});

// Add/remove tags
await client.addTag({ stay_code: 'stay-code', tag_name: 'VIP' });
await client.removeTag({ stay_code: 'stay-code', tag_name: 'VIP' });

// Move to reservation box
await client.moveToBox({ stay_code: 'stay-code' });

// Allocate to property
await client.allocateToProperty({
  stay_code: 'stay-code',
  property_id: 123,
});

// Custom fields
const customFields = await client.getCustomFields('stay-code');
await client.updateCustomFields({
  stay_code: 'stay-code',
  custom_fields: {
    online_checkin_url: 'https://...',
    special_note: 'VIP guest',
  },
});

// Get custom channels and income methods
const customChannels = await client.getCustomChannels();
const incomeMethods = await client.getIncomeMethods();
```

### Availability

```typescript
// Query availability
const availability = await client.getAvailabilities({
  property_ids: [123, 456],
  start_date: '2025-01-01',
  end_date: '2025-01-31',
});

// Update availability
await client.updateAvailabilities({
  property_ids: [123, 456],
  start_date: '2025-01-15',
  end_date: '2025-01-20',
  available: false,
});

// Or update specific dates
await client.updateAvailabilities({
  property_ids: [123],
  dates: ['2025-01-15', '2025-01-16'],
  available: false,
});
```

### Listing Calendar

```typescript
// Query listing calendars
const calendars = await client.getListingCalendars({
  start_date: '2025-01-01',
  end_date: '2025-01-31',
  listings: [
    { channel_type: 'airbnb', listing_id: '12345' },
    { channel_type: 'booking.com', listing_id: '67890-1' },
  ],
});

// Update listing inventories
await client.updateListingInventories({
  channel_type: 'airbnb',
  listing_id: '12345',
  inventories: [
    { start_date: '2025-01-15', end_date: '2025-01-20', inventory: 5 },
  ],
});

// Update listing prices
await client.updateListingPrices({
  channel_type: 'airbnb',
  listing_id: '12345',
  prices: [
    { start_date: '2025-01-15', end_date: '2025-01-20', price: 150 },
  ],
});

// Update listing restrictions
await client.updateListingRestrictions({
  channel_type: 'booking.com',
  listing_id: '67890-1',
  restrictions: [
    {
      start_date: '2025-01-15',
      end_date: '2025-01-20',
      min_stay_on_arrival: 3,
      closed_on_arrival: false,
    },
  ],
});
```

### Messages

```typescript
// Query conversations
const conversations = await client.getConversations({
  offset: 0,
  limit: 20,
});

// Get conversation details
const details = await client.getConversationDetails('conversation-id');

// Send a message
await client.sendMessage({
  conversation_id: 'conversation-id',
  message: 'Hello! How can I help you?',
});

// Send an image
await client.sendMessage({
  conversation_id: 'conversation-id',
  jpeg_base64: 'base64-encoded-image...',
});
```

### Reviews

```typescript
// Query reviews
const reviews = await client.getReviews({
  property_id: 123,
  review_status: 'reviewed',
  start_check_out_date: '2025-01-01',
  end_check_out_date: '2025-01-31',
});

// Create a review
await client.createReview({
  reservation_code: 'reservation-code',
  host_review_score: 5,
  host_review_content: 'Great guest!',
});

// Reply to guest review
await client.createReview({
  reservation_code: 'reservation-code',
  host_reply_content: 'Thank you for your review!',
});
```

### Channel Accounts & Listings

```typescript
// List connected OTA channel accounts
const accounts = await client.getChannelAccounts({ channel_type: 'airbnb' });

// List synced channel listings
const listings = await client.getListings({ channel_account_id: 5 });
```

### Price & Rules

```typescript
// Update Airbnb listing price and rules
await client.updateAirbnbPriceAndRules({
  listing_id: 'airbnb-listing-id',
  settings: { base_price: 120, minimum_stay: 2 },
});

// Update Vrbo listing price and rules
await client.updateVrboPriceAndRules({
  listing_id: 'vrbo-listing-id',
  settings: { base_price: 110 },
});
```

### Pricing Ratios

```typescript
// Read per-listing pricing ratios (exactly one of property_id / room_type_id)
const ratios = await client.getPricingRatios({ property_id: 123 });
```

### Calendar Share Links

```typescript
const links = await client.getCalendarShareLinks();
await client.createCalendarShareLink({ scope: 'partial', property_ids: [1, 2] });
await client.deleteCalendarShareLink(9);
```

### Conversation Note

```typescript
// Set or clear the host-side private note on a conversation
await client.updateConversationNote({ conversation_id: 'c1', note: 'VIP guest' });
```

### Groups

```typescript
const groups = await client.getGroups();
await client.createGroup({ name: 'Downtown', property_ids: [1, 2] });
await client.updateGroup({ id: 3, name: 'Uptown' });
await client.deleteGroup(3);
```

### Tags (properties / room types)

```typescript
const tags = await client.getTags();
await client.createTag({ name: 'Beach', property_ids: [1] });
await client.updateTag({ id: 4, color: '#1BBFA4' });
await client.deleteTag(4);
```

### Reservation Tags

```typescript
const reservationTags = await client.getReservationTags({ keyword: 'vip' });
await client.createReservationTag({ tag_name: 'VIP' });
await client.deleteReservationTag(3);
```

### Staffs

```typescript
const staffs = await client.getStaffs({ is_active: 1 });
await client.createStaff({ name: 'Ana', mobile: '+1 5551234' });
await client.updateStaff({ id: 5, is_active: false });
await client.deleteStaff(5);
```

### Tasks

```typescript
const tasks = await client.getTasks({ status: 'pending' });
await client.createTask({ type: 'cleaning', expected_date: '2026-06-12', currency: 'USD' });
await client.updateTask({ id: 8, status: 'completed' });
await client.deleteTask(8);
```

### Incomes & Expenses

```typescript
// Dictionaries
const incomeItems = await client.getIncomeItems();
const expenseItems = await client.getExpenseItems();
const expenseMethods = await client.getExpenseMethods();

// Transactions
const transactions = await client.getTransactions({ direction: 'income' });
await client.createTransaction({
  direction: 'income',
  amount: 50,
  item_id: 1,
  payment_method_id: 2,
  stay_code: 'stay-code',
});
await client.updateTransaction({ id: 11, amount: 75 });
await client.deleteTransaction(11);
```

### Knowledge Bases

```typescript
const kbs = await client.getKnowledgeBases();
const detail = await client.getKnowledgeBaseDetail(3);
await client.createKnowledgeBase({
  scope_property: { type: 'all', ids: [-1] },
  scope_channel: { type: 'all', channels: ['unlimited'] },
  contents: [{ sub_title: 'WiFi', text: 'SSID: foo / password: bar' }],
  is_enable: true,
});
await client.deleteKnowledgeBase(3);
```

### Automation Actions

```typescript
// Upcoming scheduled automations (type is required: 'message' | 'review')
const actions = await client.getAutomationActions({ type: 'message' });
await client.executeAutomationAction(12); // run now
await client.deleteAutomationAction(12);  // cancel
```

### Webhooks

```typescript
// Get all webhooks
const webhooks = await client.getWebhooks();

// Create a webhook
await client.createWebhook({
  url: 'https://your-server.com/webhook',
});

// Delete a webhook
await client.deleteWebhook({ id: '123' });
```

### OAuth

```typescript
// Obtain token with authorization code
const tokenData = await client.obtainToken({
  client_id: 'your-client-id',
  client_secret: 'your-client-secret',
  grant_type: 'authorization_code',
  code: 'authorization-code',
});

// Refresh token
const newTokenData = await client.obtainToken({
  client_id: 'your-client-id',
  client_secret: 'your-client-secret',
  grant_type: 'refresh_token',
  refresh_token: 'your-refresh-token',
});

// Revoke token
await client.revokeToken({
  client_id: 'your-client-id',
  client_secret: 'your-client-secret',
  token: 'token-to-revoke',
});
```

## Error Handling

```typescript
import { HostexApiClient, HostexApiError } from '@hermanl02/hostex-api';

try {
  const properties = await client.getProperties();
} catch (error) {
  if (error instanceof HostexApiError) {
    console.error('Hostex API Error:', {
      message: error.message,
      errorCode: error.errorCode,
      statusCode: error.statusCode,
      requestId: error.requestId,
    });

    // Check error type
    if (error.isAuthError()) {
      console.error('Authentication failed');
    } else if (error.isRateLimitError()) {
      console.error('Rate limit exceeded');
    } else if (error.isNetworkError()) {
      console.error('Network error');
    }
  } else {
    console.error('Unknown error:', error);
  }
}
```

## TypeScript Support

All API methods and data structures are fully typed:

```typescript
import type {
  Property,
  Reservation,
  RoomType,
  ReservationStatus,
  ChannelType,
  // ... and many more
} from '@hermanl02/hostex-api';
```

## Supported Channels

The package supports all Hostex channels:
- Airbnb
- Booking.com
- Agoda
- Expedia
- VRBO
- Trip.com
- Booking Site
- Houfy
- And more...

## API Documentation

For detailed API documentation, visit the [Hostex API Documentation](https://docs.hostex.io/reference/api-overview).

## License

ISC

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.
