hectoday
DocsCoursesChangelog GitHub
DocsCoursesChangelog GitHub

Access Required

Enter your access code to view courses.

Invalid code

← All courses API versioning and evolution with @hectoday/http

Why versioning

  • Breaking changes
  • The versioning contract
  • Project setup

Versioning strategies

  • URL path versioning
  • Header versioning
  • Query parameter versioning
  • Choosing a strategy

Building versioned APIs

  • Side-by-side versions
  • Version routers
  • Validation per version

Evolving without breaking

  • Additive changes
  • Deprecation
  • Field renaming and removal
  • Changing response shapes

Lifecycle management

  • Sunset policies
  • Monitoring version usage
  • Checklist

Sunset policies

How long is long enough?

You launched v2. v1 has deprecation headers on every response. Clients have been warned. But when can you actually turn v1 off?

That’s what a sunset policy defines. It’s your documented promise to clients about how long deprecated versions will remain available before they’re gone for good.

Common timelines

6 months. The most common timeline for public APIs. It’s long enough for most teams to plan, implement, test, and deploy a migration. Many SaaS companies use this.

12 months. For large enterprise APIs with slow-moving clients. Enterprise procurement cycles, compliance reviews, and testing processes take months. Government and financial APIs often need this much lead time.

3 months. For internal APIs where all clients are under your control. You can coordinate directly with the teams involved, so the timeline can be shorter.

“Forever” (no sunset). Some APIs never remove old versions. AWS and Google Cloud keep old API versions running indefinitely. This avoids breaking anyone but means you’re maintaining every version forever. The maintenance burden grows over time.

Defining a policy

A sunset policy should be documented and consistent. Here’s what one looks like:

API Version Lifecycle Policy:

- New versions are announced at least 6 months before the previous
  version is sunset.
- Deprecated versions include Deprecation and Sunset headers in
  every response.
- Deprecated versions are fully functional until the sunset date.
- After the sunset date, deprecated versions return 410 Gone.
- Emergency deprecations (security vulnerabilities) may have
  shorter timelines.

The key is consistency. Clients should know what to expect. If your policy says 6 months, honor that. Changing the timeline after the fact erodes trust.

The sunset response

After the sunset date, v1 endpoints should return 410 Gone:

// After June 1, 2025: v1 is sunset
route.get("/v1/books", {
  resolve: () => {
    return Response.json(
      {
        error: {
          code: "VERSION_SUNSET",
          message: "API v1 has been sunset. Please migrate to v2.",
          documentation: "https://api.example.com/docs/v2-migration",
        },
      },
      { status: 410 },
    );
  },
});

Why 410 Gone instead of 404 Not Found? Because they mean different things. 404 means “this resource doesn’t exist,” and the client might think the URL is wrong and try variations. 410 means “this existed but has been permanently removed on purpose.” It tells the client: stop trying, this is gone. The error body includes migration instructions so the client knows where to go next.

Automating the sunset

You don’t want to rewrite every v1 route on sunset day. Instead, check the date in onRequest and throw a Response to short-circuit the request. The onError callback catches it and returns it directly to the client:

Code along
const V1_SUNSET_DATE = new Date("2025-06-01T00:00:00Z");

export const app = setup({
  onRequest: ({ request }) => {
    const url = new URL(request.url);
    const version = url.pathname.startsWith("/v2") ? "v2" : "v1";

    // Enforce sunset
    if (version === "v1" && new Date() >= V1_SUNSET_DATE) {
      throw Response.json(
        {
          error: {
            code: "VERSION_SUNSET",
            message: "API v1 was sunset on June 1, 2025. Please migrate to v2.",
            documentation: "https://api.example.com/docs/v2-migration",
          },
        },
        { status: 410 },
      );
    }

    return { apiVersion: version };
  },

  onError: ({ error }) => {
    if (error instanceof Response) return error;
    return Response.json(
      { error: { code: "INTERNAL_ERROR", message: "An unexpected error occurred" } },
      { status: 500 },
    );
  },

  routes: [...v1Routes, ...v2Routes],
});

Let’s walk through this. The onRequest callback runs before every route handler. It checks two things: is this a v1 request, and has the sunset date passed? If both are true, it throws a Response object with a 410 status.

When something is thrown from onRequest, the framework passes it to onError. Our onError handler checks if the thrown value is a Response object. If it is, it returns it directly. That’s how the 410 reaches the client. Without onError, a thrown Response would be swallowed and replaced with a generic 500.

[!NOTE] The Error Handling course covered this pattern in detail. Throwing a Response from a route handler is handled automatically by the framework. But throwing from onRequest goes through onError first, so you need the instanceof Response check.

To test this, temporarily set V1_SUNSET_DATE to a date in the past and restart the server:

curl http://localhost:3000/v1/books

You should get a 410 with the sunset message. Now try v2:

curl http://localhost:3000/v2/books

v2 still works normally. After the sunset date, every v1 request gets a 410 automatically. No route changes needed. You set the date once, add the onError handler, and the enforcement happens by itself. Remember to set the date back to the real sunset date after testing.

Communicating the timeline

Setting headers and automating the response is only half the job. You also need to tell people about it through every channel you have:

Documentation. Update the API docs with the sunset date and migration guide.

Email. Notify registered API consumers directly.

Response headers. Every v1 response includes Sunset: Sun, 01 Jun 2025 00:00:00 GMT (we already set this up).

Changelog. Add the deprecation to the API changelog with the sunset date.

Dashboard. If you have a developer portal, show a deprecation banner.

The more channels you use, the fewer surprised developers you’ll have on sunset day.

But before you pull the trigger on a sunset, you need data. How many requests is v1 still getting? Who’s sending them? Is usage going down? The next lesson covers version usage tracking so your sunset decisions are based on numbers, not gut feelings.

Exercises

Exercise 1: Define a sunset policy for your API. Document the timeline and process.

Exercise 2: Implement the 410 Gone response for sunset versions. Test by setting a past sunset date.

Exercise 3: Add sunset enforcement in onRequest. Verify all v1 routes return 410 after the sunset date.

Why return 410 Gone instead of 404 Not Found for sunset API versions?

← Changing response shapes Monitoring version usage →

© 2026 hectoday. All rights reserved.