REA Pricing Client
An Elixir client library for the REA Pricing API, built with the Req HTTP client.
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
- Type-Safe - Function signatures with
@specfor compile-time checking - HAL+JSON Support - Handles REA's HAL+JSON response format
- RFC-7807 Errors - Structured error handling compatible with REA's error format
Installation
Add rea to your list of dependencies in mix.exs:
def deps do
[
{:rea, "~> 0.1.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 :rea,
environments: [
prod: [
base_url: "https://api.realestate.com.au",
client_id: System.get_env("REA_PROD_CLIENT_ID"),
client_secret: System.get_env("REA_PROD_CLIENT_SECRET")
]
]Runtime Configuration
For production deployments, use config/runtime.exs:
import Config
if config_env() == :prod do
config :rea,
environments: [
prod: [
base_url: "https://api.realestate.com.au",
client_id: System.fetch_env!("REA_PROD_CLIENT_ID"),
client_secret: System.fetch_env!("REA_PROD_CLIENT_SECRET")
]
]
endEnvironment Variables
Set the following environment variables:
# Production credentials
export REA_PROD_CLIENT_ID="your_client_id"
export REA_PROD_CLIENT_SECRET="your_client_secret"Multiple Environments
You can configure and use multiple environments simultaneously:
config :rea,
environments: [
prod: [
base_url: "https://api.realestate.com.au",
client_id: System.get_env("REA_PROD_CLIENT_ID"),
client_secret: System.get_env("REA_PROD_CLIENT_SECRET")
],
test: [
base_url: "https://api.test.realestate.com.au",
client_id: System.get_env("REA_TEST_CLIENT_ID"),
client_secret: System.get_env("REA_TEST_CLIENT_SECRET")
]
]
# Use them simultaneously in your application
prod_client = Rea.Client.new(environment: :prod)
test_client = Rea.Client.new(environment: :test)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 = Rea.Client.new(environment: :prod)
# Create a client for the test environment
client = Rea.Client.new(environment: :test)
# Or use the convenience function for test environment
client = Rea.Client.test()
# You can also override the base URL if needed
client = Rea.Client.new(
environment: :prod,
base_url: "https://custom.example.com"
)Realestate.com.au Prices
Get product prices for properties listed on realestate.com.au:
# Get prices with required parameters
{:ok, prices} = Rea.Client.RealestateProperties.get_prices(client,
agency_id: "FEXBNP",
suburb: "Sydney",
state: "NSW",
postcode: "2000"
)
# With optional parameters
{:ok, prices} = Rea.Client.RealestateProperties.get_prices(client,
agency_id: "FEXBNP",
suburb: "Sydney",
state: "NSW",
postcode: "2000",
section: "rent", # "buy" (default), "rent", or "sold"
listing_type: "residential", # "residential" (default), "land", or "rural"
reupgrade: false, # default: false
date: "2024-06-30T10:00:00Z"
)The response includes product categories:
"all"- Products automatically purchased when listing"elect"- Optional products with minimum purchase commitments"manual"- Products that can be manually purchased"flex"- Products requiring Campaign flex points"cappedProduct"- Products with usage caps
Realcommercial.com.au Prices
Get product prices for properties listed on realcommercial.com.au:
{:ok, prices} = Rea.Client.CommercialProperties.get_prices(client,
agency_id: "FEXBNP",
suburb: "Sydney",
state: "NSW",
postcode: "2000"
)The response includes:
"subscription"- Subscription type (Diamond, Flexi, Access, Standard)"productPrice"- Price options per section (sale/lease)"productOption"- Available upgrade options-
Listing tiers:
"elite_plus","elite","enhanced","basic"
Error Handling
The client returns tuples with {:ok, result} or {:error, reason}:
case Rea.Client.RealestateProperties.get_prices(client, params) do
{:ok, prices} ->
# Process prices
IO.inspect(prices)
{:error, {:not_found, body}} ->
# Handle not found - no products available
IO.puts("No products found: #{body["title"]}")
{:error, {:unauthorized, body}} ->
# Handle authentication error
IO.puts("Authentication failed: #{body["title"]}")
{:error, {:bad_request, body}} ->
# Handle invalid parameters
IO.puts("Invalid request: #{body["detail"]}")
{:error, reason} ->
# Handle other errors
IO.inspect(reason)
end
You can use the Rea.Client.Error module for structured error handling:
case Rea.Client.RealestateProperties.get_prices(client, params) do
{:ok, prices} ->
prices
{:error, reason} ->
error = Rea.Client.Error.new(reason)
IO.puts(Rea.Client.Error.message(error))
IO.puts(Rea.Client.Error.detail(error))
endAuthentication
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 = Rea.Client.new(environment: :prod)
# These calls all share the same token from TokenManager
Task.async(fn -> Rea.Client.RealestateProperties.get_prices(client, params1) end)
Task.async(fn -> Rea.Client.RealestateProperties.get_prices(client, params2) end)
Task.async(fn -> Rea.Client.CommercialProperties.get_prices(client, params3) 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:
Rea.Client.RealestateProperties- Pricing for realestate.com.au listingsRea.Client.CommercialProperties- Pricing for realcommercial.com.au listings
Development
# Get dependencies
mix deps.get
# Run tests
mix test
# Generate documentation
mix docs
# Format code
mix formatLicense
MIT