Apr 16, 2026

ERP API Design for Developers: How to Build Integrations That Don't Fall Apart

Building ERP integrations that actually hold up in production requires more than a REST endpoint. Here's what experienced developers need to think about before they start wiring things together.

ERP API Design for Developers: How to Build Integrations That Don't Fall Apart

Most ERP integrations are held together with duct tape. Not because the developers who built them were careless. But because the ERP itself made it nearly impossible to do it right.

If you’ve ever spent three days debugging why a webhook fires twice, or why a third-party sync job silently drops records whenever the ERP’s session token expires, you already know what I mean. The problem usually isn’t the integration code you wrote. It’s the API surface you were given to work with.

Why ERP API Design Is Different From Regular API Design

When you’re building a standard SaaS API, you control the data model and the surface area. You design for your use cases. You iterate.

ERP APIs are different. They expose a massive, interconnected domain model that covers sales orders, inventory movements, accounting entries, customer records, and a dozen other things that are all deeply related to each other. A single business operation like confirming a sale can touch five or six tables and trigger downstream effects in modules you didn’t even know were installed.

This creates a real problem for integration developers. You can’t just call an endpoint and assume the work is done. You need to understand what side effects that call triggers, what state the system expects before the call, and what happens when something goes wrong halfway through.

Most ERP platforms don’t make this easy. They expose a flat CRUD API that maps directly to database tables. You end up pushing raw records instead of expressing intent. And then you wonder why your inventory counts are off by a dozen units every week.

The lesson here is that good ERP API design needs to expose operations, not just data.

The Real Problem With CRUD-Only ERP APIs

A lot of ERP vendors took the path of least resistance. They wrapped their database tables in a REST layer, called it an API, and shipped it. It looks like an API. It technically works like one. But it doesn’t behave like a good one.

Here’s what goes wrong with a pure CRUD approach in an ERP context:

  • No transaction semantics. You’re writing a sales order line, then calling a separate endpoint to update inventory. These are two separate HTTP calls with no guarantee they both succeed or fail together.
  • No validation of business logic. You can write an order line with a negative quantity. You can update an accounting entry after it’s been posted. The API won’t stop you.
  • No awareness of module state. If inventory tracking isn’t enabled, the API might silently accept a stock movement and do nothing with it.
  • No events to build on. If you need to react to something happening in the ERP, you’re polling. Endlessly polling.

These aren’t edge cases. They show up constantly in real integration work. And they’re the reason ERP integration projects have such a bad reputation for going over budget and over schedule.

What Good ERP API Design Actually Looks Like

The pattern I keep coming back to is designing around intent rather than state.

Instead of exposing endpoints that say “write this record,” a well-designed ERP API exposes endpoints that say “do this thing.” Confirm a sale. Receive stock. Reconcile an invoice. The ERP handles the downstream effects. The integration developer just needs to express the right intent with the right data.

This matters for a few reasons:

It keeps business logic inside the ERP where it belongs. If every integration client has to replicate the logic for what happens when a sale is confirmed, you’ll have five different implementations with five different edge cases. Centralizing that logic in the API means changes to business rules propagate automatically.

It makes error handling predictable. When you call a “confirm sale” operation and it fails, you know nothing happened. When you’re stringing together five CRUD calls and the third one fails, you have a partial state to clean up.

It reduces coupling between the integration and the internal data model. If the ERP restructures how it stores inventory internally, an intent-based API can absorb that change without breaking your integration.

At Fullfinity, we built the API layer around FastAPI specifically because it made it much easier to express these kinds of domain operations cleanly. If you’re curious about that decision, we wrote about it in FastAPI for ERP: Why We Chose It and What We Learned Building at Scale.

Authentication and Access Control in Integrations

This is the part of ERP API design that most developers underinvest in, and they regret it later.

The typical approach is to create a service account, give it admin access, and call it done. The integration works. Everything looks fine. Then six months later someone on the team asks “wait, why does the sync job have permission to delete accounting records?”

Good ERP integration auth needs three things:

Scoped credentials

Every integration should have its own credentials scoped to exactly the resources and operations it needs. Not a shared admin token. Not a developer account that someone uses for both manual testing and production jobs.

When you scope credentials properly, you get a few benefits. You can audit what each integration is actually doing. You can revoke access for one integration without affecting others. And you know immediately when an integration is trying to do something it shouldn’t be doing.

Token lifecycle management

Session tokens expire. API keys get rotated. If your integration doesn’t handle credential refresh gracefully, you get silent failures at 3am when the token expires and nobody’s watching.

This is especially painful in ERP environments where the authentication system is often tied to the same session management that the web UI uses. You need to treat credential lifecycle as a first-class concern in your integration design, not an afterthought.

Access-aware responses

This one is subtle but important. In a well-designed ERP API, the response you get should reflect what the calling identity is actually allowed to see. Not a generic 403 when you touch something restricted. A response that’s shaped around your access level.

We put a lot of thought into this with our access-aware ORM. The idea is that the database layer itself knows what the current user can see, so queries are optimized and filtered automatically rather than returning everything and letting the application layer strip out what’s not allowed. If you want to understand why that matters for performance at scale, this post covers the ORM design in detail.

Handling Async Operations in ERP Integrations

A sale confirmation isn’t always instant. A large inventory reconciliation might take several seconds. An accounting period close might run for a minute or more.

If your API blocks on all of these, your integration clients time out. If your API returns immediately with no way to check status, your clients don’t know when the work is done or whether it succeeded.

This is where a lot of ERP APIs fall apart. They were built synchronously because that was easy, and now integrations have to work around it with polling loops and arbitrary sleep timers.

The better pattern is to expose long-running operations as async jobs with a status endpoint. The client submits the request, gets back a job ID immediately, and can poll for status or subscribe to a webhook when it’s done. This decouples the integration from the latency of the underlying operation.

Fullfinity runs on a fully async Python stack. Operations don’t block the event loop. This means the API is genuinely fast at handling concurrent requests without the threading overhead that traditional ERP backends carry around. We wrote about the performance implications of this approach in Async Python in ERP: Why Blocking I/O Is Killing Your Throughput if you want the technical reasoning.

Versioning and Stability in ERP APIs

Here’s something integration developers don’t think about enough until it burns them: ERP software changes. Modules get updated. New fields get added. Existing fields get deprecated. Business logic gets revised.

If you’re building an integration for a client, or for your own product, you need a plan for what happens when the ERP changes underneath you.

The things I’d think hard about:

Field additions are usually fine. If the API starts returning a new field you didn’t ask for, that’s generally safe to ignore. Design your integration to only consume fields it explicitly expects.

Field removals and renames are dangerous. If a field you depend on gets renamed or removed, your integration breaks silently or loudly depending on how you handle missing keys. Always validate the shape of API responses before you process them.

Behavior changes are the hardest. If a “confirm sale” operation used to trigger inventory deduction automatically and now it doesn’t, you won’t see a breaking change in the API schema. You’ll see incorrect inventory counts two weeks later.

The only real protection here is good integration tests that run against a staging environment that mirrors production. Not just testing that your code doesn’t throw errors. Testing that the ERP state after your integration runs looks like what you expect.

Building Idempotent Integrations

If your integration runs twice, what happens? If a webhook fires twice because of a retry, do you create two orders?

Idempotency is non-negotiable in production ERP integrations. Events get delivered multiple times. Network requests get retried. Sync jobs get run manually after a failure. Your integration needs to handle all of this without creating duplicate data.

The standard approach is idempotency keys. Every operation you submit to the API includes a unique key for that operation. If you submit the same key twice, the API treats it as a duplicate and returns the result of the first request without doing the work again.

On the client side, you want to use deterministic keys based on the source data. If you’re syncing an order from an eCommerce platform into the ERP, the idempotency key for that operation should be derived from the external order ID. That way, retries always produce the same key and duplicates are automatically handled.

If the ERP you’re integrating with doesn’t support idempotency keys natively, you’ll need to implement deduplication on your side. This usually means maintaining a log of operations you’ve already submitted, keyed by whatever unique identifier you have for the source event.

It’s extra work. But it’s much less work than debugging duplicate invoice records in a production accounting system.

Designing for Failure

The last thing I want to talk about is failure handling, because this is where most integrations are under-designed.

Happy path code is easy. The hard part is what happens when:

  • The ERP API returns a 500 error mid-way through a sync
  • A record fails validation because of data the integration doesn’t control
  • The network drops between your retry logic and the actual API call
  • A webhook arrives out of order

You need to design for all of these upfront, not patch them in after you’ve already had a production incident.

A few patterns that hold up well:

Dead letter queues. When an event or record fails to process, don’t drop it. Move it to a dead letter queue so you can inspect it, fix the underlying issue, and replay it.

Structured error logging. Log enough context to reconstruct what happened. The request payload, the response, the timestamp, the relevant record IDs. When something goes wrong at 2am, you want to be able to figure out what happened from the logs without needing to reproduce it.

Circuit breakers. If the ERP API is returning errors on every request, stop hammering it. Back off, alert someone, and wait for it to recover. This protects both your integration and the ERP.

Partial success handling. If you’re sending a batch of 100 records and 3 of them fail, decide in advance whether that means the whole batch fails or whether the 97 successes should go through. Both are valid approaches, but you need to make the decision explicitly and handle the partial failure case consistently.

Conclusion

Building ERP integrations that actually hold up in production is mostly about the decisions you make before you write the first line of code.

A few things worth taking away from all of this:

  1. Design around operations, not tables. An API that exposes intent is easier to integrate with and less likely to break when internal implementation details change.
  2. Treat auth and access as first-class concerns from the start. Scoped credentials, token lifecycle management, and access-aware responses aren’t optional extras. They’re what separates a production integration from a prototype.
  3. Plan for failure explicitly. Dead letter queues, idempotency keys, and circuit breakers aren’t over-engineering. They’re the things that let you sleep at night.

If you’re building on top of Fullfinity or evaluating it for a client, the platform overview gives you a sense of how the architecture supports these patterns natively. And if you’re thinking about access control design in particular, ERP Role-Based Access Control: How to Actually Design Permissions That Don’t Break When You Scale is worth reading before you start designing your integration auth layer.

Good integration design is a force multiplier. Get it right upfront and you’ll spend your time building features instead of debugging sync issues.

More articles

View all
ERP Data Seeding and Fixtures: How to Stop Rebuilding the Same Setup Every Time
13 Apr, 2026

ERP Data Seeding and Fixtures: How to Stop Rebuilding the Same Setup Every Time

If you're manually recreating ERP configurations for every client or environment, you're wasting hours you'll never get back. Here's how to think about data seeding, fixtures, and repeatable setup in a modern ERP.

Read more
ERP Role-Based Access Control: How to Actually Design Permissions That Don't Break When You Scale
11 Apr, 2026

ERP Role-Based Access Control: How to Actually Design Permissions That Don't Break When You Scale

Most ERP permission systems become a maintenance nightmare at scale. Here's how to design role-based access control that stays manageable as your user base and module count grow.

Read more
Multi-Tenant ERP Architecture: How to Actually Build It Without Painting Yourself Into a Corner
09 Apr, 2026

Multi-Tenant ERP Architecture: How to Actually Build It Without Painting Yourself Into a Corner

Building multi-tenant ERP systems is harder than it looks. Here's what actually breaks, how to structure your data isolation correctly, and what to consider before you commit to an approach.

Read more