DEV Community

Cover image for How Do You Design and Develop APIs the Git-Native Way?
Hassann
Hassann

Posted on • Originally published at apidog.com

How Do You Design and Develop APIs the Git-Native Way?

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.

Try Apidog today

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.

Git-native API workflow

The git-native model inverts that relationship:

  • The file in main is 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:

  1. The spec is a text file in the repo.
  2. Changes flow through normal Git operations: branch, commit, PR, merge.
  3. 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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:

  1. Design the contract.
  2. Commit the change.
  3. Open a pull request.
  4. Review the API design.
  5. 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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 listUserInvoices the right operationId?
  • Should status include all required states?
  • Should this endpoint support pagination?
  • Is 404 correct 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 operationId follow your pattern?

Check diff readability

Stable YAML makes review easier.

Use consistent ordering for:

  • paths
  • HTTP methods
  • parameters
  • responses
  • components.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]
Enter fullscreen mode Exit fullscreen mode

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.

Design to development workflow

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
Enter fullscreen mode Exit fullscreen mode

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:

  1. Start the API server in CI.
  2. Send real requests.
  3. Validate responses against api/openapi.yaml.
  4. 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 .proto files
  • 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:

  1. Move your spec into the repo.
  2. Add API linting in CI.
  3. Protect the spec with CODEOWNERS.
  4. Review contract changes in PRs.
  5. 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)