HospitableClient

Elixir client library for Hospitable Public API v2.

Features

Installation

Add ex_hospitable to your list of dependencies in mix.exs:

def deps do
  [
    {:ex_hospitable, "~> 0.1.0"}
  ]
end

Configuration

Create a .env file in your project root with your Hospitable API credentials:

# Hospitable API Configuration
HOSPITABLE_ACCESS_TOKEN=your_personal_access_token_here
HOSPITABLE_BASE_URL=https://public.api.hospitable.com/v2

# Optional: Timeout settings (in milliseconds)
HOSPITABLE_TIMEOUT=30000
HOSPITABLE_RECV_TIMEOUT=30000

Usage

Setting up Authentication

# Set authentication token programmatically
HospitableClient.set_token("your_access_token")

# Check if authenticated
HospitableClient.authenticated?()
# => true

Making API Requests

# Get all properties (first page, 10 per page)
{:ok, properties} = HospitableClient.get_properties()

# Get properties with pagination
{:ok, properties} = HospitableClient.get_properties(%{
  page: 2,
  per_page: 25
})

# Get properties with included resources (API specification compliant)
{:ok, properties} = HospitableClient.get_properties(%{
  include: "user,listings,details,bookings"
})

# Get single property by UUID
{:ok, property} = HospitableClient.get_property("550e8400-e29b-41d4-a716-446655440000")

# Get single property with all includes
{:ok, property} = HospitableClient.get_property(
  "550e8400-e29b-41d4-a716-446655440000",
  %{include: "user,listings,details,bookings"}
)

# Create a new property
{:ok, property} = HospitableClient.post("/properties", %{
  "name" => "My Vacation Rental",
  "address" => "123 Beach Street"
})

# Update a property
{:ok, property} = HospitableClient.put("/properties/123", %{
  "name" => "Updated Property Name"
})

# Partially update a property
{:ok, property} = HospitableClient.patch("/properties/123", %{
  "name" => "Partially Updated"
})

# Delete a property
{:ok, _} = HospitableClient.delete("/properties/123")

Properties Module - Advanced Features

The HospitableClient.Properties module provides specialized functions for property management:

# Get all properties across all pages (handles pagination automatically)
{:ok, all_properties} = HospitableClient.Properties.get_all_properties()

# Get properties with custom pagination settings
{:ok, properties} = HospitableClient.Properties.get_all_properties(%{
  per_page: 100,        # Max page size for faster fetching
  max_pages: 10,        # Safety limit
  include: "listings"   # Include related resources
})

# Extract all unique amenities from properties
{:ok, response} = HospitableClient.get_properties()
amenities = HospitableClient.Properties.list_amenities(response)
# => ["wifi", "kitchen", "parking", "pool", ...]

# Extract property types and currencies
property_types = HospitableClient.Properties.list_property_types(response)
currencies = HospitableClient.Properties.list_currencies(response)

# Calculate distance between properties (using coordinates)
prop1 = response["data"] |> List.first()
prop2 = response["data"] |> List.last()
{:ok, distance_km} = HospitableClient.Properties.distance_between(prop1, prop2, :km)
{:ok, distance_miles} = HospitableClient.Properties.distance_between(prop1, prop2, :miles)

# Find properties near specific coordinates (10km radius around Berlin)
nearby_berlin = HospitableClient.Properties.find_nearby(response, 52.5200, 13.4050, 10, :km)

# Filter properties (client-side)
berlin_properties = HospitableClient.Properties.filter_properties(response, %{
  city: "Berlin"
})

listed_with_kitchen = HospitableClient.Properties.filter_properties(response, %{
  listed: true,
  has_amenities: ["kitchen"]
})

large_properties = HospitableClient.Properties.filter_properties(response, %{
  min_capacity: 4
})

# Advanced filtering with new options
pet_friendly_villas = HospitableClient.Properties.filter_properties(response, %{
  property_type: "villa",
  pets_allowed: true,
  min_bedrooms: 3
})

# Location-based filtering with coordinates
nearby_properties = HospitableClient.Properties.filter_properties(response, %{
  within_radius: %{lat: 52.5200, lon: 13.4050, radius: 50, unit: :km}
})

# Ultra-luxury property search
luxury_properties = HospitableClient.Properties.filter_properties(response, %{
  currency: "USD",
  has_amenities: ["pool", "gym", "concierge"],
  events_allowed: true,
  min_capacity: 8,
  within_radius: %{lat: 40.7589, lon: -73.9851, radius: 25, unit: :miles}
})

Available Filter Options

Basic Filters:

Location Filters:

Capacity Filters:

Feature Filters:

House Rules Filters:

Error Handling

The client returns structured error tuples for different types of failures:

case HospitableClient.get("/properties") do
  {:ok, data} ->
    # Success
    process_properties(data)

  {:error, {:unauthorized, error_data}} ->
    # Authentication failed
    handle_auth_error(error_data)

  {:error, {:not_found, error_data}} ->
    # Resource not found
    handle_not_found(error_data)

  {:error, {:client_error, status, error_data}} ->
    # 4xx client error
    handle_client_error(status, error_data)

  {:error, {:server_error, status, error_data}} ->
    # 5xx server error
    handle_server_error(status, error_data)

  {:error, reason} ->
    # Other errors (network, JSON parsing, etc.)
    handle_error(reason)
end

Authentication Management

The authentication state is managed by a GenServer that provides:

# Get current token
{:ok, token} = HospitableClient.get_token()

# Validate token manually
:ok = HospitableClient.Auth.Manager.validate_token()

# Clear authentication
:ok = HospitableClient.Auth.Manager.clear_auth()

API Reference

Main Module: HospitableClient

Properties Module: HospitableClient.Properties

Authentication: HospitableClient.Auth.Manager

Development

Running Tests

mix test

Code Quality

# Run Credo for code analysis
mix credo

# Run Dialyzer for type checking
mix dialyzer

Documentation

# Generate documentation
mix docs

Architecture

The library is built with the following architectural principles:

  1. Centralized Authentication: A GenServer manages all authentication state
  2. Separation of Concerns: HTTP client and authentication are separate modules
  3. Fault Tolerance: Supervisor tree ensures processes restart on failure
  4. Configuration Flexibility: Environment-based configuration with sensible defaults
  5. Error Transparency: Structured error returns for different failure modes

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests
  5. Run the test suite and code quality checks
  6. Submit a pull request

License

MIT License - see LICENSE file for details.