Appearance
Client reference
Python SDK
python
from aiotic import AioticClient, AsyncAioticClient
client = AioticClient(base_url="https://acme.aiotic.ai", api_key="…", sync_api_key="…") # or AioticClient() from AIOTIC_* envBoth clients expose the same resources; the async one is await-able and iterators are async for.
Behaviour
| Feature | Detail |
|---|---|
| Retries | idempotent requests (GET, PUT, DELETE, and orders.upload, which always carries a request_id) on connection errors and 408 425 429 502 503 504; other POSTs (upload_raw_email, orders.retry, rejected.reprocess, erp.send, mailbox.fetch_all) only when the request provably was not processed: connection failures before sending, 408 425 429; a 502, 503 or 504 on those is surfaced, because an intermediary can answer it after the backend accepted the request; backoff 0.5 s·2ⁿ + jitter, capped 30 s; Retry-After honoured |
| Identifiers | customer numbers, item numbers, language codes, customer item numbers, file names and search text are percent-encoded into the path; an empty value, ., .., a slash or a control character raises AioticIdentifierError before any request |
| Settings | each client works on its own copy of the Settings it is given; sharing one object between clients, or changing it afterwards, affects neither |
| Rate limit | token bucket, rate_limit req/s (default 10; 0 = off); safe across threads |
| Keys | master-data paths use sync_api_key when set, everything else the integration key |
| Errors | typed exceptions with .status, .detail (string or object), .request_id; see Errors |
| Unknown fields | preserved on every model |
client.orders
| Method | Endpoint |
|---|---|
upload(files, *, request_id=None, metadata=None) → OrderUploadResponse | POST /order/upload — files: paths, (name, bytes) or (name, bytes, content_type); request_id is generated when omitted, so a request repeated after a lost response creates no second order |
upload_raw_email(eml, *, request_id=None) → OrderUploadResponse | POST /order/raw/upload — raises AioticValidationError with structured detail for non-orders |
classify_raw_email(eml) → EmailClassification | POST /order/raw/classify |
get(request_id) → OrderStatus | GET /order_status/{id} |
list(*, page=1, size=100) → OrderListResponse · iter_all(size=200, max_pages=None) | GET /order_status/list |
group(email_group_id) → OrderGroup | GET /order/group/{id} |
download_file(request_id, filename, *, preview=False) → bytes | GET /order/{id}/{filename}[/preview] |
retry(request_id) → OrderUploadResponse | POST /order/retry/{id} |
wait(request_id, *, until=LANDED_STATUSES, timeout=600, initial_interval=2, max_interval=15) → OrderStatus | polling helper |
OrderUploadResponse.request_ids returns the ids to poll (children when split).
client.erp
| Method | Endpoint |
|---|---|
send(request_id) → ErpSendResponse | POST /erp/send/{id} — raises AioticErpRejectedError(.erp_error) on success: false, AioticConflictError when not sendable |
ErpSendResponse.erp_order_number — your ERP's reference from the response body.
client.rejected
list(*, page, size, status="pending"), get(request_id), reprocess(request_id).
client.customers (sync key ok)
list(*, page, size), iter_all(size=500), search(query, *, top_k=10), get(number), upsert(number, CustomerUpsert | dict), delete(number).
client.products (sync key ok)
list(*, page, size, language_code=None), iter_all(...), get(item_number, language_code), upsert(item_number, language_code, ProductUpsert | dict), delete(item_number, language_code).
Product.description can be None in responses (the API allows products without one); ProductUpsert requires it.
client.customer_products (sync key ok)
list(*, page, size, customer_number=None, customer_item_number=None, item_number=None, language_code=None), iter_all(...), get(customer_number, customer_item_number), upsert(customer_number, customer_item_number, CustomerProductUpsert | dict), delete(...).
Misc
client.health() → HealthCheck, client.system_status() → SystemStatus, client.mailbox.fetch_all().
Exceptions
AioticError
├── AioticAuthError 401
├── AioticNotFoundError 404
├── AioticConflictError 409
├── AioticValidationError 400 / 415 / 422 (.detail may be an object)
├── AioticErpRejectedError 422 on /erp/send (.erp_error)
├── AioticUnavailableError 503
├── AioticServerError 5xx
├── AioticTransportError network, after retries
└── AioticIdentifierError a value cannot be sent as one URL path segment (raised before any request)Async example
python
import asyncio
from aiotic import AsyncAioticClient
async def main():
async with AsyncAioticClient() as client:
up = await client.orders.upload(["PO.pdf"])
status = await client.orders.wait(up.request_id)
async for c in client.customers.iter_all():
...
asyncio.run(main())Testing your code
AioticClient(transport=httpx.MockTransport(handler)) injects a fake transport; or run the mock tenant in-process as the SDK's own tests do.