# 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.