AI Agents · Airflow

Apache Airflow for ERP Workflows

Apache Airflow for ERP Workflows is the work that turns a collection of business systems into a coherent operation.

John Kihiu12 min read

ERP workflows are a good match for Airflow's execution model: they're batch-shaped, dependency-heavy, and need a retry history you can actually audit. Pulling sales orders, posting AP batches, or reconciling inventory between an ERP and a warehouse are all DAGs in the literal sense — a set of tasks with dependencies, not a stream. The failure modes that matter are different from a typical analytics DAG, though, because ERP APIs are slower, more rate-limited, and less tolerant of duplicate writes.

Modeling ERP syncs as DAGs

The natural unit of work is one DAG per entity type — orders, invoices, inventory adjustments — rather than one giant DAG that walks the whole ERP schema. Each entity sync typically breaks into extract (pull changed records since the last watermark), stage (land raw JSON or XML, unmodified, somewhere durable), and load (upsert into the warehouse or push transformed records back into the ERP). Keeping extract and load as separate tasks, rather than one fused step, means a transform bug doesn't force you to re-hit the ERP API to retry it.

Watermark on the ERP's timestamp, not your own clock

Use the ERP record's LastModifiedDateTime (or equivalent) as the incremental watermark, not the time your DAG ran. If a task retries or runs late, a self-generated watermark silently skips records that changed during the gap. Store the watermark as XCom or in a small control table keyed by entity, and only advance it after a successful load.

Idempotency against ERP write APIs

Most ERP integration failures aren't extraction failures, they're double-posting failures from ambiguous retries. If a task pushes a batch of AP bills and times out waiting for a response, you don't know if the ERP received and processed it or not. Design the write task around an idempotency key — many ERP REST endpoints (Acumatica's contract-based API included) accept a client-supplied reference number that lets a retry either update the existing record or safely no-op instead of creating a duplicate.

PYTHON · IDEMPOTENT WRITE TASK
from airflow.sdk import task
import hashlib

@task(retries=3, retry_delay=60)
def post_ap_batch(batch: dict):
    # deterministic key from batch contents, not a random uuid,
    # so a retried task reuses the same key as the first attempt
    idem_key = hashlib.sha256(
        f"{batch['vendor_id']}:{batch['period']}".encode()
    ).hexdigest()[:16]

    resp = erp_client.post(
        "/entity/Default/23.200.001/Bill",
        json=batch,
        headers={"Idempotency-Key": idem_key},
    )
    resp.raise_for_status()
    return resp.json()["id"]

Rate limits and connection pooling

ERP APIs commonly cap concurrent sessions far lower than a typical SaaS API — Acumatica's default license, for instance, limits concurrent API sessions per endpoint. Airflow's pools exist for exactly this: create a pool sized to the ERP's actual concurrency limit and assign every task that hits that ERP instance to it, so parallel task instances queue instead of tripping the ERP's own throttling or session limits.

Don't let backfills bypass the pool

A manually triggered backfill DAG run still respects pool slots, but if you spin up a second DAG that hits the same ERP endpoint outside the pool, you'll blow the concurrency limit anyway. Route every task against a given ERP connection through the same named pool, regardless of which DAG it lives in.

Sensors versus polling for ERP batch windows

Some ERP processes — period close, batch posting jobs — run on the ERP's own schedule, not Airflow's. Rather than polling with a classic Sensor (which occupies a worker slot for the whole wait), use a deferrable sensor so the task suspends and frees the worker while it waits, or move to asset-based triggering if the ERP can push a webhook when the batch closes. Airflow 2.4+ handles both patterns natively; picking the wrong one is the usual reason a small Airflow deployment runs out of worker slots during a nightly ERP window.

ERP integration concernAirflow mechanism
Incremental extractionWatermark stored per entity, advanced only on success
Duplicate writes on retryIdempotency key derived from record content
ERP session/concurrency limitsNamed pool sized to the ERP's actual limit
Waiting on ERP batch jobsDeferrable sensor or asset-triggered DAG

Wrapping up

Airflow's value for ERP workflows is less about scheduling — cron could do that — and more about the retry, backfill, and observability semantics that come for free with a DAG run history. Model each entity as its own DAG, treat idempotency as a first-class design constraint rather than an afterthought, and size pools to the ERP's real limits, not Airflow's defaults.

John Kihiu
Acumatica ERP Developer · Laravel Engineer

Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.