Claude Skills Guide

Claude Code REST API Versioning Strategy Workflow Tips

REST API versioning is one of the most critical decisions you’ll make when designing or evolving a web service. Choose wisely, and your API remains flexible and maintainable for years. Choose poorly, and you face breaking changes, confused consumers, and maintenance nightmares. This guide focuses on building versioned REST APIs—covering project structure, Express.js implementation, contract testing, and deployment—using Claude Code workflows. If you are instead consuming an external versioned API from within a Claude Skill (Python SDK calls, version negotiation, fallback logic), see Claude Code API Versioning Strategies Guide.

Why API Versioning Matters

When your API serves multiple clients—web applications, mobile apps, third-party integrations—changes to your endpoints can ripple through every consumer. A seemingly harmless field rename or response structure change can break existing functionality for thousands of users. Versioning provides a safety net, allowing you to introduce improvements without disrupting current clients.

Consider a scenario where your team needs to refactor a complex endpoint handling user profiles. Without versioning, you’d either maintain backward compatibility indefinitely or force all clients to update simultaneously—a coordination nightmare. With proper versioning, you deploy the new implementation alongside the existing one, giving clients time to migrate gradually.

Versioning Strategies Compared

There are three primary approaches to REST API versioning, each with distinct trade-offs.

URL Path Versioning embeds the version in the endpoint URL:

GET /api/v1/users/123
GET /api/v2/users/123

This approach offers excellent visibility—clients always know which version they’re using. It’s also straightforward to implement and debug. However, it creates URL pollution and requires more infrastructure overhead when managing multiple versions simultaneously.

Header Versioning passes the version via HTTP headers:

GET /api/users/123
Accept-Version: v1

This keeps URLs clean but adds complexity. Clients must remember to include the correct header, and tooling support varies. Some teams appreciate the cleaner URLs; others find the hidden configuration harder to manage.

Query Parameter Versioning uses URL parameters:

GET /api/users/123?version=1

A middle ground that maintains URL visibility while avoiding path pollution. However, caching can become complicated since the same resource has multiple valid URLs.

For most projects, URL path versioning strikes the best balance between clarity and simplicity. It’s the approach we’ll use in our examples.

Setting Up Versioned Endpoints with Claude Code

When building APIs with Claude Code, you can use skills like tdd to ensure each version maintains its contract correctly. Here’s a practical workflow:

First, define your API specification. Create a dedicated directory for each version:

/api
  /v1
    routes.js
    controllers/
    validators/
  /v2
    routes.js
    controllers/
    validators/

Using a skill like supermemory helps track what changed between versions, making it easier to document breaking changes for API consumers.

Implementing URL Path Versioning

Here’s a practical Express.js implementation using URL path versioning:

// routes/api.js
const express = require('express');
const router = express.Router();

const v1Router = require('./v1/routes');
const v2Router = require('./v2/routes');

router.use('/v1', v1Router);
router.use('/v2', v2Router);

module.exports = router;

Each version router maintains its own controllers and business logic. This separation prevents accidental leakage of v2 functionality into v1 responses—a common pitfall when developers try to share code across versions.

For your v1 endpoint:

// routes/v1/users.js
const express = require('express');
const router = express.Router();

router.get('/users/:id', (req, res) => {
  const user = getUserById(req.params.id);
  res.json({
    id: user.id,
    name: user.name,
    email: user.email
  });
});

module.exports = router;

And the v2 version with expanded profile data:

// routes/v2/users.js
router.get('/users/:id', (req, res) => {
  const user = getUserById(req.params.id);
  res.json({
    id: user.id,
    name: user.name,
    email: user.email,
    avatarUrl: user.avatar_url,
    createdAt: user.created_at,
    preferences: user.preferences
  });
});

Notice how v2 adds fields without removing any from v1. This backward compatibility principle is essential—never remove fields from a versioned API unless you’re intentionally deprecating that version.

Version Negotiation Best Practices

Rather than forcing clients to pin to specific versions, implement a sensible default with explicit opt-in:

// middleware/version.js
function versionHandler(req, res, next) {
  const acceptHeader = req.headers['accept-version'];
  const pathVersion = req.path.match(/^\/v(\d+)/);
  
  if (pathVersion) {
    req.apiVersion = parseInt(pathVersion[1], 10);
  } else if (acceptHeader) {
    req.apiVersion = parseFloat(acceptHeader.replace('v', ''));
  } else {
    req.apiVersion = 1; // Default to v1
  }
  
  next();
}

This middleware checks both URL paths and headers, defaulting to v1 for backward compatibility. Clients can then upgrade at their own pace.

Deprecation Middleware

Once a version is scheduled for sunset, you need to signal that to clients actively using it. Add deprecation response headers so consumers know they are on an older version and where to migrate:

// middleware/deprecation.js
function deprecationMiddleware(req, res, next) {
  if (req.apiVersion === 1) {
    res.set('Deprecation', 'true');
    res.set('Sunset', 'Sat, 01 Jan 2027 00:00:00 GMT');
    res.set('Link', '<https://api.example.com/v2/users>; rel="successor-version"');
  }
  next();
}

The Deprecation and Sunset headers follow RFC 8594, which many HTTP clients and API gateways understand natively. The Link header with rel="successor-version" points consumers directly to the replacement endpoint. Claude Code can generate this middleware for you and wire it into your Express router automatically when you describe the deprecation timeline.

Documenting Your Versions

Each API version deserves comprehensive documentation. Skills like pdf can generate beautiful API documentation from your OpenAPI specs. Maintain a changelog specifically for version transitions:

## v2.0.0 (2026-03-14)

### Added
- `avatar_url` field in user responses
- `created_at` timestamp for all resources
- User preferences object

### Deprecated
- v1 endpoints will be sunset in 12 months

### Breaking Changes
- Response structure for `/users` now includes nested objects

Generating OpenAPI Specs with Claude Code

Beyond changelogs, Claude Code can keep your OpenAPI specification in sync with your implementation. When you update an endpoint, ask Claude Code to:

This workflow prevents the common drift where the spec documents the old behavior and developers have to reverse-engineer the actual contract from the source code.

Testing Across Versions

When implementing multiple API versions, automated testing becomes critical. Use the tdd skill to create comprehensive test suites that verify each version’s contract:

// tests/api-contract.test.js
describe('API v1 Contract', () => {
  test('GET /v1/users/:id returns required fields', async () => {
    const response = await request(app)
      .get('/v1/users/123');
    
    expect(response.status).toBe(200);
    expect(response.body).toHaveProperty('id');
    expect(response.body).toHaveProperty('name');
    expect(response.body).toHaveProperty('email');
  });
});

describe('API v2 Contract', () => {
  test('GET /v2/users/:id includes all v1 fields', async () => {
    const response = await request(app)
      .get('/v2/users/123');
    
    expect(response.body).toMatchObject({
      id: expect.any(Number),
      name: expect.any(String),
      email: expect.any(String)
    });
  });
  
  test('GET /v2/users/:id includes v2 enhancements', async () => {
    const response = await request(app)
      .get('/v2/users/123');
    
    expect(response.body).toHaveProperty('avatarUrl');
    expect(response.body).toHaveProperty('createdAt');
    expect(response.body).toHaveProperty('preferences');
  });
});

These tests ensure that v2 clients receive everything from v1 plus the new fields—a principle called “expansion, not replacement.”

Another valuable pattern is the negative assertion: explicitly verify that v1 responses do not contain v2-only fields. This prevents accidental leakage if shared code is refactored carelessly:

describe('API v1 isolation', () => {
  test('GET /v1/users/:id does not expose v2 fields', async () => {
    const response = await request(app)
      .get('/v1/users/123');

    expect(response.status).toBe(200);
    expect(response.body).not.toHaveProperty('avatarUrl');
    expect(response.body).not.toHaveProperty('preferences');
  });
});

Use the tdd skill to generate both positive and negative assertions together so coverage of the version boundary is never incomplete.

Deployment Considerations

Deploying versioned APIs requires infrastructure planning. Common strategies include:

For most projects starting out, parallel deployment within a single application keeps things simple. As traffic grows, you can migrate to separate services without changing your public API contracts.

Key Takeaways

Implementing REST API versioning doesn’t have to be complicated. URL path versioning provides the best balance of clarity and simplicity for most use cases. Key principles to remember:

  1. Start with v1 from day one—even if you don’t expect changes, versioned URLs from the start make future migrations far less painful
  2. Never remove fields from an existing version—only add new ones
  3. Apply semantic versioning discipline—major version bumps for breaking changes, minor for new fields, patch for bug fixes
  4. Document everything—changelogs, deprecation timelines, migration guides
  5. Add deprecation headers proactivelyDeprecation, Sunset, and Link headers give clients machine-readable signal to migrate
  6. Test contracts rigorously—use both positive assertions (v2 has new fields) and negative assertions (v1 does not leak v2 fields)
  7. Monitor version usage—track which versions are still in active use before scheduling sunsets
  8. Plan deprecation early—give clients ample time to migrate before sunsetting old versions
  9. Use tooling—skills like tdd for testing, supermemory for documentation, and pdf for generating client-facing docs

By following these practices, you’ll build APIs that evolve gracefully while maintaining trust with your client developers.


Built by theluckystrike — More at zovo.one