Keep the products you have. Build the part between them.
Plenty of projects that arrive as a request for a new system are really a request for two systems to talk to each other. That version is cheaper, lands sooner, and leaves your team far less to own afterwards.
In short: Connective tissue is often the honest alternative to a rebuild. We design and build APIs and integrations that keep working under the conditions real ones meet: old clients that never upgrade, rate limits, failed calls that get retried, and a receiving system that goes down at three in the morning.
The cheaper answer is usually the right one
A team asks for a new platform because five people spend their mornings copying numbers between an accounting package, a CRM and a warehouse system. The pain is real. The diagnosis is usually wrong. Nothing about those products is broken; there is simply nothing joining them, so people are doing the joining by hand.
Replacing the three products with one costs more, takes longer, and hands your team a system to maintain forever. Building the link costs a fraction of it and leaves the vendors carrying the rest. It is a smaller invoice for us and usually the better outcome for you, which is why we raise it early rather than after a proposal has been signed.
Sometimes the work goes the other way and you are the one publishing an API, because partners want to pull your data or a customer wants your product inside theirs. That is a product in its own right, with consumers you cannot control and cannot ask to change.
Versioning
Old clients keep calling the old shape for years. A version in the path, additive changes by default, and a deprecation date announced long before it arrives.
Rate limits and retries
Limits stated in the response headers, and clients that back off with increasing delays instead of hammering a struggling service into the ground.
Idempotency
A key on every write, so a retried request returns the original result rather than charging a customer twice or creating a second order.
Webhooks that survive
Signed, queued, retried on a schedule, and replayable after the fact. The receiver will be down at some point and the events still have to arrive.
The failure cases are the design
An integration that works when both systems are healthy is a demo. The engineering is in what happens otherwise. A request times out after the other side already processed it. A network blip means a job runs twice. Somebody replays yesterday’s file by accident. Without idempotency, each of those becomes a duplicate invoice, a double shipment, or a payment taken twice, and duplicates are far harder to unpick than errors.
So every write carries a key, and repeating the same call returns the same answer instead of doing the work again. Retries are spaced out and capped. Anything that cannot be delivered after the last attempt lands in a queue a human can look at, with the original message intact, rather than disappearing into a log file. And when a downstream service starts failing consistently, we stop calling it for a while instead of piling on more traffic.
The same discipline applies to webhooks you send. Sign them so the receiver can verify the source. Keep delivery attempts on a schedule that stretches out over hours. Give the receiver a way to ask for the last day of events again after they fix their end. A webhook system with one attempt and no history quietly loses data, and nobody notices until a reconciliation months later.
Data that means the same thing on both sides
Two systems rarely agree on what a customer is. One has a record per billing account, the other one per contact, and the same person exists in both with a different email. Before any code, we settle which system owns each field, what happens on conflict, and how records are matched. Getting that wrong produces a sync that dutifully overwrites good data with stale data at regular intervals. Where the tangle is mostly customer records, our CRM data integration work goes deeper into it.
We also push toward the simplest shape that works. A one-way feed beats a two-way sync when only one side really edits the data. A nightly batch beats a live stream when nobody acts on the information before morning. Less machinery means fewer ways to fail, and integrations built around automated steps follow the same rule.
What you get at the end is documentation somebody can actually use, a test suite that runs against the real contract, and monitoring that tells you an integration stopped rather than letting you find out from a customer.
What we won’t do
We won’t build a two-way sync where a one-way feed would do
Bidirectional sync doubles the conflict cases and the debugging. If only one system genuinely edits the data, we will build the simpler thing even though it is the smaller job.
We won’t hide a broken source system behind an integration
If records are duplicated or wrong at the source, moving them faster just spreads the problem. We will name the cleanup that has to happen first, even when it delays the part you asked for.
We won’t scrape a private endpoint you have no permission to use
Undocumented internal endpoints and screen scraping work until the vendor changes something without warning, usually at the worst moment. If there is no supported route, we will say so rather than build on sand.
What are your people copying between systems by hand?
Tell us which products you already run and where the data stalls. We will tell you whether an integration solves it, and what it would take.
Is an integration really cheaper than a new system?
Usually, and the difference continues after launch. You keep paying vendors to maintain the products you already run, and your team owns only the connecting piece rather than an entire platform.
What is idempotency and why does it matter here?
It means repeating the same request produces the same result instead of doing the work twice. Networks time out and clients retry, so without it a single retried call can charge a customer twice or create a duplicate order.
What happens when the receiving system is down?
Events queue and delivery is retried on a spreading schedule rather than abandoned. Anything still undelivered at the end lands somewhere a person can see it, and the receiver can ask for past events to be sent again once they recover.
How do you handle changes to a published API?
Additive changes wherever possible, so existing clients keep working untouched. Anything that breaks the old shape gets a new version and a deprecation date announced well ahead, because some clients will never upgrade.