Easy-Acumatica
Documentation

AcumaticaClient

Main client class for Acumatica REST API integration

Introduction

The AcumaticaClient handles authentication, session management, and provides dynamically-generated services and models based on your Acumatica instance's schema.

Python
from easy_acumatica import AcumaticaClient

# Initialize client (loads from .env automatically)
client = AcumaticaClient()

# Access dynamically generated services
customer = client.customers.get_by_id("ABCCOMP")
print(f"Customer: {customer.CustomerName}")

# Use dynamically generated models
new_customer = client.models.Customer(
    CustomerID="NEWCUST",
    CustomerName="New Customer"
)
created = client.customers.put_entity(new_customer)

Initialization

The client can be initialized with explicit credentials, environment variables, or automatic .env file loading.

Python
from easy_acumatica import AcumaticaClient

# Method 1: Automatic .env loading (recommended)
# Client searches for .env file in current directory and parent directories
client = AcumaticaClient()

# Method 2: Specify .env file location
client = AcumaticaClient(env_file="config/.env")

# Method 3: Explicit credentials
client = AcumaticaClient(
    base_url="https://your-instance.acumatica.com",
    username="api_user",
    password="secure_password",
    tenant="Company"
)

# Method 4: Environment variables (without .env file)
# Set: ACUMATICA_URL, ACUMATICA_USERNAME, ACUMATICA_PASSWORD, ACUMATICA_TENANT
import os
os.environ['ACUMATICA_URL'] = 'https://your-instance.acumatica.com'
os.environ['ACUMATICA_USERNAME'] = 'api_user'
# ...
client = AcumaticaClient()

# Method 5: 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"
)
client = AcumaticaClient(config=config)
ParameterTypeRequiredDescription
base_urlstr
Optional
Root URL of your Acumatica instance
usernamestr
Optional
API username
passwordstr
Optional
API password
tenantstr
Optional
Tenant/Company name
branchstr
Optional
Branch code
localestr
Optional
Locale (e.g., "en-US")
env_filestr | Path
Optional
Path to .env file (v0.5.4+)
auto_load_envbool
Optional
Auto-search for .env files (default: True, v0.5.4+)
cache_methodsbool
Optional
Enable differential caching (v0.5.4+)
cache_ttl_hoursint
Optional
Cache TTL in hours (default: 24, v0.5.4+)
cache_dirPath
Optional
Cache directory (default: ~/.easy_acumatica_cache, v0.5.4+)
force_rebuildbool
Optional
Force rebuild ignoring cache (v0.5.4+)
verify_sslbool
Optional
Verify SSL certificates (default: True)
persistent_loginbool
Optional
Maintain session (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
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)

.env File Loading

As of v0.5.4, the client automatically searches for and loads credentials from .env files. This simplifies configuration management across different environments.

Python
# .env file in your project root
ACUMATICA_URL=https://your-instance.acumatica.com
ACUMATICA_USERNAME=your-username
ACUMATICA_PASSWORD=your-password
ACUMATICA_TENANT=your-tenant
ACUMATICA_BRANCH=MAIN
ACUMATICA_CACHE_METHODS=true
ACUMATICA_CACHE_TTL_HOURS=24

# Your Python script
from easy_acumatica import AcumaticaClient

# Automatically loads from .env - no credentials needed!
client = AcumaticaClient()

# Or specify a custom .env location
client = AcumaticaClient(env_file="config/.env")

# Disable auto-loading if needed
client = AcumaticaClient(
    base_url="...",
    username="...",
    password="...",
    tenant="...",
    auto_load_env=False  # Don't search for .env files
)

Configuration Options

Optional parameters control caching, rate limiting, session behavior, and performance tuning.

Python
client = AcumaticaClient(
    base_url="https://your-instance.acumatica.com",
    username="api_user",
    password="secure_password",
    tenant="Company",

    # Caching (new in v0.5.4)
    cache_methods=True,         # Enable differential caching
    cache_ttl_hours=24,        # Cache lifetime in hours
    cache_dir=Path("~/.cache"), # Cache directory location
    force_rebuild=False,       # Force rebuild ignoring cache

    # Session behavior
    persistent_login=True,      # Maintain session (default)
    retry_on_idle_logout=True,  # Auto-retry on session timeout

    # Performance
    rate_limit_calls_per_second=10.0,  # Rate limiting
    timeout=60,                         # Request timeout (seconds)

    # SSL verification
    verify_ssl=True,

    # API endpoint
    endpoint_name="Default",
    endpoint_version=None,  # Auto-detect latest

    # Localization
    branch="MAIN",
    locale="en-US"
)

Differential Caching

Version 0.5.4 introduces differential caching, which dramatically reduces initialization time by caching generated models and services. The cache intelligently updates only changed components.

Python
from easy_acumatica import AcumaticaClient
from pathlib import Path

# Enable caching for faster subsequent initializations
client = AcumaticaClient(
    cache_methods=True,           # Enable differential caching
    cache_ttl_hours=24,          # Cache valid for 24 hours
    cache_dir=Path("~/.cache/acumatica")  # Custom cache location
)

# First run: Full schema discovery and model/service generation (~5-10 seconds)
# Subsequent runs: Differential cache loading (~0.5-1 second)

# Check cache statistics
stats = client.get_cache_stats()
print(f"Cache hits: {stats['hits']}")
print(f"Cache misses: {stats['misses']}")
print(f"Cache age: {stats['age_hours']:.1f} hours")

# Force rebuild (ignores cache)
client = AcumaticaClient(force_rebuild=True)

# Clear cache manually
client.clear_cache()

# The cache intelligently tracks changes:
# - Only rebuilds models/services that changed
# - Removes models/services that were deleted
# - Preserves unchanged components from cache
# - Automatically invalidates on schema changes

Schema Discovery

On initialization, the client fetches the OpenAPI schema and generates models and services based on your instance configuration.

Python
# The client discovers everything on initialization
client = AcumaticaClient()

# Use built-in methods to see what was discovered
print("\nAvailable Services:")
services = client.list_services()
print(f"Found {len(services)} services")
for service in services[:10]:  # Show first 10
    print(f"  - {service}")

print("\nAvailable Models:")
models = client.list_models()
print(f"Found {len(models)} models")
for model in models[:10]:  # Show first 10
    print(f"  - {model}")

# Get detailed information about a specific service
service_info = client.get_service_info('Customer')
print(f"\nCustomer service methods: {service_info['methods']}")

# Get detailed information about a specific model
model_info = client.get_model_info('Customer')
print(f"\nCustomer model fields: {model_info['field_count']} fields")

# Check for custom fields
customer_fields = model_info['fields']
custom_fields = [name for name in customer_fields if name.startswith('Usr')]
print(f"Custom fields on Customer: {custom_fields}")

Dynamic Models

Models are Python dataclasses generated from the OpenAPI schema. They include standard fields and any custom fields defined in your instance.

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

Service instances are created for each endpoint defined in your schema. They provide methods for CRUD operations, actions, and file attachments.

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.odata import QueryOptions, F

# 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_by_id(entity_id, select=None, expand=None)Retrieve a record by ID
get_list(filter=None, options=None)List records with OData filtering
put_entity(entity)Create or update a record
delete_by_id(entity_id)Delete a record by ID
put_file(entity_id, filename, data, comment=None)Attach a file to a record
get_files(entity_id)List files attached to a record
invoke_action_<action_name>(entity, parameters=None)Execute an action (dynamically generated)

OData Query Features

The client supports OData filtering and query options through the F filter factory and QueryOptions class.

Python
from easy_acumatica.odata 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

Sessions can be persistent (default) or non-persistent. The client includes automatic retry logic for idle session timeouts.

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

Task Scheduler

Version 0.5.4 adds a built-in task scheduler for running periodic jobs. Access it via client.scheduler property. See the Task Scheduler documentation for detailed information.

Python
from easy_acumatica import AcumaticaClient
import time

client = AcumaticaClient()

# Define a task function
def sync_customers():
    customers = client.customers.get_list()
    # Process customers...
    print(f"Synced {len(customers)} customers")

# Schedule task to run every 5 minutes
task_id = client.scheduler.schedule_task(
    func=sync_customers,
    interval_seconds=300,  # 5 minutes
    task_name="customer_sync"
)

# Schedule with a cron-like expression
task_id = client.scheduler.schedule_task(
    func=sync_customers,
    cron_expression="0 */6 * * *",  # Every 6 hours
    task_name="hourly_sync"
)

# List scheduled tasks
tasks = client.scheduler.list_tasks()
for task in tasks:
    print(f"{task['name']}: {task['status']}")

# Stop a specific task
client.scheduler.stop_task(task_id)

# Stop all tasks
client.scheduler.stop_all()

# Scheduler automatically stops when client is closed
client.close()

Exception Handling

The library defines specific exception types for authentication, network, timeout, and API errors.

Python
from easy_acumatica import (
    AcumaticaError,
    AcumaticaAuthError,
    AcumaticaNotFoundError,
    AcumaticaValidationError,
    AcumaticaBusinessRuleError,
    AcumaticaConcurrencyError,
    AcumaticaServerError,
    AcumaticaConnectionError,
    AcumaticaTimeoutError,
    AcumaticaRateLimitError
)

try:
    customer = client.customers.get_by_id("UNKNOWN")

except AcumaticaNotFoundError as e:
    # 404 - Resource not found
    print(f"Not found: {e}")
    print(f"Suggestions: {e.suggestions}")

except AcumaticaAuthError as e:
    # 401/403 - Authentication or authorization failed
    print(f"Auth error: {e.message}")
    print(f"Status: {e.status_code}")

except AcumaticaValidationError as e:
    # 400/422 - Data validation failed
    print(f"Validation error: {e}")
    if e.field_errors:
        for field, errors in e.field_errors.items():
            print(f"  {field}: {errors}")

except AcumaticaBusinessRuleError as e:
    # 422 - Business rule violation
    print(f"Business rule error: {e}")

except AcumaticaConcurrencyError as e:
    # 412 - Concurrent modification
    print(f"Concurrency conflict: {e}")

except AcumaticaTimeoutError as e:
    # Request timeout
    print(f"Timeout: {e.timeout_seconds}s")

except AcumaticaRateLimitError as e:
    # 429 - Rate limit exceeded
    print(f"Rate limited. Retry after: {e.retry_after}s")

except AcumaticaServerError as e:
    # 5xx - Server error
    print(f"Server error: {e.status_code}")

except AcumaticaConnectionError as e:
    # Network/connection error
    print(f"Connection error: {e}")

except AcumaticaError as e:
    # Catch-all for any Acumatica error
    print(f"Error: {e}")
    print(f"Detailed: {e.get_detailed_message()}")
    print(f"Context: {e.context}")

Advanced Features