apploi-partner-api 0.1.8
Official Python client for Apploi Partner API with high-level abstractions
Requires Python: >=3.8
Keywords: apploi, api, client, sdk, partner-api, hr, recruitment, applicant-tracking

# Apploi Partner API - Python Client

The official Python client library for the Apploi Partner API. This library provides a clean, type-safe interface for integrating with Apploi's applicant tracking system, allowing you to programmatically access and manage job applications, candidates, and related data.

## What is the Apploi Partner API?

The Apploi Partner API enables seamless integration with Apploi's platform, giving you programmatic access to:

- **Job Applications**: Retrieve, filter, and process candidate applications across your organization
- **Candidate Data**: Access detailed candidate information including contact details, application history, and status
- **Application Workflow**: Track application status changes, state transitions, and timeline data
- **Team Management**: Filter and organize data by teams and their associated jobs

## Why Use This Client Library?

While you could interact with the API directly using HTTP requests, this Python client provides significant advantages:

### 🛡️ **Type Safety & Validation**
- Strong typing with full IDE autocomplete support
- Automatic parameter validation before API calls
- Structured data models with proper Python types (`int`, `datetime`, `Enum`)

### 🚀 **Developer Experience** 
- Native Python idioms - use `date(2024, 1, 1)` instead of `"2024-01-01"`
- Intuitive method names and parameter handling
- Comprehensive error handling with descriptive messages
- Built-in pagination support

### 📦 **Production Ready**
- Automatic retry logic for transient failures
- Proper timeout handling and connection management
- Extensive test coverage and validation
- Clear documentation and examples

## Installation

```bash
pip install apploi-partner-api
```

## Quick Start

```python
from apploi_partner_api import ApploiClient
from datetime import date

# Initialize client
client = ApploiClient(api_key="your-api-key")

# Get job applications with native Python types
job_applications = client.get_job_applications(
    job_id=12345,
    updated_after=date(2024, 1, 1),
    limit=50
)

# Work with strongly-typed results
for application in job_applications:
    print(f"{application.candidate.name} - {application.status.value}")
    print(f"  Email: {application.email}")
    print(f"  Applied: {application.applied_at}")
    print(f"  Job: {application.job.name}")
```

## Key Features

### 🎯 **Simple Integration**
- Single `pip install` - no complex setup required
- Works with Python 3.7+ across all major platforms
- Minimal dependencies for easy deployment

### 🔧 **Native Python Experience**
- Use native Python types: `int`, `date`, `datetime`, `bool`
- Automatic type conversion and validation
- Pythonic method names and parameter handling
- No need to manually format dates or convert integers to strings

### 🛡️ **Robust Error Handling**
- Custom exception hierarchy for different error types
- Descriptive error messages with actionable guidance
- Automatic retry logic for transient network issues
- Proper handling of rate limits and API quotas

### 📊 **Rich Data Models**
- Structured dataclasses with full type annotations
- Nested models for complex data (Job, Candidate, Application)
- Convenient properties for common operations
- Access to both processed and raw API response data

### 🚀 **Production Ready**
- Comprehensive test coverage and validation
- Built-in pagination for large datasets
- Configurable timeouts and connection settings
- Thread-safe for concurrent usage

## API Reference

### Client Initialization

```python
from apploi_partner_api import ApploiClient

client = ApploiClient(
    api_key="your-api-key",        # Required
    base_url=None,                  # Optional, defaults to production
    timeout=60                      # Optional, request timeout in seconds
)
```

### get_job_applications()

Retrieve job applications with filtering and pagination.

```python
job_applications = client.get_job_applications(
    state="applied",                          # Filter by status
    query="Smith",                            # Search query filters by name, phone number, or email
    limit=50,                                 # Max results (1-1000)
    team_id=123,                              # Team ID (int or str)
    updated_before=datetime.now(),            # Before date
    offset=0,                                 # Pagination offset
    job_id=456,                               # Job ID (int or str)
    updated_after=date(2024, 1, 1)            # After date
)
```

**Parameters:**
- All parameters are optional
- IDs accept `int` or `str`
- Dates accept `date`, `datetime`, or ISO string
- Limit must be 1-1000
- Offset must be ≥ 0

**Returns:** `JobApplicationsList` with:
- `items`: List of `JobApplication` objects
- `total`: Total count available
- `limit`: Page size
- `offset`: Current offset
- `has_more`: Boolean for more pages

## Domain Models

### JobApplication

```python
@dataclass
class JobApplication:
    # Identifiers
    id: int                              # Application ID
    candidate_id: int                    # Candidate ID
    job_id: int                          # Job ID
    
    # Required fields
    status: ApplicationStatus            # Application status
    job: Job                             # Job details (nested)
    candidate: Candidate                 # Candidate details (nested)
    email: str                           # Applicant email
    applicant_state: str                 # Current state
    applicant_state_id: int              # State ID
    
    # DateTime/State Management
    date_current_state_assigned: Optional[datetime]
    previous_applicant_state: Optional[str]
    previous_applicant_state_id: Optional[int]
    date_previous_state_assigned: Optional[datetime]
    applicant_updated: Optional[datetime]
    date_created: Optional[datetime]
    application_source: Optional[str]
    
    # Optional fields
    first_name: Optional[str]
    last_name: Optional[str]
    phone: Optional[str]
    applied_at: Optional[datetime]
    updated_at: Optional[datetime]
    team_id: Optional[int]

    @property
    def name(self) -> str:
        """Full name (first + last)"""
```

### Job

```python
@dataclass  
class Job:
    id: Optional[int]
    name: Optional[str]               # Job title
    archived: Optional[bool]          # Is archived
    archived_date: Optional[datetime]
    address: Optional[str]            # Job location
    source: Optional[str]             # Job source
    brand_name: Optional[str]         # Company brand
    job_code: Optional[str]           # Internal job code
    creator_id: Optional[int]         # Creator ID
    team_id: Optional[int]            # Team ID
    team_name: Optional[str]          # Team name
    role_type: Optional[str]          # Role type
    longitude: Optional[float]        # Geo coordinates
    latitude: Optional[float]
```

### Candidate

```python
@dataclass
class Candidate:
    id: int                           # Candidate ID
    first_name: Optional[str]
    last_name: Optional[str]
    email: Optional[str]
    home_phone_number: Optional[str]
    mobile_phone_number: Optional[str]
    city: Optional[str]
    state: Optional[str]
    country: Optional[str]
    address: Optional[str]
    zip_code: Optional[str]

    @property
    def name(self) -> str:
        """Full name (first + last)"""

    @property 
    def phone(self) -> Optional[str]:
        """Primary phone (mobile or home)"""
```

### ApplicationStatus

```python
class ApplicationStatus(Enum):
    APPLIED = "applied"
    SCREENING = "screening"
    INTERVIEWING = "interviewing"
    REFERENCE_CHECK = "reference_check"
    OFFER_EXTENDED = "offer_extended"
    HIRED = "hired"
    REJECTED = "rejected"
    WITHDRAWN = "withdrawn"
    ON_HOLD = "on_hold"
    UNKNOWN = "unknown"  # Fallback for unrecognized values
```

**Note:** The enum supports legacy values `"interview"` and `"offer"` for backwards compatibility, automatically mapping them to `INTERVIEWING` and `OFFER_EXTENDED` respectively.

## Error Handling

```python
from apploi_partner_api import (
    ApploiValidationError,
    ApploiAuthenticationError,
    ApploiAPIError
)

try:
    job_applications = client.get_job_applications(limit=50)

except ApploiValidationError as e:
    # Invalid parameters (e.g., limit > 1000)
    print(f"Validation error: {e}")

except ApploiAuthenticationError as e:
    # 401/403 errors
    print(f"Auth failed: {e}")

except ApploiAPIError as e:
    # Other API errors
    print(f"API error: {e}")
    if e.status_code == 429:
        # Handle rate limiting
        time.sleep(60)
```

## Usage Examples

### Basic Usage

```python
# Get all job applications
all_job_applications = client.get_job_applications()
print(f"Found {len(all_job_applications)} job applications")
```

### Filtering

```python
# Filter with native types
filtered_job_applications = client.get_job_applications(
    job_id=12345,                         # Integer
    team_id=789,                          # Integer
    updated_after=date(2024, 1, 1),       # Date object
    state="applied",                      # String
    limit=50                              # Integer
)
```

### Pagination

```python
# Paginate through all results
all_results = []
offset = 0
page_size = 50

while True:
    page = client.get_job_applications(
        limit=page_size,
        offset=offset
    )

    all_results.extend(page.items)

    if not page.has_more:
        break

    offset += page_size

print(f"Total fetched: {len(all_results)}")
```

### Working with Results

```python
job_applications = client.get_job_applications(limit=1)

if job_applications:
    application = job_applications[0]

    # Strongly typed fields
    print(f"ID: {application.id}")
    print(f"Name: {application.candidate.name}")
    print(f"Status: {application.status.value}")

    # Date handling
    if application.applied_at:
        days_ago = (datetime.now() - application.applied_at).days
        print(f"Applied {days_ago} days ago")

    # Access raw data if needed
    raw = application.raw_data
```

## Requirements

- Python 3.7+
- httpx (for Fern SDK)
- python-dateutil (optional, for date parsing)