初めてのSaaSビルダー向けに実用的にまとめた公開API設計:バージョニング、ページネーション、レート制限、ドキュメント、素早く出せる小さなSDKの選び方。

公開APIは単にアプリが外に出すエンドポイントではありません。チーム外の人々に対して「この契約は変わっても動き続けます」という約束です。
難しいのは v1 を書くことではなく、バグ修正や機能追加、顧客の実際のニーズを学ぶ間に安定させておくことです。
初期の選択は後になってサポートチケットとして現れます。レスポンスの形が予告なく変わったり、命名が一貫していなかったり、クライアントがリクエストの成功を判別できないと、摩擦が生まれます。その摩擦は不信感に変わり、不信感は人々があなたの上に構築するのをやめさせます。
スピードも重要です。多くの初めてのSaaSビルダーはまず役に立つものを早く出し、その後改善します。トレードオフは単純です:ルールなしで早く出すほど、実際のユーザーが来たときにそれらの決定をやり直す時間が増えます。
v1 に対して「十分良い」状態は通常、実際のユーザー操作に対応する少数のエンドポイント、一貫した命名とレスポンス形、明確な変更戦略(たとえそれが "v1" だけでも)、予測可能なページネーションと合理的なレート制限、そして何を送れば何が返ってくるかを正確に示すドキュメントです。
具体例:ある顧客が夜間に請求書を作成する統合を構築したとします。後でフィールド名を変えたり、日付フォーマットを変更したり、部分的な結果を密かに返すようになると、彼らのジョブは午前2時に失敗します。彼らは自分のコードではなくあなたのAPIを責めるでしょう。
Koder.ai のようなチャット駆動のツールで多くのエンドポイントを素早く生成するのは魅力的ですが、公開する表面は小さく保ってください。内部用のエンドポイントを非公開にして学習期間中に何を長期的契約にするか決められます。
良い公開API設計は、顧客が製品について話す方法に合う少数の名詞(リソース)を選ぶことから始まります。内部データベースが変わってもリソース名は安定させてください。機能を追加するときは、コアリソースの名前を変えるよりフィールドを追加するか新しいエンドポイントを追加することを優先します。
多くのSaaS製品で実用的な出発点は:users、organizations、projects、events です。あるリソースを一文で説明できないなら、それは公開に向いていない可能性があります。
HTTPの使い方は退屈で予測可能に保ちましょう:
認証は最初から凝らなくて構いません。APIが主にサーバー間(顧客がバックエンドから呼ぶ)であれば、APIキーで十分なことが多いです。個々のエンドユーザーとして動く必要がある場合や、ユーザーがアクセスを許可するサードパーティ統合を想定するなら OAuth の方が適切です。誰が呼び出すのか、誰のデータに触れるのかを平易な言葉で書いてください。
早い段階で期待値を設定してください。サポート対象とベストエフォートを明確に区別します。例えば:一覧エンドポイントは安定して後方互換性があるが、検索フィルタは拡張される可能性があり網羅的ではない、といった具合です。これによりサポートチケットを減らし、改善の自由度が保たれます。
Koder.aiのようなプラットフォーム上で構築するなら、APIをプロダクトとして扱い、まずは契約を小さくし、実際の使用に基づいて伸ばしてください。
バージョニングは主に期待値の問題です。クライアントは「来週私の統合は壊れるのか?」を知りたい。あなたは改善の余地が欲しい。
ヘッダーによるバージョニングは見た目がすっきりしますが、ログやキャッシュ、サポートのスクリーンショットに隠れやすいです。URLバージョニングは通常もっとシンプルです:/v1/...。顧客が失敗リクエストを送ったときバージョンがすぐ分かり、v1 と v2 を並行して動かすのも簡単です。
正しく動作するクライアントがコードを変えずに動かなくなるなら、それは破壊的変更です。一般的な例:
customer_id を customerId にする)安全な変更は古いクライアントが無視できるものです。新しいオプションのフィールドを追加するのは通常安全です。例:GET /v1/subscriptions のレスポンスに plan_name を追加しても、status しか読んでいないクライアントは壊れません。
実践的なルール:同じメジャーバージョンの中でフィールドを削除したり、再利用したりしないでください。新しいフィールドを追加し、古いものは残し、バージョン全体を廃止する準備ができたときに退役させます。
シンプルに保ちます:廃止を早めに告知し、レスポンスに分かりやすい警告を返し、終了日を設定します。最初のAPIでは90日間の猶予が現実的です。その間は v1 を動かし続け、短い移行ノートを公開し、サポートが一文で説明できるようにします:v1 はこの日まで動きます;v2 で何が変わったか、という具合です。
Koder.ai のようなプラットフォームで構築するなら、APIバージョンをスナップショットのように扱ってください:改善は新しいバージョンで出荷し、古いものは安定させ、顧客に移行の時間を与えた後に切り捨てます。
ページネーションは信頼を勝ち取るか失うかの分かれ目です。リクエスト間で結果が跳んだりすると、人々はAPIを信用しなくなります。
データセットが小さくクエリが単純でユーザーがページ3を見に行くような場合は page/limit を使い、リストが大きく新しいアイテムが頻繁に入るかソートやフィルタが多い場合はカーソルベースを使います。カーソルは新しいレコードが追加されてもシーケンスを安定させます。
ページネーションを信頼できるものにするためのルール:
created_at desc)。Totals(総数)は扱いが難しいです。total_count はフィルタ付きの大きなテーブルでは高コストになりえます。安価に提供できるなら含め、できないなら省くかクエリフラグでオプションにしてください。
ここにシンプルなリクエスト/レスポンスの形を示します。
// Page/limit
GET /v1/invoices?page=2\u0026limit=25\u0026sort=created_at_desc
{
"items": [{"id":"inv_1"},{"id":"inv_2"}],
"page": 2,
"limit": 25,
"total_count": 142
}
// Cursor-based
GET /v1/invoices?limit=25\u0026cursor=eyJjcmVhdGVkX2F0IjoiMjAyNi0wMS0wOVQxMDozMDowMFoiLCJpZCI6Imludl8xMDAifQ==
{
"items": [{"id":"inv_101"},{"id":"inv_102"}],
"next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wMS0wOVQxMDoyNTowMFoiLCJpZCI6Imludl8xMjUifQ=="
}
レート制限は厳しくすることが目的ではなく、オンライン状態を保つことが目的です。トラフィックの急増からアプリを守り、高価なクエリを頻繁に投げるのを防ぎ、インフラ費用の驚きを避けます。制限は契約でもあり、クライアントは通常の使用量がどれくらいかを知れます。
まずはシンプルに始めて後で調整します。典型的な使用をカバーし短いバーストに余裕を持たせるものを選び、実際のトラフィックを観察してください。データがなければ安全なデフォルトは API キーごとに 60 requests/minute と小さなバースト許容です。あるエンドポイントが重い(検索やエクスポートなど)なら、すべてのリクエストを罰する代わりに、そのエンドポイントに厳しい制限や別のコストルールを設けてください。
制限を適用するときは、クライアントが正しい行動を取りやすくします。429 Too Many Requests を返し、いくつかの標準ヘッダーを含めてください:
X-RateLimit-Limit: ウィンドウ内で許可される最大X-RateLimit-Remaining: 残り回数X-RateLimit-Reset: ウィンドウがリセットされる時刻(タイムスタンプか秒数)Retry-After: 再試行まで待つ時間クライアントは 429 を通常の状態として扱うべきで、戦うエラーと考えるべきではありません。穏やかなリトライパターンは両者を幸せにします:
Retry-After がある場合はそれを待つ例:顧客が夜間の同期でAPIを激しく叩く場合、ジョブは1分間にリクエストを分散し、429 を受けたら自動的に遅くすることで全体が失敗するのを防げます。
エラーが読みにくいとサポートチケットはすぐに山になります。1つのエラー形に統一し、500系でも同じ形を使ってください。シンプルな標準は:code、message、details、そしてサポートに貼れる request_id です。
小さく予測可能なフォーマットの例:
{
"error": {
"code": "validation_error",
"message": "Some fields are invalid.",
"details": {
"fields": [
{"name": "email", "issue": "must be a valid email"},
{"name": "plan", "issue": "must be one of: free, pro, business"}
]
},
"request_id": "req_01HT..."
}
}
HTTPステータスコードはいつも同じ意味で使ってください:入力不正は400、認証がないまたは無効は401、認可がないのは403、リソースが見つからないのは404、競合(重複や状態不整合)は409、レート制限は429、サーバーエラーは500。整合性は工夫より重要です。
バリデーションエラーは直しやすくしてください。フィールドレベルのヒントは内部DB列名ではなくドキュメントで使う正確なパラメータ名を指すべきです。形式要件(日付、通貨、列挙型)があるなら受け付ける形式を示し、例を出してください。
リトライは多くのAPIが誤って重複データを作るポイントです。重要な POST アクション(支払い、請求書作成、メール送信など)には冪等キーをサポートして安全に再試行できるようにします。
Idempotency-Key ヘッダーを受け付ける。この一つのヘッダーでネットワークが不安定な場合やタイムアウトが起きた場合の多くの厄介な境界条件が防げます。
あなたが projects、users、invoices の3つの主要オブジェクトを持つシンプルなSaaSを運営しているとします。プロジェクトには多くのユーザーがいて、各プロジェクトには月次の請求書が発生します。クライアントは請求書を会計ツールに同期したり、自分のアプリ内で基本的な請求情報を表示したりしたい。
クリーンな v1 は例えば以下のようになります:
GET /v1/projects/{project_id}
GET /v1/projects/{project_id}/invoices
POST /v1/projects/{project_id}/invoices
ここで破壊的変更が起きるとします。v1 では請求額をセント単位の整数で保存していた:amount_cents: 1299。後にマルチカレンシーや小数が必要になり、amount: "12.99" と currency: "USD" にしたいとします。古いフィールドを上書きしてしまうと既存の統合はすべて壊れます。バージョニングがあれば慌てる必要はありません:v1 は安定させ、/v2/... を出して新フィールドを導入し、クライアントが移行するまで両方をサポートします。
請求書一覧には予測可能なページネーション形を使います。例えば:
GET /v1/projects/p_123/invoices?limit=50\u0026cursor=eyJpZCI6Imludl85OTkifQ==
200 OK
{
"data": [ {"id":"inv_1001"}, {"id":"inv_1000"} ],
"next_cursor": "eyJpZCI6Imludl8xMDAwIn0="
}
ある日、顧客がループで請求書をインポートしてレート制限に引っかかりました。ランダムな失敗ではなく、彼らは明確なレスポンスを受け取ります:
429 Too Many RequestsRetry-After: 20{ "error": { "code": "rate_limited" } }クライアント側は20秒待ってから同じ cursor で続行でき、すべてを再ダウンロードしたり重複請求書を作る必要はありません。
v1 のローンチはエンドポイントの束ではなく小さなプロダクトリリースとして扱うと上手くいきます。目標はシンプル:人々がそれに構築でき、あなたは驚きなく改善を続けられることです。
まずは API の目的と非目的を説明する1ページを書きます。表面積を一言で説明できるくらい小さくしてください。
順序は以下の通り。各ステップが十分でない限り次に進まないでください:
コード生成ワークフロー(例:Koder.ai を使ってエンドポイントとレスポンスをスキャフォールドする)を使う場合でも、偽クライアントテストはやってください。生成コードは見た目は正しくても使いにくいことがあります。
その対価はサポートメールの減少、ホットフィックスの減少、そして実際に保守できる v1 です。
最初のSDKは第二のプロダクトではありません。HTTP API の薄いフレンドリーなラッパーだと考えてください。一般的な呼び出しを簡単にする一方で、APIの仕組みを隠しすぎないこと。まだラップしていない機能が必要なら、生のリクエストに戻れることが重要です。
顧客が実際に使っている言語に基づいて1つの言語を選んでください。多くのB2B SaaS APIでは JavaScript/TypeScript か Python が一般的です。1つのしっかりした SDK を出す方が、3つの中途半端なものを出すより価値があります。
初期に良いセットは:
手作りでも OpenAPI から生成してもよいです。生成は正確な仕様があり型の一貫性が欲しいときに優れていますが、多くのコードを生み出すことが多いです。初期段階では手書きの最小クライアントに OpenAPI ファイルでドキュメントを添えるだけで十分なことが多いです。公開 SDK インターフェースが安定していれば、後で生成クライアントに切り替えてもユーザーを壊さずにできます。
API バージョンは互換性ルールに従い、SDK バージョンはパッケージングルールに従います。
新しいオプションパラメータやエンドポイントを追加するのは通常マイナーな SDK バンプで良いです。メソッド名の変更やデフォルトの変更など SDK 自体の破壊的変更はメジャーリリースにしてください。これによりアップグレードが穏やかになりサポートチケットが減ります。
ほとんどのAPIサポートチケットはバグではなく驚きについてです。公開API設計の本質は退屈で予測可能であることです。クライアントコードが月ごとに問題なく動き続けることを目指します。
信頼を失う一番の早道は、誰にも言わずにレスポンスを変えることです。フィールド名を変える、型を変える、値の代わりに null を返すようになる――これらはクライアントを壊します。どうしても振る舞いを変える必要があるなら、バージョンを切るか新しいフィールドを追加してしばらく古いものを残し、明確なサンセット計画を示してください。
ページネーションも繰り返し問題になります。一つのエンドポイントが page/pageSize、別のエンドポイントが offset/limit、別のがカーソルでそれぞれデフォルトが違うと問題が起きます。v1 では一つのパターンを選んで全体に適用してください。ソートも安定させて、次ページでアイテムが飛んだり重複したりしないようにします。
エラーの不一致も多くの往復を生みます。あるサービスは { "error":"..." } を返し、別のサービスは { "message":"..." } を返すとクライアントはエンドポイントごとに特別なハンドラを作る羽目になります。
以下は最も長いメールスレッドを生む5つのミスです:
簡単な習慣が役立ちます:すべてのレスポンスに request_id を含め、すべての 429 に再試行の説明を入れること。
公開前に一貫性に焦点を当てた最終チェックを行ってください。ほとんどのサポートは小さな詳細の不一致から起きます。
よく問題を見つけるチェック:
ローンチ後は、期待ではなく実際に人々が叩いているものを監視してください。最初は小さなダッシュボードと週次レビューで十分です。
まず監視すべき信号:
フィードバックを集めてもすべてを書き換えないでください。ドキュメントに問題報告の短い経路を用意し、各報告にエンドポイント、request id、クライアントバージョンをタグ付けしてください。修正する際は加法的な変更(新しいフィールド、新しいオプションパラメータ、新エンドポイント)を優先し、既存の振る舞いを壊すのは避けます。
次のステップ:あなたのリソース、バージョニング計画、ページネーションルール、エラー形式をまとめた1ページのAPI仕様を書きます。その後ドキュメントと認証+2〜3コアエンドポイントをカバーする小さなスターターSDKを作ります。もっと速く進めたいなら、チャットベースの計画(Koder.ai のようなツールのプランニングモード)で仕様、ドキュメント、スターターSDK を下書きしてから生成しても良いでしょう。
Start with 5–10 endpoints that map to real customer actions.
A good rule: if you can’t explain a resource in one sentence (what it is, who owns it, how it’s used), keep it private until you learn more from usage.
Pick a small set of stable nouns (resources) customers already use in conversation, and keep those names stable even if your database changes.
Common starters for SaaS are users, organizations, projects, and events—then add more only when there’s clear demand.
Use the standard meanings and be consistent:
GET = read (no side effects)POST = create or start an actionPATCH = partial updateDELETE = remove or disableThe main win is predictability: clients shouldn’t guess what a method does.
Default to URL versioning like /v1/....
It’s easier to see in logs and screenshots, easier to debug with customers, and simpler to run v1 and v2 side by side when you need a breaking change.
A change is breaking if a correct client can fail without changing their code. Common examples:
Adding a new optional field is usually safe.
Keep it simple:
A practical default is a 90-day window for a first API, so customers have time to migrate without panic.
Pick one pattern and stick to it across all list endpoints.
Always define a default sort and a tie-breaker (like created_at + ) so results don’t jump around.
Start with a clear per-key limit (for example 60 requests/minute plus a small burst), then adjust based on real traffic.
When limiting, return 429 and include:
X-RateLimit-LimitX-RateLimit-RemainingUse one error format everywhere (including 500s). A practical shape is:
code (stable identifier)message (human-readable)details (field-level issues)request_id (for support)Also keep status codes consistent (400/401/403/404/409/429/500) so clients can handle errors cleanly.
If you generate lots of endpoints quickly (for example with Koder.ai), keep the public surface small and treat it as a long-term contract.
Do this before launch:
POST actionsThen publish a tiny SDK that helps with auth, timeouts, retries for safe requests, and pagination—without hiding how the HTTP API works.
idX-RateLimit-ResetRetry-AfterThis makes retries predictable and reduces support tickets.