API-first: integration is an architecture decision, not a final task
Integration is rarely planned — it shows up as a request near the end of a project, once the most important decision has already been made in silence. API-first is the opposite order: the contract exists before the implementation.
In short
- API-first means the communication contract exists before the implementation, and the user interface is just one client of that contract.
- An API bolted on at the end mirrors internal tables instead of business concepts, so every schema change breaks a client.
- Any integration that runs long enough eventually delivers the same message twice — idempotency is not a luxury but a correctness requirement.
- Choosing between polling, webhooks and event streams is a decision about cost and immediacy, not a matter of taste.
- Point-to-point integrations grow quadratically with the number of systems; a hub and a clear contract are the only way to stop it.
Integration is rarely planned. It almost always appears as a request near the end of a project — once the software already works, the screens are finished, and someone finally asks "and how do we connect this to the rest of the company". By then it is late. Not because it is technically impossible, but because the most important decision has already been made in silence: the system was built as if its only user were a person staring at a screen.
API-first is the opposite order. The contract for how you talk to the system exists before a single line of implementation is written, and the user interface is just one of that contract's clients — equal to another system, to a mobile app, to the nightly job that syncs data. This is not a backend team's working style, it is an architecture decision that determines what every future integration will cost you.
What "API-first" actually means
The difference is not whether the system has an API. Almost everything does now. The difference is what came first.
When the interface comes first, the API is added as a wrapper around what the screen already did. Such an API speaks the language of the database — it returns table rows, demands internal identifiers, exposes how the data is physically stored. When the contract comes first, the API speaks the language of the business: "create an order", "reserve stock", "issue a dispatch note". The first describes how the system works inside; the second describes what the system means from outside.
It sounds like a nuance until the first client arrives. The moment another system starts calling your API, you have published a promise. That promise — the contract — determines what the client may expect and what you may change without breaking it. If the contract is an accidental reflection of your database schema, every schema change is also a contract change, and every refactor breaks in someone else's code.
Why "we'll add an API later" always produces the wrong API
"We'll add an API later" sounds like deferring a cost. In reality it is a decision that the API will mirror internal tables instead of business concepts, because at the moment of adding it the only thing that exists is tables. The client then has to know your schema to do anything, and you cannot change it because the client depends on it. You have created a dependency in the wrong direction.
A contract-first approach turns this on its head. First you describe the contract — the paths, the message shapes, the meaning of fields — as a document both a human and a machine can read. That description is agreed before implementation. The team building the client and the team building the server work in parallel against the same paper, instead of one waiting for the other. When the implementation arrives, it fills in a contract that has already been reviewed, not the other way around.
Versioning is part of the same thinking. The contract may change, but it must not break underneath a client that already works. The rules are simple and worth writing down:
- Adding is safe. A new optional field or a new path does not touch existing clients.
- Removing and renaming is not. Every field someone reads is a promise; when you remove it, you have broken it.
- Meaning does not change in silence. If "price" suddenly includes tax when it did not before, that is a breaking change even though the message shape is unchanged.
- A breaking change gets a new version. The old version lives until the last client has migrated, not until you get tired of maintaining it.
Idempotency, retries and the message that arrives twice
Any integration that runs long enough eventually delivers the same message twice. This is not a bug to be fixed but a property of the network to be accepted. The client sends a request, the server processes it correctly, but the response is lost on the way back. The client does not know it succeeded, so it tries again. Without protection, you have just created two orders, two invoices, two shipments of the same goods.
Idempotency is the property that the same request, repeated, has the same effect as if it were sent once. It is achieved with a key the client assigns to each attempt: the server remembers what it did for that key and, on a repeated request, returns the same response instead of doing the work a second time. Without it, every retry mechanism — and one is essential, because networks fail — becomes a duplication machine.
The rule that prevents duplicate invoices: every request that changes something carries a unique key the client generates. The server remembers the outcome for that key and, on a repeat, returns the same response rather than doing new work. The retry then becomes harmless instead of dangerous.
Webhooks, polling or event streams
How does a client learn that something has changed? Three answers are in circulation, and none is universally right — each solves a different kind of problem and charges a different price.
| Approach | How it works | When it fits | Cost |
|---|---|---|---|
| Polling | The client asks "anything new" at intervals | Changes are rare, the client is simple, immediacy does not matter | Empty queries waste resources; latency equals the polling interval |
| Webhook | The server calls the client as soon as something happens | Events are rare but urgent, and the client can receive a call | The client must be reachable; delivery must be retried and acknowledged |
| Event stream | The client reads a continuous stream at its own pace | Many events, several consumers, history is needed | Demands infrastructure and discipline about order and replay |
Polling is the easiest to build and the most expensive to make immediate — the more often you ask, the more empty answers you get. Webhooks shift the cost to the moment of the event, but introduce a new obligation: what if the client is unreachable when you call. Event streams are most powerful when several systems must hear the same change and order matters, but they demand the most discipline. The mistake is choosing by fashion rather than by the nature of the traffic.
Error semantics and rate limits
An error code is not decoration — it is a message to the caller about what to do next. A client that does not understand the difference between "your request is wrong" and "I am currently down" behaves wrongly in both cases: it either gives up when it should retry, or stubbornly repeats something that will never succeed.
- 400 — the request is malformed; repeating it unchanged gives the same result, so there is no point retrying.
- 409 — a conflict with the current state; the resource already exists or has changed since you last saw it. It calls for checking the state, not blind retrying.
- 429 — too many requests; slow down and try later, ideally with an interval that grows.
- 5xx — the server failed; retrying makes sense, but only with an idempotency key so the work is not doubled.
Rate limits and backpressure are two sides of the same problem. A system with no limit accepts as much traffic as the client sends, right up until it collapses under the load. A clear limit with a 429 response is not an obstacle but a gift: it tells the client exactly how much it may send, instead of letting it discover the limit by taking both systems down.
Observability: you cannot debug what you cannot see
An integration you cannot see cannot be fixed. When two companies argue over whose fault it is that an order never arrived, the only argument worth anything is the record: which request was sent, when, with what content, and what response came back. Without that you have two sides swearing they are right and no way to establish who is.
In practice, every call should carry an identifier that threads through both systems, so that a single transaction can be followed from start to finish. When an agent in NG Commerce passes an order to the warehouse, you must be able to see where it stopped if it did not arrive. Without observability, every integration is a black box that works while it works and goes silent when it stalls.
Point-to-point or a hub
The first integration is easy. You connect two systems directly and it all works. The second is easy too. The trouble starts around the fifth system, when you realise every new one has to connect to every existing one. Ten systems connected each to each gives forty-five links, and every one of them is a separate contract someone has to maintain. The number of links grows quadratically while the team grows linearly — an equation that always ends in gridlock.
A hub breaks that growth. Instead of every system knowing every other, they all talk to one point that knows the contracts. When NG Operations plans production and KickOff WMS runs the warehouse, they do not need to know about each other — it is enough that each knows its contract with the hub. A new system then joins with a single link, not with all of them at once.
The last part everyone skips is documentation. A contract that is not described well enough for another developer to understand without a meeting is not a product but an oral tradition. Good API documentation is not a formality after delivery — it is part of the product just as the code is. If you are thinking about connecting systems you already run, look at how we approach integration or contact us with a concrete case — the architecture decision is made at the start, not at the end.
Let's talk about your case
Describe the process that costs you the most. In a short call we tell you whether automating it pays off, and what that would concretely involve.
Frequently asked questions
What does an API-first approach mean?
API-first means the communication contract for a system is defined before the implementation, and the user interface is treated as just one of that contract's clients. The goal is for the API to speak the language of the business — order, stock, invoice — rather than the language of the internal database. This avoids every database change breaking clients that depend on it.
Why is it bad to add an API at the end of a project?
When an API is added at the end, the only thing that exists is internal tables, so the API becomes their reflection instead of a description of business concepts. Clients then have to know your schema to work, and you cannot change it without breaking them. The dependency forms in the wrong direction and is paid for expensively at every refactor.
What is idempotency and why does it matter for integrations?
Idempotency means the same request, sent more than once, has the same effect as if it were sent once. It matters because every integration eventually delivers the same message twice — usually because the response was lost and the client retried. Without an idempotency key, the retry creates a duplicate invoice or a duplicate shipment.
When should you use a webhook versus polling?
Polling is good when changes are rare and immediacy does not matter, since it is simple but wastes resources on empty queries. A webhook fits when events are urgent and the client can receive a call, but it introduces the duty to redeliver if the client is unreachable. An event stream is used when several systems must hear the same change and order matters.
Why do point-to-point integrations become a problem?
With point-to-point, every new system must connect to every existing one, so the number of links grows quadratically with the number of systems. Ten systems connected each to each gives forty-five separate contracts to maintain. A hub stops that growth by having each system talk to one point, and a new system joins with a single link.
Keep reading
- Integrations9 min read
ERP integration without double entry: what actually gets synced
Double entry is a symptom, not the disease. The disease is that two systems both believe they own the same record. The fix begins with deciding who owns what — and writing it down.
Read - Architecture10 min read
Modernising a legacy system without stopping the business
A full rewrite fails because the old system keeps moving while you build the new one. Modernisation that works proceeds in small, reversible steps — one slice, behind a façade, while the business keeps running.
Read - AI & automation9 min read
Business process automation: from scripts to AI agents
Automation is not one technology but a ladder of five rungs. A guide to what each is good at, how it fails, and why you fix the process before you automate it.
Read