README.md

# CampaignFlow Client

An Elixir client library for the [Campaign Flow API](https://campaignflow.com.au), built with the [Req](https://hexdocs.pm/req) HTTP client.

## ⚠️ AI Generated! ⚠️
Full disclosure, this repo was almost completely generated by Claude Code. Use at your own risk.

## Features

- OAuth2 client credentials authentication
- Automatic token management and refresh
- Full API coverage for all Campaign Flow endpoints
- Type-safe function signatures with `@spec`
- Comprehensive error handling
- Support for both production and test environments
- Configurable via application config or runtime options

## Installation

Add `campaign_flow` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:campaign_flow, "~> 0.1.0"}
  ]
end
```

## Configuration

### Application Config

Configure the client in your `config/config.exs`:

```elixir
config :campaign_flow,
  client_id: System.get_env("CAMPAIGNFLOW_CLIENT_ID"),
  client_secret: System.get_env("CAMPAIGNFLOW_CLIENT_SECRET")
```

### Environment Selection

Use the `environment` option to select between production and test APIs:

```elixir
# Production (default) - uses https://app.campaignflow.com.au/api/v2
config :campaign_flow,
  environment: :prod,
  client_id: "your_client_id",
  client_secret: "your_client_secret"

# Test - uses https://test.campaignflow.com.au/api/v2
config :campaign_flow,
  environment: :test,
  client_id: "your_client_id",
  client_secret: "your_client_secret"
```

You can also override with a custom `base_url` if needed:

```elixir
config :campaign_flow,
  base_url: "https://custom.example.com/api/v2",
  client_id: "your_client_id",
  client_secret: "your_client_secret"
```

### Runtime Config

For production, use `config/runtime.exs`:

```elixir
import Config

if config_env() == :prod do
  config :campaign_flow,
    environment: :prod,
    client_id: System.get_env("CAMPAIGNFLOW_CLIENT_ID"),
    client_secret: System.get_env("CAMPAIGNFLOW_CLIENT_SECRET")
end
```

### Environment Variables

Set the following environment variables:

```bash
export CAMPAIGNFLOW_CLIENT_ID="your_client_id"
export CAMPAIGNFLOW_CLIENT_SECRET="your_client_secret"
```

## Usage

### Creating a Client

```elixir
# Using application config (environment determined by config)
client = CampaignFlow.Client.new()

# Pass credentials directly (defaults to production)
client = CampaignFlow.Client.new(
  client_id: "your_client_id",
  client_secret: "your_client_secret"
)

# Specify test environment
client = CampaignFlow.Client.new(
  client_id: "your_client_id",
  client_secret: "your_client_secret",
  environment: :test
)

# Or use the convenience function for test environment
client = CampaignFlow.Client.test(
  client_id: "your_client_id",
  client_secret: "your_client_secret"
)
```

### Campaigns

```elixir
# List campaigns
{{:ok, campaigns}, client} = CampaignFlow.Client.Campaigns.list(client)

# Get a specific campaign
{{:ok, campaign}, client} = CampaignFlow.Client.Campaigns.get(client, 123)

# Create a new campaign
{{:ok, campaign}, client} = CampaignFlow.Client.Campaigns.create(client, %{
  name: "Summer Campaign 2024",
  agency_id: 1,
  property_id: 2
})

# Update a campaign
{{:ok, campaign}, client} = CampaignFlow.Client.Campaigns.update(client, 123, %{
  name: "Updated Campaign Name"
})

# Add a comment to a campaign
{{:ok, response}, client} = CampaignFlow.Client.Campaigns.add_comment(client, 123, %{
  comment: "Campaign approved by client"
})

# Set campaign status
{{:ok, response}, client} = CampaignFlow.Client.Campaigns.set_status(client, 123, %{
  status: "approved"
})

# Send approved email
{{:ok, response}, client} = CampaignFlow.Client.Campaigns.send_approved_email(client, 123)
```

### Campaign Vendors

```elixir
# List campaign vendors
{{:ok, vendors}, client} = CampaignFlow.Client.Campaigns.list_vendors(client, 123)

# Get a specific vendor
{{:ok, vendor}, client} = CampaignFlow.Client.Campaigns.get_vendor(client, 123, 456)

# Add a vendor to a campaign
{{:ok, vendor}, client} = CampaignFlow.Client.Campaigns.add_vendor(client, 123, %{
  vendor_id: 456
})

# Update a campaign vendor
{{:ok, vendor}, client} = CampaignFlow.Client.Campaigns.update_vendor(client, 123, 456, %{
  status: "approved"
})

# Remove a vendor from a campaign
{{:ok, response}, client} = CampaignFlow.Client.Campaigns.remove_vendor(client, 123, 456)

# Send campaign to vendor
{{:ok, response}, client} = CampaignFlow.Client.Campaigns.send_campaign_to_vendor(client, 123, 456)

# Verify vendor contact
{{:ok, response}, client} = CampaignFlow.Client.Campaigns.verify_vendor_contact(client, 123, 456)
```

### Agencies

```elixir
# List agencies
{{:ok, agencies}, client} = CampaignFlow.Client.Agencies.list(client)

# Get a specific agency
{{:ok, agency}, client} = CampaignFlow.Client.Agencies.get(client, 123)
```

### Invoices

```elixir
# List invoices
{{:ok, invoices}, client} = CampaignFlow.Client.Invoices.list(client)

# Get a specific invoice
{{:ok, invoice}, client} = CampaignFlow.Client.Invoices.get(client, 123)

# Create an invoice
{{:ok, invoice}, client} = CampaignFlow.Client.Invoices.create(client, %{
  campaign_id: 123,
  amount: 1000.00
})

# Update an invoice
{{:ok, invoice}, client} = CampaignFlow.Client.Invoices.update(client, 123, %{
  amount: 1200.00
})
```

### Campaign Budgets

```elixir
# List campaign budgets
{{:ok, budgets}, client} = CampaignFlow.Client.CampaignBudgets.list(client)

# Get a specific budget
{{:ok, budget}, client} = CampaignFlow.Client.CampaignBudgets.get(client, 123)

# Create a campaign budget
{{:ok, budget}, client} = CampaignFlow.Client.CampaignBudgets.create(client, %{
  campaign_id: 123,
  amount: 10000.00
})

# Update a campaign budget
{{:ok, budget}, client} = CampaignFlow.Client.CampaignBudgets.update(client, 123, %{
  amount: 12000.00
})

# List finance options
{{:ok, options}, client} = CampaignFlow.Client.CampaignBudgets.list_finance_options(client, 123)

# Get a finance quote
{{:ok, quote}, client} = CampaignFlow.Client.CampaignBudgets.get_finance_quote(client, 123, "OPTION_CODE")
```

### Finance Applications

```elixir
# List finance applications
{{:ok, applications}, client} = CampaignFlow.Client.FinanceApplications.list(client)

# Get a specific application
{{:ok, application}, client} = CampaignFlow.Client.FinanceApplications.get(client, 123)

# Set application status
{{:ok, response}, client} = CampaignFlow.Client.FinanceApplications.set_status(client, 123, %{
  status: "approved"
})

# Submit an application
{{:ok, response}, client} = CampaignFlow.Client.FinanceApplications.submit(client, 123)
```

### Users, Tenants, Properties

```elixir
# Users
{{:ok, users}, client} = CampaignFlow.Client.Users.list(client)
{{:ok, user}, client} = CampaignFlow.Client.Users.get(client, 123)

# Tenants
{{:ok, tenants}, client} = CampaignFlow.Client.Tenants.list(client)
{{:ok, tenant}, client} = CampaignFlow.Client.Tenants.get(client, 123)

# Properties
{{:ok, properties}, client} = CampaignFlow.Client.Properties.list(client)
{{:ok, property}, client} = CampaignFlow.Client.Properties.get(client, 123)
```

### Error Handling

The client returns tuples with `{:ok, result}` or `{:error, reason}`:

```elixir
case CampaignFlow.Client.Campaigns.get(client, 123) do
  {{:ok, campaign}, updated_client} ->
    # Process campaign
    IO.inspect(campaign)
    updated_client

  {{:error, :not_found}, client} ->
    # Handle not found
    IO.puts("Campaign not found")
    client

  {{:error, :unauthorized}, client} ->
    # Handle authentication error
    IO.puts("Authentication failed")
    client

  {{:error, {:validation_error, details}}, client} ->
    # Handle validation errors
    IO.inspect(details)
    client

  {{:error, reason}, client} ->
    # Handle other errors
    IO.inspect(reason)
    client
end
```

### Pagination

Most list endpoints support pagination:

```elixir
# Get page 2 with 50 items per page
{{:ok, campaigns}, client} = CampaignFlow.Client.Campaigns.list(client,
  page: 2,
  per_page: 50
)
```

## Authentication

The client automatically handles OAuth2 authentication using the client credentials flow:

1. When you make your first API request, the client automatically obtains an access token
2. The token is cached and reused for subsequent requests
3. When the token expires, a new one is automatically obtained

You don't need to manually manage authentication - it's handled transparently.

## Client State Management

Note that the client struct is updated with each request to maintain the current access token. You should use the updated client returned from each function call:

```elixir
# Initial client
client = CampaignFlow.Client.new(client_id: "...", client_secret: "...")

# Client is updated after each request
{{:ok, campaigns}, client} = CampaignFlow.Client.Campaigns.list(client)
{{:ok, campaign}, client} = CampaignFlow.Client.Campaigns.get(client, 123)

# Use the updated client for the next request
{{:ok, agencies}, client} = CampaignFlow.Client.Agencies.list(client)
```

## Available Resources

The following resource modules are available:

- `CampaignFlow.Client.Campaigns` - Campaign management
- `CampaignFlow.Client.Agencies` - Agency operations
- `CampaignFlow.Client.Invoices` - Invoice management
- `CampaignFlow.Client.CampaignBudgets` - Campaign budget operations
- `CampaignFlow.Client.FinanceApplications` - Finance application management
- `CampaignFlow.Client.Users` - User operations
- `CampaignFlow.Client.Tenants` - Tenant management
- `CampaignFlow.Client.Properties` - Property operations
- `CampaignFlow.Client.Referrals` - Referral management
- `CampaignFlow.Client.Signatories` - Signatory operations

## Development

```bash
# Get dependencies
mix deps.get

# Run tests
mix test

# Generate documentation
mix docs

# Format code
mix format
```

## License

MIT

## Contributing

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