api-versioning
Use when designing or modifying APIs. Use when adding breaking changes. Use when clients depend on API stability.
Works with
---
name: api-versioning
description: Use when designing or modifying APIs. Use when adding breaking changes. Use when clients depend on API stability.
license: MIT
---
# API Versioning
## Overview
**Version your APIs from day one. Never break existing clients.**
Breaking changes without versioning destroy client trust. Version APIs explicitly, support multiple versions gracefully, and deprecate with ample warning.
## When to Use
- Designing new API endpoints
- Adding or modifying existing endpoints
- Making changes that could break clients
- Planning API evolution strategy
- Reviewing API design decisions
## The Iron Rule
```
NEVER make breaking changes to a released API version.
```
**No exceptions:**
- Not for "it's a bug fix"
- Not for "nobody uses that field"
- Not for "we'll notify clients"
- Not for "it's just internal"
- Not for "the old way was wrong"
**Breaking changes require a new version. Always.**
## Detection: What's a Breaking Change?
| Change | Breaking? | Safe Alternative |
|--------|-----------|------------------|
| Removing a field | YES | Deprecate, keep returning |
| Renaming a field | YES | Add new, keep old |
| Changing field type | YES | Add new field |
| Changing response structure | YES | New version |
| Adding required parameter | YES | Make optional with default |
| Removing endpoint | YES | Deprecate, maintain |
| Changing error codes | YES | Add new codes, keep old |
| Adding optional field | NO | Safe to add |
| Adding new endpoint | NO | Safe to add |
| Adding optional parameter | NO | Safe to add |
## Versioning Strategies
### 1. URL Path Versioning (Recommended)
```typescript
// ✅ CORRECT: Version in URL path
// /api/v1/users
// /api/v2/users
app.get('/api/v1/users', handleUsersV1);
app.get('/api/v2/users', handleUsersV2);
```
**Pros:** Explicit, cacheable, easy to route
**Cons:** URL proliferation
### 2. Header Versioning
```typescript
// ✅ CORRECT: Version in Accept header
// Accept: application/vnd.api+json; version=1
app.get('/api/users', (req, res) => {
const version = parseVersion(req.headers.accept);
if (version === 1) return handleUsersV1(req, res);
if (version === 2) return handleUsersV2(req, res);
return res.status(406).json({ error: 'Unsupported version' });
});
```
**Pros:** Clean URLs
**Cons:** Hidden, harder to test, caching complexity
### 3. Query Parameter Versioning
```typescript
// ✅ ACCEPTABLE: Version as query param
// /api/users?version=1
app.get('/api/users', (req, res) => {
const version = parseInt(req.query.version) || LATEST_VERSION;
// ...
});
```
**Pros:** Simple
**Cons:** Optional parameter often forgotten
## Correct Version Evolution Pattern
```typescript
// Version 1: Original API
interface UserV1 {
id: string;
name: string; // Full name
email: string;
}
// Version 2: Split name into parts (BREAKING!)
interface UserV2 {
id: string;
firstName: string; // New field
lastName: string; // New field
email: string;
}
// ✅ CORRECT: Support both versions
class UserController {
async getUserV1(id: string): Promise<UserV1> {
const user = await this.userService.getUser(id);
return {
id: user.id,
name: `${user.firstName} ${user.lastName}`, // Compute for v1 clients
email: user.email,
};
}
async getUserV2(id: string): Promise<UserV2> {
const user = await this.userService.getUser(id);
return {
id: user.id,
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
};
}
}
// ❌ WRONG: Silently change v1 response
// ❌ WRONG: Force all clients to update simultaneously
// ❌ WRONG: Remove v1 without deprecation period
```
## Deprecation Protocol
Never surprise clients. Follow this:
```typescript
// 1. Announce deprecation (headers + docs)
res.setHeader('Deprecation', 'true');
res.setHeader('Sunset', 'Sat, 01 Jan 2025 00:00:00 GMT');
res.setHeader('Link', '</api/v2/users>; rel="successor-version"');
// 2. Log usage to track migration
logger.info('Deprecated v1 endpoint called', {
endpoint: '/api/v1/users',
clientId: req.clientId,
});
// 3. Maintain for deprecation period (minimum 6 months for external APIs)
// 4. Return 410 Gone after sunset date
if (isPastSunset('/api/v1/users')) {
return res.status(410).json({
error: 'This API version has been retired',
migration: 'https://docs.api.com/migration-v1-to-v2',
successor: '/api/v2/users',
});
}
```
## Pressure Resistance Protocol
### 1. "Just Ship It, We'll Version Later"
**Pressure:** "We need to launch, versioning can wait"
**Response:** Adding versioning to an existing API is 10x harder than starting with it. Clients already depend on the unversioned endpoints.
**Action:** Add `/v1/` prefix now. Takes 5 minutes.
### 2. "It's Internal, We Control All Clients"
**Pressure:** "We can just update all our services"
**Response:** Internal APIs become external. Services can't update simultaneously. Deployments fail mid-rollout.
**Action:** Version internal APIs too. Your future self will thank you.
### 3. "It's a Bug Fix, Not a Breaking Change"
**Pressure:** "The old behavior was wrong"
**Response:** Clients may depend on the "wrong" behavior. A fix can break them.
**Action:** Fix in new version. Document the fix. Let clients opt-in.
### 4. "Nobody Uses That Field"
**Pressure:** "Analytics show it's unused"
**Response:** Analytics might be wrong. One client relying on it = breaking change.
**Action:** Deprecate with warning, keep returning the field, sunset after migration period.
### 5. "We'll Just Notify Clients"
**Pressure:** "We'll email everyone before the change"
**Response:** Emails get missed. Clients need deploy time. Surprise breaks cause outages.
**Action:** Deprecation headers + sunset period + new version.
## Red Flags - STOP and Reconsider
If you notice ANY of these, you're about to break clients:
- Removing or renaming fields without new version
- Changing response structure "to improve it"
- "Nobody will notice this change"
- No version in API path or headers
- Only one version supported
- Deprecating without sunset date
- Changing semantics (same field, different meaning)
**All of these mean: Create a new API version.**
## Version Lifecycle Management
```
┌─────────┐ ┌─────────────┐ ┌────────────┐ ┌───────┐
│ Alpha │ ──► │ Released │ ──► │ Deprecated │ ──► │ Sunset│
└─────────┘ └─────────────┘ └────────────┘ └───────┘
Breaking OK No breaking Warn clients 410 Gone
Support 2-3 ver 6+ months Remove code
```
| Phase | Breaking Changes | Client Action |
|-------|------------------|---------------|
| Alpha (v0.x) | Allowed with notice | Expect instability |
| Released (v1+) | Never | Rely on stability |
| Deprecated | None | Migrate to successor |
| Sunset | N/A | Endpoint returns 410 |
## Common Rationalizations (All Invalid)
| Excuse | Reality |
|--------|---------|
| "We'll add versioning later" | Later = breaking existing clients. |
| "It's internal only" | Internal becomes external. Version anyway. |
| "Small change, won't break anything" | Small changes break clients constantly. |
| "We'll coordinate the update" | Coordination fails. Services deploy independently. |
| "It's just a rename" | Renames break clients that parse responses. |
| "The docs explain the change" | Clients don't re-read docs. Code breaks. |
| "Analytics show no usage" | One client matters. Analytics miss things. |
## Quick Reference
| Scenario | Action |
|----------|--------|
| New API | Start with `/v1/` immediately |
| Adding field | Add to current version (non-breaking) |
| Removing field | New version + deprecate old |
| Renaming field | Add new name, keep old, deprecate old |
| Changing structure | New version |
| Bug that changes behavior | Fix in new version |
| Deprecating version | Announce + 6mo minimum + sunset date |
## The Bottom Line
**Version from day one. Never break released versions. Deprecate gracefully.**
When pressured to "just change it" or "version later": add versioning now, create new version for breaking changes, give clients time to migrate. API stability is a contract—don't break it.More API Design skills
lark-event
larksuite/cli
Lark/Feishu real-time event listening / subscribing / consuming: stream events as NDJSON via `lark-cli event consume <EventKey>` (covers IM messages/reactions/chat changes, Approval status changes, Task updates, VC meeting started/joined/ended, Minutes generated, Whiteboard updated, etc.). Use for Lark bots, real-time message processing, long-running subscribers, streaming webhook/push handlers. Supports `--max-events` / `--timeout` bounded runs and a stderr ready-marker contract — designed for AI agents running as subprocesses.
lark-contact
larksuite/cli
飞书 / Lark 通讯录:按姓名 / 邮箱解析成 open_id,或按 open_id 反查姓名 / 部门 / 邮箱 / 联系方式 / 个人状态 / 签名,以及按关键词搜索当前用户可见的机器人 / 智能体(agent)。当用户提到一个名字要下一步发消息 / 排日程,或拿到 open_id 想查具体信息时使用。不负责部门树遍历、按部门列员工、组织架构图,这类需求走原生 OpenAPI。
lark-openapi-explorer
larksuite/cli
飞书/Lark 原生 OpenAPI 探索:从官方文档库中挖掘未经 CLI 封装的原生 OpenAPI 接口。当用户的需求无法被现有 lark-* skill 或 lark-cli 已注册命令满足,需要查找并调用原生飞书 OpenAPI 时使用。

