Most API teams treat the contract as an afterthought: write code, generate a spec, then watch the two drift apart. Git-native API design reverses that flow. You treat the API contract as source code, version it in Git, and review every change the same way you review application logic.
This guide focuses on implementation discipline, not a single tool. You’ll design contracts in branches, review them in pull requests, and turn a committed spec into mocks, tests, and docs. The goal is simple: your Git history should also be your API history.
If you already know what Spec-First tooling looks like and want the product walkthrough, read the companion piece on the git-native API workflow. This article stays focused on practice.
What “git-native” means for API work
Git-native means your API definition lives in your repository as a plain text file. Not in a proprietary cloud database. Not behind a vendor login. A .yaml or .json file sits next to your code and is tracked by the same version control system your team already uses.
In many cloud-locked API design tools, the contract lives in the vendor’s backend. You edit through a web UI, and your repository only contains an export. That export can become stale, and your Git history no longer explains how the API evolved.
The git-native model inverts that relationship:
- The file in
mainis the contract. - Any GUI is a view onto that file.
- Branches, commits, pull requests, blame, and rollback all apply to your API surface.
- Mocks, docs, tests, and generated clients derive from the committed spec.
A git-native setup has three core properties:
- The spec is a text file in the repo.
- Changes flow through normal Git operations: branch, commit, PR, merge.
- Downstream artifacts derive from the committed file, not from a separate database.
Why design and develop APIs in Git
You already trust Git with your code. Your API contract deserves the same treatment.
1. History
When someone asks, “When did we add the cursor pagination parameter?”, Git answers directly:
git log -p -- api/openapi.yaml
The commit that introduced the change includes an author, date, message, and diff. No screenshots. No manual changelog archaeology.
2. Blame
Use git blame to find who changed a field and when:
git blame api/openapi.yaml
A confusing field name can be traced back to the PR that added it, including the review discussion.
3. Rollback
If a bad design ships, revert the merge:
git revert <merge-commit-sha>
The contract returns to its previous state. Codegen, mocks, docs, and tests regenerate from the reverted file.
4. Review
A pull request is the right place to debate API design before implementation.
Reviewers can comment on the exact + line that adds a required field, changes a response shape, or introduces a new enum value. The design discussion stays attached to the change permanently.
5. Single source of truth
When the contract is one file in main, there is no ambiguity about which version is real. Frontend, backend, QA, and docs all read the same OpenAPI definition.
That is the core value of a git-based API specification workflow.
The git-native API design loop
The loop has five steps:
- Design the contract.
- Commit the change.
- Open a pull request.
- Review the API design.
- Merge, then implement.
Implementation follows the merged contract, not the other way around.
Step 1: Create a branch
git checkout -b feat/api-invoices-list
Step 2: Edit the OpenAPI file
Suppose you are adding an endpoint to fetch a user’s invoices.
# api/openapi.yaml
paths:
/users/{userId}/invoices:
get:
operationId: listUserInvoices
summary: List invoices for a user
parameters:
- name: userId
in: path
required: true
schema:
type: string
format: uuid
- name: status
in: query
required: false
schema:
type: string
enum: [draft, open, paid, void]
responses:
"200":
description: A page of invoices
content:
application/json:
schema:
$ref: "#/components/schemas/InvoiceList"
"404":
description: User not found
Step 3: Commit the design change
Keep the commit small and specific:
git add api/openapi.yaml
git commit -m "Add GET /users/{userId}/invoices contract"
Step 4: Open a pull request
The PR diff should show one logical design change:
- One path
- One operation
- Two parameters
- Two responses
Reviewers can now discuss:
- Is
listUserInvoicesthe rightoperationId? - Should
statusinclude all required states? - Should this endpoint support pagination?
- Is
404correct for a missing user? - Does the response schema match existing conventions?
Step 5: Merge, then implement
After approval, merge the contract into main. The implementation is then constrained by the agreed spec.
This is the practical meaning of spec-first API development: the agreement comes before the code.
The payoff is cost control. Changing a YAML field during review takes minutes. Changing a shipped, implemented, documented endpoint can take days.
Branching strategy for API contracts
Treat contract changes like code changes: one branch per logical unit of work.
Small branches keep diffs readable and make API review realistic.
| Change type | Branch prefix | Example | Review weight |
|---|---|---|---|
| New endpoint | feat/api- |
feat/api-invoices-list |
Standard |
| Additive field | feat/api- |
feat/api-invoice-currency |
Light |
| Breaking change | break/api- |
break/api-remove-legacy-id |
Heavy, needs sign-off |
| Bug fix in spec | fix/api- |
fix/api-status-enum-typo |
Light |
| Refactor only | chore/api- |
chore/api-reorder-schemas |
Light |
The prefix communicates intent.
A break/api- branch tells reviewers to slow down and check consumers. A chore/api- branch signals no semantic API change, so review can move faster.
Pick a branching model
| Model | Best for | API tradeoff |
|---|---|---|
| Trunk-based | Continuous delivery, small teams | Contract evolves in small steps; less merge pain |
| Gitflow | Scheduled releases, regulated shipping | Spec diverges on develop; bigger, riskier merges |
For most API teams, prefer trunk-based development:
- Short-lived branches
- Small PRs
- Frequent merges into
main - Less spec drift
- Fewer YAML merge conflicts
Long-lived branches are risky because two teams can restructure the same spec file and create painful conflicts. If that happens often, split the spec into multiple files with $ref.
Reviewing API design in pull requests
A spec PR is a design review, not just a syntax check.
Reviewers should focus on semantic impact.
Check for breaking changes
Breaking changes include:
- Removing a field
- Renaming a path
- Changing a response type
- Making an optional field required
- Removing an enum value
- Tightening validation rules
If the change is breaking, require:
- Explicit PR labeling
- API steward approval
- Version bump
- Migration or deprecation plan
Check naming consistency
Look for consistency with the existing API:
- Are collection paths plural?
- Are path parameters named consistently?
- Do error responses use the same shape?
- Are enum values styled the same way?
- Does the
operationIdfollow your pattern?
Check diff readability
Stable YAML makes review easier.
Use consistent ordering for:
paths- HTTP methods
parametersresponsescomponents.schemas
Avoid reformatting the whole file in the same PR as a semantic change. A five-line diff is reviewable. A 500-line reordered spec hides the real change.
Example: safe enum addition
parameters:
- name: status
in: query
schema:
type: string
- enum: [draft, open, paid, void]
+ enum: [draft, open, paid, void, uncollectible]
This adds a new enum value, so it is usually additive.
Compare that with removing void, which would break any client that sends that value.
Inline comments make this process concrete. Reviewers should comment on the spec line just like they comment on application code.
From design to development
Once the contract is in main, it becomes the input for everything downstream.
Generate code
Use tools like openapi-generator to generate server stubs or typed clients from the committed spec.
Example:
openapi-generator-cli generate \
-i api/openapi.yaml \
-g typescript-fetch \
-o generated/clients/typescript
Your application code fills in business logic, but request and response shapes come from the contract.
Generate mocks
Run a mock server from the OpenAPI file so frontend developers can build before the backend is complete.
The contract becomes usable immediately after merge.
Add contract tests
Contract tests verify that the running server matches the committed spec.
A typical flow:
- Start the API server in CI.
- Send real requests.
- Validate responses against
api/openapi.yaml. - Fail the build if the server and spec diverge.
This turns spec/code drift into a pipeline failure instead of a production bug.
Generate docs
Reference docs should render from the same OpenAPI file.
When the contract changes, docs change with it. No separate manual doc update should be required.
The rule is simple: every API artifact should derive from the committed contract.
Team conventions that scale
Conventions keep a git-native workflow manageable as the team grows.
1. Choose one spec file or many
A single openapi.yaml is simple and works well for smaller APIs.
As the API grows, split the spec:
api/
openapi.yaml
paths/
users.yaml
invoices.yaml
schemas/
user.yaml
invoice.yaml
Use $ref to connect files and bundle them in CI.
2. Version deliberately
Update info.version for meaningful contract changes.
A practical versioning convention:
- Additive change: minor version bump
- Bug fix or documentation correction: patch version bump
- Breaking change: major version bump
Breaking changes often require a new path prefix such as /v2/.
3. Keep a changelog
Place CHANGELOG.md next to the spec.
Git history is precise, but a changelog is easier for API consumers to scan.
Example:
# API Changelog
## 2.1.0
- Added `GET /users/{userId}/invoices`
- Added `uncollectible` invoice status
## 2.0.0
- Removed legacy `customer_id` field
- Introduced `/v2` invoice endpoints
4. Protect the spec with CODEOWNERS
Require API stewards to approve contract changes.
# .github/CODEOWNERS
/api/openapi.yaml @api-stewards
/api/paths/ @api-stewards
/api/schemas/ @api-stewards
This prevents inconsistent changes from slipping into the contract.
5. Lint in CI
Use a linter to catch style and consistency issues before human review.
Example GitHub Actions workflow:
# .github/workflows/api-lint.yml
name: API Lint
on:
pull_request:
paths:
- "api/**"
jobs:
spectral:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Spectral
run: npx @stoplight/spectral-cli lint api/openapi.yaml --fail-severity warn
With linting plus CODEOWNERS, each contract change gets automated checks and human review.
Common pitfalls and how to avoid them
Git-native API design has predictable failure modes.
Pitfall 1: Spec/code drift
The spec says one thing. The running server does another.
Avoid it with contract tests in CI. Validate live responses against the committed spec and fail the build on divergence.
Pitfall 2: Giant PRs
A branch that adds twenty endpoints is hard to review.
Avoid it by splitting API work into small PRs:
- One endpoint
- One schema change
- One behavior change
- One breaking change proposal
Small diffs get real review.
Pitfall 3: Hand-written artifacts
Hand-written clients, docs, or mocks can silently drift from the spec.
Avoid it by generating artifacts from the committed OpenAPI file every time.
Treat hand-written API artifacts as a smell.
Pitfall 4: YAML merge conflicts
Long-lived branches and large spec files create painful merge conflicts.
Avoid them with:
- Short-lived branches
- Stable key ordering
- Split-file specs
- Trunk-based development
- Small PRs
The pattern is consistent: keep changes small, generate from the spec, and let CI enforce the contract.
Where Apidog fits
You can run a git-native workflow with a text editor and a CLI. Many teams, however, want a visual design surface without giving up Git as the source of truth.
That is the gap Apidog’s Spec-First Mode fills.
Spec-First Mode keeps the OpenAPI file in your Git repository and supports two-way sync. You can edit the contract in Apidog’s visual designer or in your editor, while the file in Git remains canonical. Branches, PRs, and history still work as described above.
See the Spec-First Mode documentation for setup details.
The point is not to replace Git. The point is to add a GUI while keeping the repository as the single source of truth.
FAQ
Is git-native API design only for OpenAPI?
No. The discipline applies to any text-based contract format.
OpenAPI is common, but the same workflow works for:
- AsyncAPI
- gRPC
.protofiles - GraphQL SDL
- JSON Schema
If the contract is a text file you can diff, branch, and review, it can be git-native.
How do I handle breaking changes in a git-native workflow?
Make breaking changes visible and deliberate.
Use a break/api- branch prefix, bump the major version, and require steward approval through CODEOWNERS.
Where possible, add the new shape alongside the old one and deprecate the old path on a timeline. The PR diff and version bump should clearly signal the break to consumers.
Should the API spec live in the same repo as the code?
Usually yes, if one team owns both the API and implementation.
Co-locating the spec and code means:
- One PR can update contract and handler together.
- Contract tests run in one pipeline.
- Reviewers can see implementation impact.
Use a separate spec repo only when many teams consume one shared API and need independent versioning.
How do I prevent spec and code from drifting apart?
Add contract tests to CI.
They should send real requests to your running server and validate responses against the committed spec. If the server and spec diverge, the build fails.
Combine that with generated stubs, clients, mocks, and docs to keep the whole API workflow aligned.
Conclusion
Git-native API design is a discipline, not a product. You treat the contract as source code, evolve it in branches, review it in pull requests, and generate downstream artifacts from the committed file.
Start small:
- Move your spec into the repo.
- Add API linting in CI.
- Protect the spec with
CODEOWNERS. - Review contract changes in PRs.
- Generate clients, mocks, docs, and tests from the spec.
The workflow compounds. Each convention makes the next one easier, and your Git history becomes a complete record of how your API grew.
If you want a visual design surface that keeps the spec in Git, try Spec-First Mode in Apidog and see how two-way sync fits the workflow above.


Top comments (0)