How to Build a CI/CD Pipeline for APIs
A platform team running APIs on Kong, one running them on Apigee, and one running them on WSO2 all hit the same wall eventually. Someone changes an API definition, promotes it by hand from dev to staging to production, and a week later nobody can say with confidence which version is actually live where. The fix isn't a specific gateway. It's treating API changes the way mature teams already treat application code: version them, test them, and let a pipeline move them forward.
This guide covers the general pattern first, because it applies no matter which gateway sits behind it. Then it looks at where the pattern still needs a decision, like secrets handling and build versus buy. Only at the end does it walk through what this looks like on one real platform.
What CI/CD Means for APIs
Continuous integration and continuous delivery bring the same discipline to APIs that they bring to any other software. Build, test, and promote changes automatically instead of by hand. For application code, that typically means compiling the code, running its automated test suite, and running static analysis or security checks before anything merges. For an API, it means something more specific, because an API is also a contract. Client applications and partner integrations depend on that contract staying stable, so a CI/CD pipeline for APIs has to validate the interface itself, not just the code behind it.
That distinction matters for the CI/CD pipeline you actually build. Continuous integration checks that a new API definition is valid and doesn't break the contract. Continuous delivery gets that definition packaged and ready to deploy to any environment. Continuous deployment goes a step further and pushes it to production automatically once it clears the pipeline's checks. Most API teams land somewhere between delivery and full deployment, since production changes to a live API usually still call for a review gate.
Done well, this automation gets APIs to consumers faster, keeps dev, staging, and production consistent with each other, and removes the manual gateway configuration steps where mistakes creep in. Done manually, promoting an API is slow, inconsistent across environments, and hard to audit after the fact. That gap is why CI/CD for APIs is worth the setup effort even on a small team.
A General Pattern That Works on Any Gateway
The mechanics below don't depend on Kong, Apigee, Azure API Management, or any other specific product. They come from how mature API programs already operate, and they hold regardless of what enforces them at runtime.
Design contract-first
Write the OpenAPI specification before the implementation, and treat that spec as the artifact your pipeline actually builds and tests. A contract-first approach means the interface is agreed on by the API's producers and early consumers before code gets written, which is what makes the rest of the pipeline possible. Sometimes the contract only emerges as a byproduct of whatever the code happens to do. It can still be checked into Git and diffed like any other file. But nothing forces it to stay stable from one release to the next, because no one outside the implementation team agreed to it up front.
Test the API itself, not just the code behind it
Lint the OpenAPI spec for structural problems, spin up a mock server from the spec so consumers can start integrating early, and run contract tests that check the deployed API still matches what was published. This is where teams most often cut corners, because it's easier to test the backend service than the interface a client actually calls.
Version deliberately
Semantic versioning tells consumers what a release means before they upgrade. A patch release shouldn't break anyone. A minor release adds capability without breaking existing calls. A major release can break compatibility, and it needs a side-by-side deployment strategy so existing consumers aren't forced to migrate on your schedule.
Make pipeline operations idempotent
A deployment step that fails partway through and gets rerun shouldn't leave the API gateway in an inconsistent state. Getting that right depends on how the tooling is implemented, not on which style of pipeline you write. Declaring the API definition you want and letting a reconciler apply it makes this easier by default. A well-built reconciler converges on the declared state no matter how many times it runs. Imperative deployment scripts can be written to behave the same way. It just takes more discipline to make every step safe to rerun. This is the same idea behind infrastructure as code, applied to the API layer instead of the servers underneath it.
Treat your API configuration as code
Store API definitions, policies, and environment-specific settings in a Git repository, the same way you'd store application source. This is often called API-Management-as-Code, and it's what makes the rest of the pipeline auditable. Every change to an API is a commit, with a diff and an author.
Promote through environments deliberately, not by copying files
One pattern that generalizes well is used by Azure API Management customers, among others. It keeps a repository of environment-specific configuration templates. A developer forks or branches it to change only their own API's settings. A pull request against that repository is the mechanism for promoting a change from one environment to the next, and it gives reviewers a natural checkpoint before anything reaches production. Whether you implement this with folders, branches, or separate repositories per environment, the shape is the same. Dev, staging, and production each have their own configuration, and promotion is a reviewed change, not a manual copy.
Put together, a typical pipeline for an API change has four steps. Validate the OpenAPI spec. Build it into whatever artifact your gateway consumes. Run tests against a mock or staging deployment. Then promote it through environments behind a review gate. Three separate services carry out those steps: the CI runner, the Git host, and the API gateway. None of them needs to call the others directly in a fixed sequence. Each does its job independently, and the pipeline is what coordinates them.
Build vs. Buy: Self-Hosted Pipelines vs. Managed Gateways
Before wiring any of this together, it's worth being honest about a decision that sits underneath the whole exercise: how much of this pipeline do you want to own?
A fully managed, cloud-hosted API gateway can absorb a meaningful share of the operational load described above. The vendor handles gateway uptime, scaling, and often the deployment mechanics themselves, which means less pipeline to build and maintain. What you give up is some control over exactly how promotion, rollback, and environment isolation work, since those are largely fixed by the vendor's platform.
A self-hosted or open-source gateway is the opposite trade. You own more of the pipeline, which means more setup work up front, but you also get to shape environment promotion, secrets handling, and rollback behavior to match how your organization actually operates. Neither choice is a mistake. Regulated industries and teams with existing Kubernetes and GitOps investments often lean self-hosted precisely because they need that control. Teams optimizing for time to a working pipeline often lean managed. The right answer depends on how much of this operational surface your team wants to run.
Secrets and Credentials in an API Pipeline
Every stage of this pipeline touches something that needs a credential. The CI runner needs access to your Git host, the deployment step needs a token or API key for the gateway, and the gateway itself often needs credentials for backend services. Handling this well is gateway-agnostic, and it's worth its own short checklist, since it's the piece most likely to get skipped under deadline pressure.
- Never commit credentials to the Git repository, even a private one. Treat the repo as something that will eventually leak.
- Use your CI platform's built-in secrets store (GitHub Actions secrets, a dedicated vault, or similar) rather than environment files checked into source control.
- Keep secrets separate per environment, so a compromised staging credential doesn't grant access to production.
- Rotate credentials on a schedule rather than only after an incident.
These are consistent with the OWASP Secrets Management Cheat Sheet, which frames secrets as something to centralize and rotate rather than store and forget.
GitOps: Git as the Source of Truth
GitOps is the practice of using a Git repository as the single source of truth for what should be running, rather than issuing one-off commands against live infrastructure. You commit the desired state, such as an API definition, a routing policy, or an environment configuration. A controller such as Argo CD or Flux compares that declared state against what's deployed. Then it reconciles any drift.
For API pipelines specifically, this has a few concrete payoffs. Every change is a Git commit, so there's a full audit trail of who changed what and when. Rollback is a revert, not a manual undo process. And because the controller is always reconciling toward the declared state, configuration drift between dev, staging, and production gets corrected automatically instead of accumulating unnoticed.
How a Modern API Platform Supports This Pattern
The pattern above doesn't need a specific vendor, but it's worth seeing how it plays out on a real platform. In the WSO2 API Platform, the API Gateway and the API Control Plane are separate components. Each one deploys independently. That matters for GitOps, because the gateway itself can be described and reconciled through the same declarative model as everything else in the pipeline.
Concretely, the gateway has a Kubernetes Operator mode built around custom resources. An APIGateway resource deploys and configures the gateway infrastructure. A RestApi resource defines an API's routes, upstream service, and policies. The same operator can also use standard Kubernetes Gateway API resources (Gateway and HTTPRoute) instead. Both are plain Kubernetes objects, so they live in Git like any other manifest. You can apply them with kubectl apply, or let Argo CD or Flux reconcile them like any other workload. There's no separate CLI import step and no proprietary export format standing between your API definition and the gateway that runs it.
That structure maps directly onto the general pattern from earlier in this piece. Each environment gets its own RestApi manifests and, typically, its own gateway instance or namespace. Promoting an API from staging to production means merging a pull request that updates the production manifest. The gateway operator picks up the change through its normal reconciliation loop. No one runs a manual deployment step. The API Control Plane gives operators a consistent view across environments. It can also federate other gateways an organization already runs, such as AWS, Azure, Kong, and Envoy.
A Worked Example: Promoting an API Through Git
To make this concrete, walk through promoting a single API, orders-api, from a development namespace to production using the CRD-based pattern above.
1. Define the API as a RestApi manifest
A developer writes a manifest describing the API's version, its context path, its upstream backend, and its operations, and commits it to a Git repository alongside the APIGateway manifest for that environment. The environment label on the manifest is descriptive, useful for querying and grouping resources, but it isn't what keeps dev, staging, and production apart. Isolation comes from two things. The first is the namespace each manifest targets. The second is a separate APIGateway per environment, with an apiSelector that only accepts that environment's APIs. Set the selector on purpose. The sample in the WSO2 docs uses scope: Cluster, which accepts APIs from any namespace.
apiVersion: gateway.api-platform.wso2.com/v1
kind: RestApi
metadata:
name: orders-api
namespace: orders-dev
labels:
environment: dev
spec:
displayName: Orders API
version: v1.0
context: /orders-api
upstream:
main:
url: https://orders-backend.dev.internal/v1
operations:
- method: GET
path: /orders
- method: POST
path: /orders
The gateway operator composes the final external route from context and each operation's path, so this manifest exposes GET /orders-api/orders and POST /orders-api/orders, not a bare /orders. That composition is documented behavior. Still, check it explicitly. A mismatch shows up as a route that looks correct in the manifest but returns 404 at the gateway. Once the operator has reconciled the resource in the orders-dev namespace, a request against the composed route confirms it: curl -k https://orders-dev.internal/orders-api/orders
2. Validate and test in CI
First, a pipeline step lints the manifest and the underlying OpenAPI definition. It runs a compatibility check against the last published version of the spec. Then it applies the manifest to the development namespace and confirms the operator has reconciled the change. Only then does it send a request to the running API. A minimal version of this as a CI workflow:
name: orders-api-ci
on:
pull_request:
paths:
- 'apis/orders-api/**'
jobs:
validate-and-deploy-dev:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Fetch main for the breaking-change check
run: git fetch --depth=1 origin main
- name: Lint the OpenAPI spec
run: npx @redocly/cli lint apis/orders-api/openapi.yaml
- name: Check for breaking changes
uses: oasdiff/oasdiff-action/breaking@v0
with:
base: 'origin/main:apis/orders-api/openapi.yaml'
revision: 'HEAD:apis/orders-api/openapi.yaml'
fail-on: WARN
- name: Deploy to dev
run: kubectl apply -f apis/orders-api/k8s/dev/orders-api.yaml -n orders-dev
- name: Confirm the operator has reconciled
run: kubectl get restapi orders-api -n orders-dev -o json | jq '.status'
- name: Smoke test
run: curl -kf https://orders-dev.internal/orders-api/orders
The lint and OpenAPI-diff steps above use @redocly/cli and oasdiff. Both are real tools your pipeline can run today. Swap in whatever linter and API-diff tool your team already prefers. The important part is the order: the breaking-change check is a required gate here, at the start of the pipeline, not something added on later once the basic deploy works. The status check before the smoke test matters too. A pipeline that curls the API right after kubectl apply can pass or fail on timing alone. Confirm the resource's status before testing against it. Fail the build if that status doesn't show a successful reconciliation.
3. Promote with a pull request
Moving orders-api to staging means opening a PR against a copy of the manifest with metadata.namespace changed to orders-staging, the environment label updated to match, and the staging-specific upstream URL substituted in:
metadata:
name: orders-api
namespace: orders-staging
labels:
environment: staging
spec:
upstream:
main:
url: https://orders-backend.staging.internal/v1
A reviewer checks the diff. It is small and readable because it's a Kubernetes manifest, not a binary export. The reviewer confirms that the namespace and upstream changes are the intended promotion and not accidental drift. That distinction matters, since an unreviewed namespace or upstream change is exactly the kind of mistake this review step exists to catch.
4. Let the operator reconcile
Once merged, the GitOps controller detects the change and applies it to the staging cluster automatically. No one runs a manual import or deployment command. The same PR-and-reconcile flow repeats to promote from staging to production.
5. Roll back if needed, within limits
If something regresses after the production promotion, revert the merge commit and let the controller reconcile again. That restores the previously declared configuration. The gateway's routes, upstream URL, and policies go back to what they were. It doesn't undo anything outside that configuration. That includes a database migration the backend already ran, a webhook that already fired, a credential that was already rotated, or a backend release that no longer works with the older API version. Treat a Git revert as a configuration rollback, not a full application rollback. Follow it with the same smoke request used in the CI workflow above. That confirms the gateway is serving what you expect.
This is the same shape described in the general pattern section: contract-first definition, environment-specific configuration in Git, and promotion gated by review rather than manual deployment. What changes from gateway to gateway is the manifest format and which controller does the reconciling, not the underlying discipline.
Hardening the Breaking-Change Gate You Already Have
The worked example above already includes a compatibility check as a required pipeline step, comparing each change against the previous version of the spec before it's allowed to deploy. That's a meaningful difference from treating breaking-change detection as an optional add-on. It's still worth knowing where a basic version of that check falls short as an API surface grows.
Diffing against the previous commit catches most accidental breaks, but it can miss changes introduced across several small commits that each look fine in isolation. A more durable setup diffs against what's actually running in each environment, not just the previous Git revision, so the baseline reflects reality even if history gets rewritten or commits get squashed. Track intentional deprecations separately from accidental breaks. Otherwise, a field your team is removing on a published timeline gets either silently allowed or permanently blocked by the same check. And failures should surface where a reviewer will actually see them, as a comment or a check on the pull request itself, not only in a CI log someone has to go find.
Conclusion
The pattern in this guide doesn't require picking a single vendor to start. Put your API definitions in Git, add a validation step to your existing CI platform, and introduce a review gate for promotion between environments. From there, you can adopt a GitOps controller or move to a platform with GitOps-native CRDs, like the one described above. That changes how deployment happens. It doesn't mean rewriting the pipeline you've already built.
If you're evaluating platforms for this, the WSO2 API Gateway is open source under Apache 2.0 and can be self-hosted. You can try the CRD-based GitOps workflow described here directly, either standalone or alongside gateways you already run.
Frequently Asked Questions
How do I create my own CI/CD pipeline?
Start by putting your API definition in a Git repository. Then add pipeline steps that validate the spec and run tests against a mock or a lower environment. Promote changes to the next environment behind a pull request review. Most teams build this incrementally on top of a CI platform they already use, like GitHub Actions or GitLab CI, rather than adopting a new toolchain all at once.
Which tool is best for a CI/CD pipeline?
There isn't one universal answer, since the CI runner, the GitOps controller, and the API gateway are three separate decisions. GitHub Actions and GitLab CI are common choices for the CI layer. Argo CD and Flux are the two most established GitOps controllers. The gateway choice usually follows from what your organization already runs in production.
Is CI/CD difficult?
The core loop of commit, test, and promote is easy to describe but takes real work to implement well, especially the parts specific to APIs, like contract testing and breaking-change detection. Most of the difficulty is organizational rather than technical.
How are CI/CD pipelines built?
They're built from a few independent pieces. A Git repository is the source of truth. A CI platform runs validation and tests on each change. A GitOps controller or a direct deployment step applies approved changes to the target environment. None of these pieces needs to be replaced to add the others, which is why most teams grow a pipeline incrementally rather than designing the whole thing upfront.