CampaignFlow Client
View SourceAn Elixir client library for the Campaign Flow API, built with the Req HTTP client.
⚠️ AI Generated! ⚠️
Full disclosure, this repo was almost completely generated by Claude Code. Use at your own risk.
Features
- Shared Token Manager - OAuth2 tokens managed by GenServer processes and shared across your application
- Multi-Environment Support - Use both production and test environments simultaneously
- Automatic Token Refresh - Tokens refreshed automatically with race-condition-free coordination
- Simple API - No need to thread updated client through your code
- Full API Coverage - Complete coverage for all Campaign Flow endpoints
- Type-Safe - Function signatures with
@specfor compile-time checking - Comprehensive Error Handling - Structured error types for robust error handling
Installation
Add campaign_flow to your list of dependencies in mix.exs:
def deps do
[
{:campaign_flow, "~> 2.0.0"}
]
endConfiguration
The client uses application-level configuration where credentials are configured globally and shared across your entire application. Each environment (production, test, etc.) has its own dedicated TokenManager process.
Basic Configuration
Configure environments in your config/config.exs:
config :campaign_flow,
environments: [
prod: [
base_url: "https://app.campaignflow.com.au/api/v2",
client_id: System.get_env("CAMPAIGNFLOW_PROD_CLIENT_ID"),
client_secret: System.get_env("CAMPAIGNFLOW_PROD_CLIENT_SECRET")
],
test: [
base_url: "https://test.campaignflow.com.au/api/v2",
client_id: System.get_env("CAMPAIGNFLOW_TEST_CLIENT_ID"),
client_secret: System.get_env("CAMPAIGNFLOW_TEST_CLIENT_SECRET")
]
]Runtime Configuration
For production deployments, use config/runtime.exs:
import Config
if config_env() == :prod do
config :campaign_flow,
environments: [
prod: [
base_url: "https://app.campaignflow.com.au/api/v2",
client_id: System.fetch_env!("CAMPAIGNFLOW_PROD_CLIENT_ID"),
client_secret: System.fetch_env!("CAMPAIGNFLOW_PROD_CLIENT_SECRET")
]
]
endEnvironment Variables
Set the following environment variables:
# Production credentials
export CAMPAIGNFLOW_PROD_CLIENT_ID="your_prod_client_id"
export CAMPAIGNFLOW_PROD_CLIENT_SECRET="your_prod_client_secret"
# Test environment credentials (if needed)
export CAMPAIGNFLOW_TEST_CLIENT_ID="your_test_client_id"
export CAMPAIGNFLOW_TEST_CLIENT_SECRET="your_test_client_secret"
Multiple Environments
You can configure and use multiple environments simultaneously:
# Configure both prod and test
config :campaign_flow,
environments: [
prod: [
base_url: "https://app.campaignflow.com.au/api/v2",
client_id: System.get_env("CAMPAIGNFLOW_PROD_CLIENT_ID"),
client_secret: System.get_env("CAMPAIGNFLOW_PROD_CLIENT_SECRET")
],
test: [
base_url: "https://test.campaignflow.com.au/api/v2",
client_id: System.get_env("CAMPAIGNFLOW_TEST_CLIENT_ID"),
client_secret: System.get_env("CAMPAIGNFLOW_TEST_CLIENT_SECRET")
]
]
# Use them simultaneously in your application
prod_client = CampaignFlow.Client.new(environment: :prod)
test_client = CampaignFlow.Client.new(environment: :test)
{:ok, prod_campaigns} = CampaignFlow.Client.Campaigns.list(prod_client)
{:ok, test_campaigns} = CampaignFlow.Client.Campaigns.list(test_client)Usage
Creating a Client
Clients are lightweight structs that reference a configured environment. Credentials are stored in the application config, not in the client:
# Create a client for the production environment
client = CampaignFlow.Client.new(environment: :prod)
# Create a client for the test environment
client = CampaignFlow.Client.new(environment: :test)
# Or use the convenience function for test environment
client = CampaignFlow.Client.test()
# You can also override the base URL if needed
client = CampaignFlow.Client.new(
environment: :prod,
base_url: "https://custom.example.com/api/v2"
)Campaigns
# List campaigns
{:ok, campaigns} = CampaignFlow.Client.Campaigns.list(client)
# Get a specific campaign
{:ok, campaign} = CampaignFlow.Client.Campaigns.get(client, 123)
# Create a new campaign
{:ok, campaign} = CampaignFlow.Client.Campaigns.create(client, %{
name: "Summer Campaign 2024",
agency_id: 1,
property_id: 2
})
# Update a campaign
{:ok, campaign} = CampaignFlow.Client.Campaigns.update(client, 123, %{
name: "Updated Campaign Name"
})
# Add a comment to a campaign
{:ok, response} = CampaignFlow.Client.Campaigns.add_comment(client, 123, %{
comment: "Campaign approved by client"
})
# Set campaign status
{:ok, response} = CampaignFlow.Client.Campaigns.set_status(client, 123, %{
status: "approved"
})
# Send approved email
{:ok, response} = CampaignFlow.Client.Campaigns.send_approved_email(client, 123)Campaign Vendors
# List campaign vendors
{:ok, vendors} = CampaignFlow.Client.Campaigns.list_vendors(client, 123)
# Get a specific vendor
{:ok, vendor} = CampaignFlow.Client.Campaigns.get_vendor(client, 123, 456)
# Add a vendor to a campaign
{:ok, vendor} = CampaignFlow.Client.Campaigns.add_vendor(client, 123, %{
vendor_id: 456
})
# Update a campaign vendor
{:ok, vendor} = CampaignFlow.Client.Campaigns.update_vendor(client, 123, 456, %{
status: "approved"
})
# Remove a vendor from a campaign
{:ok, response} = CampaignFlow.Client.Campaigns.remove_vendor(client, 123, 456)
# Send campaign to vendor
{:ok, response} = CampaignFlow.Client.Campaigns.send_campaign_to_vendor(client, 123, 456)
# Verify vendor contact
{:ok, response} = CampaignFlow.Client.Campaigns.verify_vendor_contact(client, 123, 456)Agencies
# List agencies
{:ok, agencies} = CampaignFlow.Client.Agencies.list(client)
# Get a specific agency
{:ok, agency} = CampaignFlow.Client.Agencies.get(client, 123)Invoices
# List invoices
{:ok, invoices} = CampaignFlow.Client.Invoices.list(client)
# Get a specific invoice
{:ok, invoice} = CampaignFlow.Client.Invoices.get(client, 123)
# Create an invoice
{:ok, invoice} = CampaignFlow.Client.Invoices.create(client, %{
campaign_id: 123,
amount: 1000.00
})
# Update an invoice
{:ok, invoice} = CampaignFlow.Client.Invoices.update(client, 123, %{
amount: 1200.00
})Campaign Budgets
# List campaign budgets
{:ok, budgets} = CampaignFlow.Client.CampaignBudgets.list(client)
# Get a specific budget
{:ok, budget} = CampaignFlow.Client.CampaignBudgets.get(client, 123)
# Create a campaign budget
{:ok, budget} = CampaignFlow.Client.CampaignBudgets.create(client, %{
campaign_id: 123,
amount: 10000.00
})
# Update a campaign budget
{:ok, budget} = CampaignFlow.Client.CampaignBudgets.update(client, 123, %{
amount: 12000.00
})
# List finance options
{:ok, options} = CampaignFlow.Client.CampaignBudgets.list_finance_options(client, 123)
# Get a finance quote
{:ok, quote} = CampaignFlow.Client.CampaignBudgets.get_finance_quote(client, 123, "OPTION_CODE")Finance Applications
# List finance applications
{:ok, applications} = CampaignFlow.Client.FinanceApplications.list(client)
# Get a specific application
{:ok, application} = CampaignFlow.Client.FinanceApplications.get(client, 123)
# Set application status
{:ok, response} = CampaignFlow.Client.FinanceApplications.set_status(client, 123, %{
status: "approved"
})
# Submit an application
{:ok, response} = CampaignFlow.Client.FinanceApplications.submit(client, 123)Users, Tenants, Properties
# Users
{:ok, users} = CampaignFlow.Client.Users.list(client)
{:ok, user} = CampaignFlow.Client.Users.get(client, 123)
# Tenants
{:ok, tenants} = CampaignFlow.Client.Tenants.list(client)
{:ok, tenant} = CampaignFlow.Client.Tenants.get(client, 123)
# Properties
{:ok, properties} = CampaignFlow.Client.Properties.list(client)
{:ok, property} = CampaignFlow.Client.Properties.get(client, 123)Error Handling
The client returns tuples with {:ok, result} or {:error, reason}:
case CampaignFlow.Client.Campaigns.get(client, 123) do
{:ok, campaign} ->
# Process campaign
IO.inspect(campaign)
{:error, :not_found} ->
# Handle not found
IO.puts("Campaign not found")
{:error, :unauthorized} ->
# Handle authentication error
IO.puts("Authentication failed")
{:error, {:validation_error, details}} ->
# Handle validation errors
IO.inspect(details)
{:error, reason} ->
# Handle other errors
IO.inspect(reason)
endPagination
Most list endpoints support pagination:
# Get page 2 with 50 items per page
{:ok, campaigns} = CampaignFlow.Client.Campaigns.list(client,
page: 2,
per_page: 50
)Authentication
The client uses a shared token manager architecture for OAuth2 authentication:
- Application Startup: When your application starts, a
TokenManagerGenServer process is started for each configured environment (:prod,:test, etc.) - Automatic Token Acquisition: When you make your first API request, the TokenManager automatically obtains an access token using the OAuth2 client credentials flow
- Token Caching: The token is cached in the TokenManager process and shared across all processes in your application
- Automatic Refresh: When a token expires, it's automatically refreshed. Only one refresh occurs even with concurrent requests
- Race-Condition Free: GenServer's message queue ensures only one token refresh happens at a time, preventing duplicate refresh requests
How It Works
# Multiple processes can safely use the same client
client = CampaignFlow.Client.new(environment: :prod)
# These calls all share the same token from TokenManager
Task.async(fn -> CampaignFlow.Client.Campaigns.list(client) end)
Task.async(fn -> CampaignFlow.Client.Agencies.list(client) end)
Task.async(fn -> CampaignFlow.Client.Users.list(client) end)
# When the token expires, only ONE refresh request is made
# All concurrent calls automatically receive the new tokenBenefits
- Shared State: One token per environment, not one per client instance
- Thread-Safe: Race-condition-free token refresh coordination
- Memory Efficient: Reduced memory usage compared to per-client tokens
- Secure: Credentials never stored in client structs, only in TokenManager processes
- Simple: No need to manually manage or refresh tokens
Available Resources
The following resource modules are available:
CampaignFlow.Client.Campaigns- Campaign managementCampaignFlow.Client.Agencies- Agency operationsCampaignFlow.Client.Invoices- Invoice managementCampaignFlow.Client.CampaignBudgets- Campaign budget operationsCampaignFlow.Client.FinanceApplications- Finance application managementCampaignFlow.Client.Users- User operationsCampaignFlow.Client.Tenants- Tenant managementCampaignFlow.Client.Properties- Property operationsCampaignFlow.Client.Referrals- Referral managementCampaignFlow.Client.Signatories- Signatory operations
Development
# 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.