APIs and integrations that fail loudly, safely, rarely.
Contract-first APIs, event-driven integration and the unglamorous discipline — idempotency, retries, dead-letter queues — that keeps enterprises consistent.
- System mapping
- Contracts
- Build & shadow
- Cutover & observe
Built for the day the other side fails.
Every enterprise is an integration problem wearing a business model. ERP, CRM, payment rails, partner feeds — the value is in the joins. We design APIs as contracts and integrations as event flows with explicit failure behaviour, because that is the part that decides whether the estate holds together on a bad Tuesday.
The pattern we meet most often is not a missing integration. It is many of them, written by several teams over many years, each with its own retry logic, its own idea of what a customer identifier is, and no shared view of whether anything is currently flowing. Nightly files paper over the gaps; a reconciliation spreadsheet papers over the files. Our job is to replace that with a small number of governed contracts, an event backbone where facts belong on a log, and enough observability that a broken flow is a prompt alert rather than a month-end surprise.
The test of an integration is not the happy path — it’s what happens when the other side is down, slow, or sends the same message twice.
Four disciplines, applied in that order.
Contracts first, because they are cheap to change on a whiteboard and expensive to change in many consuming systems. Failure behaviour last to be forgotten, because it is what you actually live with.
Contract-first API design
OpenAPI specs before code. Versioning, pagination and error semantics agreed while they’re cheap to change. We write the contract with the teams that will consume it, generate server stubs and client SDKs from it, and enforce compatibility rules in CI so a breaking change fails the build rather than a partner’s integration.
Event-driven backbones
Kafka / queue-based integration with the outbox pattern — no dual-write races, no silent loss. The event is written in the same database transaction as the business change and relayed by a connector or Debezium, so a producer can never commit a fact and lose its notification. Consumers are idempotent, keyed on a business identifier, and commit offsets only after the effect is durable.
Legacy & SaaS connectivity
SOAP fronting, file-drop ingestion, webhook normalisation — meeting systems where they actually are. A mainframe that nobody may modify becomes an event source through change data capture; an FTP drop becomes a validated, replayable feed. Behind the wrapper we modernise at our own pace, and consumers never see the difference.
Failure engineering
Idempotency keys, retry budgets, dead-letter queues and replay tooling — designed in, not bolted on after the first incident. Every flow we hand over has a written answer to four questions: what happens when the target is down, when it is slow, when it returns an error, and when the same message arrives twice. That answer is tested, not assumed.
How the work runs, day to day.
The contract is the deliverable, not the code.
An API that ships before its consumers have read the specification will be redesigned in production, in front of an audience. We invert that. The OpenAPI or AsyncAPI document is written first and reviewed with every consuming team — resource shapes, pagination, idempotency headers, error codes, rate limits and the deprecation policy. Only when the contract survives that review does implementation start, generated from the same document that the client SDKs and the mock server come from. Consumers can build against the mock once the contract is signed, which takes sequencing out of the plan.
- OpenAPI 3.1 and AsyncAPI documents under version control, reviewed as pull requests like any other change
- Additive-change policy with compatibility checks in CI — a breaking field removal fails the pipeline
- Deprecation windows backed by usage telemetry, so you can see who still calls v1 before switching it off
Facts on a log, not calls into the dark.
A synchronous call between two systems couples their uptime, their throughput and their release calendars. Where the interaction is really a fact that several systems need to know about — an order placed, a shipment tendered, a payment cleared — we publish it once to a durable, ordered log and let each consumer read at its own pace. A slow system stops being everyone’s problem, an outage becomes a backlog rather than a loss, and a bug that corrupted a downstream table is fixed by replaying a time range instead of re-keying data. The producer side uses the outbox pattern, so the fact and its event share one commit.
- Topic ownership, keys, schemas and retention recorded in an event catalogue that lives in the repository
- Schemas in a registry with compatibility rules enforced in CI — Avro, Protobuf or JSON Schema
- Change data capture from Oracle, SQL Server, MySQL or PostgreSQL where the source cannot be changed
Failure behaviour, decided in advance.
Integrations do not usually break with an exception. They break with a timeout on one side and a committed write on the other, or with a partner replaying yesterday’s file, or with a retry storm that turns a slow dependency into a dead one. We write the failure policy per flow before the code: what is retried and how many times, what backs off, what is held, what goes to a dead-letter queue, who is paged and what the replay procedure is. The policy is a table you can read in a review, and it is tested with fault injection before cutover rather than discovered at 03:00.
- Idempotency keyed on a business identifier so at-least-once delivery produces exactly-once outcomes
- Retry budgets with exponential back-off and circuit breakers, so a slow dependency cannot be turned into an outage
- Dead-letter queues with a rehearsed replay procedure and alerting on dead-letter volume, not just on errors
Shadow first, cut over second.
The riskiest hour in an integration programme is the one where a new flow starts writing to a system of record. We remove it. New integrations run in shadow mode against real production traffic — consuming the same inputs, producing the same outputs, writing nowhere — while a comparator reports every difference between the new result and the current one. Differences are worked down until the remaining ones are explainable and agreed. Only then does the flow start writing, usually for one segment first, with the old path still warm and a rehearsed rollback.
- A comparator that reports differences by type and volume, not a sample someone eyeballed
- Cutover by segment, partner or region, each step reversible
- The legacy path kept running until the difference report has been clean for an agreed period
Three people care about an integration, for three reasons.
One estate, governed, with a picture that is true.
You get an inventory that reflects reality: every flow, its owner, its contract, its consistency requirement and its failure policy. New joins go through the same review, so the estate does not re-fragment after we leave. Where the right answer is fewer integrations rather than better ones, we say so — a point-to-point flow that exists because two teams could not agree on ownership is a governance problem, not an engineering one.
- System and flow inventory with data owners and consistency requirements
- Standards for REST, GraphQL and events — and the rule for choosing between them
- Gateway policy: authentication, rate limits, keys, quotas and a docs portal
You find out from a dashboard, not from a customer.
Every flow we hand over ships with its own view: throughput, lag, error rate, dead-letter depth and the age of the oldest unprocessed message. Alert policies are exported as code alongside the flow, so a new integration cannot reach production without them. The runbook for each failure mode is written by the engineers who built the flow and rehearsed with your team in a game day before go-live.
- Per-flow dashboards and alert policies, versioned with the code
- Runbooks per failure mode, rehearsed rather than filed
- Replay and reconciliation tooling your on-call can run without us
An API a partner can onboard onto without a call.
External APIs are a product with a support cost. We build the gateway layer with the developer experience attached: self-service keys, sandbox credentials, generated reference documentation, code samples and a changelog that says what changed and when the old behaviour stops. Rate limits and quotas are per consumer, so one enthusiastic partner cannot degrade the rest, and usage telemetry tells you which endpoints are actually earning their maintenance.
- Self-service onboarding: keys, sandbox, generated docs and samples
- Per-consumer rate limits, quotas and usage telemetry
- Versioning and deprecation communicated on a schedule, backed by call data
What is in every engagement, and what is shaped per estate.
Four phases, and nothing writes until it has been shadowed.
System mapping
Inventory of systems, data owners and flows — and the consistency requirements each join really has. We work out which joins need to be strongly consistent, which can be eventually consistent by seconds, and which have been nightly for years without anyone minding. That answer determines the architecture, and it is usually cheaper than the one people assume.
Contracts
OpenAPI / event schemas reviewed with every consuming team before implementation starts. Each contract carries its error semantics, idempotency rules, retention and ownership. Mock servers and generated SDKs go out with the signed contract, so consuming teams build in parallel with us instead of waiting for us.
Build & shadow
Integrations run in shadow mode against production traffic, outputs compared before anything cuts over. Fault injection covers the failure policy — target down, target slow, duplicate delivery, malformed payload — and the comparator report is worked to zero unexplained differences before a date is agreed.
Cutover & observe
Staged cutover with per-flow dashboards, alerting on lag and failure rates, and documented replay procedures. We move one segment at a time with the old path warm, then hand over the runbooks in a game day with your on-call team. Nothing is declared done because it deployed; it is done when your team has run the recovery once.
What you have at the end.
- OpenAPI / AsyncAPI contracts under version control
- Deployed integration flows with monitoring dashboards
- Idempotency, retry and dead-letter policies — documented and tested
- Replay and reconciliation tooling
- Runbooks for every failure mode we designed for
- An event and endpoint catalogue with owners, keys, schemas and retention
- Infrastructure and alert policies as code, in the same repositories as the flows
When this is the right call.
Choose this when more than a handful of systems have to agree about the same facts, when a join carries money or a regulatory obligation, or when the current estate is a set of point-to-point scripts whose owners have left. It is also the right call before a modernisation or legacy re-engineering programme: a governed integration layer is what lets you replace a system of record without renegotiating with every consumer. And it is the right call when partner-facing APIs are becoming a product, with the onboarding, versioning and support that implies.
It is not the right call for a single connection between two systems that a product team can own end to end — that is a sprint, not a programme, and we will say so. It is not the right call when nobody will own the contracts, the schemas and the consumer lag after we leave; an unowned integration layer becomes a very durable place to lose data. And if the real problem is that two departments disagree about who owns the customer record, no amount of engineering will settle it — that conversation has to happen first.
Frequently asked questions
Where this service usually sits.
Tell us what you’re building.
Describe what exists today and what you need. An engineer reads it and replies in writing with how we would approach it.