There’s No API for Operational Chaos: Lessons from Scaling Acquired SaaS
Acquiring SaaS is easy. Making their codebases play nice is where the pain begins. From CI/CD to APIs, here’s what breaks, and how engineers cope.
Join the DZone community and get the full member experience.
Join For FreeThe deal closed on Friday. By Monday, the acquired company’s CI/CD pipeline had triggered an outage during onboarding. No one knew where the deployment scripts were, and half the infrastructure was still named after an ex-employee’s dog.
This is not edge case material — it is standard operating procedure in the world of permanent capital SaaS.
As private equity and holdco operators lean into long-term ownership models, they are gobbling up B2B software companies at a record pace. Evergreen players like Constellation Software and Battery Ventures are scaling by acquisition, not by codebase.
But while the spreadsheets align in the boardroom, the infrastructure underneath these deals tells a different story. There is no API for operational chaos — yet somehow, we are expected to merge pipelines, schemas, and APIs like they came off the same assembly line.
“You can close a deal in 90 days. Fixing their CI config takes 90 weeks.”
CI/CD: Where ‘Continuous’ Meant Continuously Breaking
There is an assumption in many acquisitions that engineering systems will simply align themselves post-deal. But in practice, the CI/CD systems are where the ghosts of previous org structures come out to play. Legacy pipelines are often held together by tribal knowledge, undocumented environment variables, and cron jobs that everyone’s too afraid to touch. Even understanding where one deployment starts and another ends can be an archaeological exercise.
The first stop in post-acquisition entropy is always the CI/CD pipeline.
One company deploys via Jenkins. The next uses GitHub Actions with undocumented secrets. A third runs a Bash script from 2016 on a cron job. None of them agree on naming conventions. All of them break differently.
# Excerpt from legacy GitLab CI
stages:
- build
- test
- deploy
deploy_prod:
stage: deploy
script:
- echo "$PROD_SECRET" # hardcoded secret
- scp ./build [email protected]:/var/www/html
only:
- main
Trying to standardize across a new portfolio often ends in Terraform battles:
# Merge conflict between two Terraform states
resource "aws_s3_bucket" "app_logs" {
bucket = "company-a-logs"
acl = "private"
}
# Conflicts with:
resource "aws_s3_bucket" "app_logs" {
bucket = "company-b-logs"
versioning {
enabled = true
}
}
It is not about tools. It is about assumptions. One team assumes infra-as-code is sacred. The other treats it like a suggestion. Merging them means rewriting not just files, but mental models.
Beyond the tooling clash, the culture clash is harder to resolve. Some teams bake deployments into sprint planning, while others treat release windows like an optional adventure. Shared ownership rarely survives past the first failed release.
The result? A Frankenstein pipeline with half-written rollback scripts, misaligned approval gates, and a backlog full of To Do's no one owns.
Schema Spaghetti: Data Contracts from a Parallel Universe
Data is supposed to be the common language of modern SaaS. But when you acquire multiple companies with different interpretations of the same fields, it becomes a game of semantic roulette. Some systems were built with normalized databases and event sourcing; others store user preferences in XML blobs in a PostgreSQL column labeled 'misc_data'. The problem is not just a lack of standardization — it is the outright contradiction in business logic baked into these schemas.
Even if you get the deployment ducks in a row, your data is already betraying you.
Every acquired SaaS has a different take on "user," "order," or "status." You do not notice it until the dashboards stop matching or a unified billing report starts quoting fictional revenue.
// App A
{
"order_status": "complete",
"customer_id": "987654",
"tax": null
}
// App B
{
"status": "fulfilled",
"clientId": 987654,
"tax": "included"
}
And then the ETL pipeline dies quietly:
ERROR: Unexpected field 'clientId' at line 742
ERROR: Null value in 'tax' violates NOT NULL constraint
You think this is an easy fix until you discover there is no data contract. Just an API built six years ago that someone “wrote docs for” in a deprecated Notion workspace.
Even worse, changes get pushed without schema versioning. Your data team gets paged at 2 AM because a critical dashboard stopped rendering, all because someone renamed a key in a hotfix. Tracking down the issue feels like digital archaeology.
Most of it lives between inconsistent data models and tribal knowledge. Nobody owns the schema — everyone inherits the problem.
“Our analytics platform became a liar with confidence.”
API Versioning Hell: When Integration Becomes Interpretation
If the data is contradictory and the deployments are broken, the APIs are downright poetic in their dysfunction. Inherited APIs often reflect a time when REST was optional, authentication was an afterthought, and versioning was a dream deferred. The effort to merge these into a unified platform is less about engineering and more about forensic linguistics. You are not integrating systems — you are translating dialects of dysfunction.
Welcome to the final boss: legacy APIs.
Not because they are old. Because they lie.
One system uses REST v1.2 but pretends to be v2. Another returns HTTP 200 with error payloads. Swagger files exist, but they are aspirational.
# Two APIs returning conflicting payloads for the same endpoint
curl https://api.company-a.com/user/123
{
"id": 123,
"email": "[email protected]"
}
curl https://api.company-b.com/user/123
{
"user_id": "123",
"contact": {
"email": "[email protected]"
}
}
Then there is the documentation:
# Excerpt from Swagger file
/user/{id}:
get:
summary: Get user
responses:
200:
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/User'
# But 'User' is undefined in components
Integration becomes improvisation. You set up adapter services just to reconcile version mismatches. Internal SLAs are dropped to "best effort."
Debugging these APIs is often trial by fire. You spin up Postman collections that start resembling fan fiction. Devs end up memorizing undocumented behavior patterns instead of fixing them.
“Every integration becomes a negotiation. And someone always loses.”
Tech Debt Is a Line Item. Chaos Is a System.
By the time engineering leadership starts asking why everything is slow, the chaos has calcified. You are no longer debugging APIs — you are negotiating across philosophies.
This is the unsexy reality of scaling SaaS via acquisition. You do not build new platforms. You inherit entropy. You inherit decisions made under entirely different constraints. Your job is not to erase them, but to surface, absorb, and redirect them.
Three takeaways:
-
Unify observability before code: If you cannot see across systems, you cannot scale them.
-
Treat architectural unification like product work: Roadmap it. Staff it. Do not just assign it to “DevOps.”
-
Audit data before you trust it: Contracts, not just pipelines, must align.
“In rollups, you are not scaling products — you are scaling mismatches. The trick is surviving long enough to standardize anything.”
Opinions expressed by DZone contributors are their own.
Comments