Easy-Acumatica
Documentation

AcumaticaClient

The intelligent client that discovers your entire Acumatica instance

Dynamic Discovery
Type Safety
Zero Configuration

Introduction

The AcumaticaClient is your gateway to seamless Acumatica integration. It automatically discovers your endpoints, generates models, and creates type-safe services - all at runtime.

python
# The v0.4.8 way - everything is dynamic!
from easy_acumatica import AcumaticaClient

# Initialize - this discovers your entire instance
client = AcumaticaClient(
    base_url="https://your-instance.acumatica.com",
    username="your_username",
    password="your_password",
    tenant="YourTenant",
    branch="YourBranch"
)

# Models are generated and ready to use
print(f"Available models: {dir(client.models)}")

# Services are attached dynamically
print(f"Available services: {[attr for attr in dir(client) if attr.endswith('s') and not attr.startswith('_')]}")

# Use them immediately with full type safety!
customer = client.customers.get_by_id("ABCCOMP")
print(f"Customer: {customer.CustomerName}")

Initialization

Initialize your client with credentials. The client will automatically discover and configure everything else.

python
from easy_acumatica import AcumaticaClient

# Method 1: Direct parameters
client = AcumaticaClient(
    base_url="https://your-instance.acumatica.com",
    username="api_user",
    password="secure_password",
    tenant="Company",
    branch="MAIN"
)

# Method 2: Environment variables
# Set: ACUMATICA_URL, ACUMATICA_USERNAME, etc.
client = AcumaticaClient()

# Method 3: Mix of both (env vars as defaults)
client = AcumaticaClient(
    tenant="Company",  # Override specific values
    branch="BRANCH01"
)

# Method 4: Using a config object
from easy_acumatica import AcumaticaConfig

config = AcumaticaConfig(
    base_url="https://your-instance.acumatica.com",
    username="api_user",
    password="secure_password",
    tenant="Company",
    endpoint_name="Default",
    endpoint_version="23.200.001"
)
client = AcumaticaClient(config=config)
ParameterTypeRequiredDescription
base_urlstr
Optional
Root URL of your Acumatica instance (or use env var)
usernamestr
Optional
API username (or use env var)
passwordstr
Optional
API password (or use env var)
tenantstr
Optional
Tenant/Company name (or use env var)
branchstr
Optional
Branch code (optional)
localestr
Optional
Locale like "en-US" (optional)
verify_sslbool
Optional
Verify SSL certificates (default: True)
persistent_loginbool
Optional
Keep session alive (default: True)
retry_on_idle_logoutbool
Optional
Auto-retry on timeout (default: True)
endpoint_namestr
Optional
API endpoint name (default: "Default")
endpoint_versionstr
Optional
Specific API version (optional)
configAcumaticaConfig
Optional
Config object (overrides other params)
rate_limit_calls_per_secondfloat
Optional
API rate limit (default: 10.0)
timeoutint
Optional
Request timeout in seconds (default: 60)

Configuration Options

Configure advanced behaviors with optional parameters.

python
# Configure advanced behaviors
client = AcumaticaClient(
    base_url="https://your-instance.acumatica.com",
    username="api_user",
    password="secure_password",
    tenant="Company",
    
    # Session behavior
    persistent_login=True,      # Keep session alive (default)
    retry_on_idle_logout=True,  # Auto-retry on 401 (default)
    
    # Performance tuning
    rate_limit_calls_per_second=10.0,  # API throttling
    timeout=60,                        # Request timeout in seconds
    
    # SSL and security
    verify_ssl=True,           # Verify SSL certificates
    
    # API targeting
    endpoint_name="Default",   # Which endpoint to use
    endpoint_version=None,     # Auto-detect latest version
    
    # Localization
    locale="en-US"            # API locale setting
)

Dynamic Discovery

The client automatically discovers your Acumatica instance structure on initialization.

python
# The client discovers everything on initialization
client = AcumaticaClient(...)

# See what was discovered
print("\nAvailable Services:")
for attr in dir(client):
    if attr.endswith('s') and not attr.startswith('_'):
        service = getattr(client, attr)
        print(f"  - client.{attr} -> {type(service).__name__}")

print("\nAvailable Models:")
for model_name in sorted(dir(client.models)):
    if not model_name.startswith('_'):
        model = getattr(client.models, model_name)
        print(f"  - {model_name}: {len(model.__dataclass_fields__)} fields")

# Check if custom entities exist
if hasattr(client, 'custom_entity'):
    print("\nCustom entity service available!")

# Check for custom fields
customer_fields = client.models.Customer.__dataclass_fields__
custom_fields = [f for f in customer_fields if f.startswith('Usr')]
print(f"\nCustom fields on Customer: {custom_fields}")

Dynamic Models

Models are generated dynamically based on your actual Acumatica entities. They include all standard fields and your custom fields automatically.

python
# All models follow Python dataclass patterns

# Create a new customer
customer = client.models.Customer(
    CustomerID="PYTHN001",
    CustomerName="Python Customer",
    CustomerClass="DEFAULT",
    # Your custom fields are here too!
    UsrCustomField="Custom Value" if hasattr(client.models.Customer, 'UsrCustomField') else None
)

# Models support all standard operations
print(customer.CustomerID)  # PYTHN001
print(customer.__dict__)    # See all fields

# Nested models work seamlessly
order = client.models.SalesOrder(
    OrderType="SO",
    CustomerID="PYTHN001",
    Details=[
        client.models.SalesOrderDetail(
            InventoryID="WIDGET",
            Quantity=5
        )
    ]
)

# Type hints work perfectly in your IDE
# Your editor knows all fields and their types!

Dynamic Services

Services are dynamically attached to the client based on your available endpoints. Each service provides methods that correspond exactly to your API operations.

python
# Services provide intuitive CRUD operations

# GET - Retrieve by keys
customer = client.customers.get_by_id("PYTHN001")

# GET - With field selection
opts = QueryOptions(filter=F.CustomerID == "PYTHN001", select=["CustomerID","CustomerName","Balance"])
customer = client.customers.get_list(options=opts)

# LIST - Get multiple records
all_customers = client.customers.get_list()

# LIST - With OData parameters
from easy_acumatica import F, QueryOptions

# CREATE - Add new record
new_customer = client.models.Customer(
    CustomerID="PYTHN002",
    CustomerName="Another Python Customer"
)
created = client.customers.put_entity(new_customer)

# UPDATE - Modify existing
customer.CustomerName = "Updated Name"
updated = client.customers.put_entity(customer)

# DELETE - Remove record
client.customers.delete_by_id("PYTHN002")

Service Method Patterns

Method PatternDescription
get(keys, select=None, expand=None)Retrieve a single record by its keys
list(top=None, skip=None, filter=None, select=None, orderby=None, expand=None)List records with optional OData parameters
create(entity)Create a new record
update(entity)Update an existing record
delete(keys)Delete a record by its keys
get_by_id(id)Get a record by its internal ID
invoke_action(name, payload)Execute an action
attach_file(keys, filename, content)Attach a file to a record
get_files(keys)List files attached to a record
get_file(keys, file_id)Download a specific file
delete_file(keys, file_id)Delete a file attached to an entity

OData Query Features

Leverage the power of OData with our intuitive query builder and filter factory.

python
from easy_acumatica import F

# Simple equality filter
active_filter = F.Status == "Active"

# Comparison operators
high_value = F.Balance > 10000
recent = F.CreatedDate >= "2024-01-01"

# Logical operators
combined = (F.Status == "Active") & (F.Balance > 5000)
either_or = (F.Type == "Business") | (F.Type == "Corporate")

# String functions (lowercase for OData compatibility)
name_starts = F.CustomerName.tolower().startswith("acme")
contains_text = F.Description.contains("important")
name_ends = F.CustomerName.endswith("LLC")

# Date functions
this_year = F.CreatedDate.year() == 2024
this_month = F.OrderDate.month() == 3

# Navigation properties
has_email = F.MainContact.Email != None
contact_city = F.MainContact.Address.City == "New York"

# Complex nested conditions
complex_filter = (
    ((F.Status == "Active") | (F.Status == "OnHold")) &
    (F.Balance > 1000) &
    F.CustomerName.tolower().startswith("tech") &
    (F.Terms == "30D" | F.Terms == "60D")
)

# Use with list operations
results = client.customers.get_list(filter=complex_filter)

Session Management

The client handles all session complexity for you, with intelligent retry logic and automatic cleanup.

python
# Default behavior - one login, multiple operations

client = AcumaticaClient(
    base_url="https://your-instance.acumatica.com",
    username="api_user",
    password="secure_password",
    tenant="Company"
    # persistent_login=True is the default
)

# Session established once during __init__
# All operations use the same session
customers = client.customers.get_list()
orders = client.sales_orders.get_list()
invoices = client.invoices.get_list()

# Session automatically cleaned up on exit

Exception Handling

Easy-Acumatica provides specific exception types for different error scenarios.

python
from easy_acumatica import (
    AcumaticaException,
    AcumaticaAuthError,
    AcumaticaAPIError,
    AcumaticaNetworkError,
    AcumaticaTimeoutError,
    AcumaticaNotFoundError
)

try:
    # This might fail in various ways
    customer = client.customers.get_by_id("UNKNOWN")
    
except AcumaticaNotFoundError as e:
    # Record doesn't exist
    print(f"Customer not found: {e}")
    
except AcumaticaAuthError as e:
    # Authentication failed
    print(f"Auth error: {e}")
    # Maybe refresh credentials
    
except AcumaticaTimeoutError as e:
    # Request timed out
    print(f"Timeout after {e.timeout}s")
    # Maybe retry with longer timeout
    
except AcumaticaAPIError as e:
    # API returned an error
    print(f"API error {e.status_code}: {e.message}")
    print(f"Details: {e.details}")
    
except AcumaticaNetworkError as e:
    # Network-level error
    print(f"Network error: {e}")
    
except AcumaticaException as e:
    # Catch-all for any Acumatica-related error
    print(f"Acumatica error: {e}")

Advanced Features