Gemini API で作るマイクロSaaS は、個人開発者にとって「長く育てやすい収益源」として強い選択肢です。一方で、実際に収益化を回そうとすると、API の従量課金が持つ独特の難しさに直面します。ユーザーが増えるほどコストも増え、使い込まれるほどサーバー費用も重くなる — この構造を理解しないまま価格を決めると、売上は立つのに利益が残らない悲しい状態になります。
ここで扱うのは私自身が Gemini API をベースにしたマイクロSaaS を運営してきて得た、原価を崩さずに続けていくための実装レベルの設計 をまとめます。プラン設計、単価、課金導線、解約防止、サポート運用までを一気通貫で書きます。個人開発者が「3ヶ月で黒字、半年で月10〜30万円」を現実的に目指すときの地図として使えるはずです。
マイクロSaaS の原価を「分解」して見る
価格設計の前提として、Gemini API を使う SaaS の原価構造を分解して理解しておく必要があります。一般的な SaaS と違い、固定費と変動費の比率が逆転しているのが特徴です。
主要な原価は次の4つに分かれます。
Gemini API の呼び出しコスト — 変動費。入力トークン数・出力トークン数・モデル種別で決まる。
ホスティング費用 — 半固定費。Cloudflare Workers や Vercel のような従量課金寄りのインフラなら変動費に近づく。
認証・決済の月額 — 固定費。Stripe 手数料は変動費。
サポート運用の時間コスト — 擬似的な変動費。ユーザー数に比例します。
一般的な SaaS では固定費が大きく、ユーザーが増えるほど1人あたりのコストが薄まります。ところが Gemini API ベースでは、API コストが変動費として直接積み上がるので、ユーザーが増えても薄まらない原価が常に残ります。
この違いを踏まえると、マイクロSaaS の価格設計は「月額でどれだけ取るか」だけでなく、「1ユーザーあたり月にどれだけの API コストが乗るか」をセットで決める必要があります。
1ユーザーあたりの API コストを見える化する
個人開発者が最初に必ずやるべきは、「1ユーザーが1ヶ月にどれくらい API を使うか」を計測可能にすることです。これをやっていない SaaS は、価格を決めたあとで必ずコストショックに遭います。
最小限の実装は次のようになります。
// Gemini API 呼び出しをラップする
import { GoogleGenerativeAI } from "@google/generative-ai" ;
interface UsageLog {
userId : string ;
model : string ;
inputTokens : number ;
outputTokens : number ;
costUsd : number ;
timestamp : number ;
}
const COST_TABLE = {
"gemini-2.5-flash" : { in: 0.000075 , out: 0.0003 }, // per 1k tokens (USD)
"gemini-2.5-pro" : { in: 0.00125 , out: 0.01 },
};
export async function generateWithBilling (
userId : string ,
model : keyof typeof COST_TABLE ,
prompt : string ,
) {
const genAI = new GoogleGenerativeAI (process.env. GEMINI_API_KEY ! );
const client = genAI. getGenerativeModel ({ model });
const result = await client. generateContent (prompt);
const usage = result.response.usageMetadata;
const inputTokens = usage?.promptTokenCount ?? 0 ;
const outputTokens = usage?.candidatesTokenCount ?? 0 ;
const costUsd =
(inputTokens / 1000 ) * COST_TABLE [model].in +
(outputTokens / 1000 ) * COST_TABLE [model].out;
await recordUsage ({
userId, model, inputTokens, outputTokens, costUsd,
timestamp: Date. now (),
});
return result.response. text ();
}
ここで記録した costUsd を、ユーザーごと・プランごとに集計すれば、「どのユーザーがどの程度の原価を乗せているか」が一目で見えるようになります。この可視化ができて初めて、次の価格設計の議論が意味を持ちます。
プラン設計 — 3プラン構成の組み立て方
マイクロSaaS で最初にやるべき価格構造は、次のような3プラン構成が扱いやすいです。
Free : 月5〜10回までの利用。AI コストは月に数十円程度で済むレベル。
Pro : 月額3,000〜5,000円。月50〜200回利用できます。ターゲット層の主力。
Team : 月額10,000〜20,000円。複数人で使える枠+優先サポート。
設計の肝は、Pro プランの上限回数を、典型ユーザーの2倍くらいに設定する ことです。典型ユーザーの2倍に設定しておくと、課金ユーザーのほとんどが上限に達する前に満足できるため、「使い切れなかった」という不満も起きにくく、上限越えのユーザーだけが Team プランに自然に移っていきます。
Free プランは集客のためだけでなく、「本当に自分に合うサービスかを試すため」のプランとして設計します。無料ユーザーが本当に続ける気になるかを自分で判断できる回数、かつ原価が持続可能な回数、この交差点を探ります。
// プラン別の上限と料金
export const PLANS = {
free: {
monthlyQuota: 10 ,
priceJpy: 0 ,
},
pro: {
monthlyQuota: 150 ,
priceJpy: 4800 ,
},
team: {
monthlyQuota: 600 ,
priceJpy: 14800 ,
seats: 3 ,
},
} as const ;
プラン変更時のプロレーション(日割り計算)は Stripe が自動で処理してくれるので、設計上は気にしなくて大丈夫です。ただし「Free → Pro 切り替え直後の付与量」は、フェアに感じてもらえるように「切り替え時点で即座に月の上限分を付与する」設計のほうが満足度が高くなります。
使用量の制限とグレースフルな拒否
上限を超えたユーザーへの対応は、機械的にエラーを返すのではなく、「気持ちよく次のプランへ誘導する」設計にすべきです。この一手間が、解約率と課金継続率の両方に効きます。
export async function checkQuota ( userId : string ) : Promise <{ allowed : boolean ; remaining : number ; plan : string ; }> {
const user = await getUser (userId);
const used = await getMonthlyUsage (userId);
const quota = PLANS [user.plan].monthlyQuota;
return {
allowed: used < quota,
remaining: Math. max ( 0 , quota - used),
plan: user.plan,
};
}
// エンドポイント内での使用
const { allowed , remaining , plan } = await checkQuota (userId);
if ( ! allowed) {
return Response. json ({
error: "QUOTA_EXCEEDED" ,
message: `今月の利用上限(${ PLANS [ plan ]. monthlyQuota }回)に達しました。` ,
upgradeUrl: "/billing/upgrade" ,
nextResetAt: getNextMonthStart (),
}, { status: 402 });
}
上限に達したあとの UI は、単に「使えません」と出すのではなく、「今月はあと〇日で上限がリセットされます」「上位プランなら月〇〇回使えます」「アップグレードはこちら」の3つを同じ画面に並べます。ユーザーが自分で状況を判断して行動できる情報密度が、そのまま CVR に跳ね返ります。
Stripe Webhook と KV ストア — 状態管理の最小構成
個人開発者が運営する SaaS で最もバグりやすいのが、Stripe の状態と自前 DB の状態が食い違う問題です。これを避けるには、「Stripe を主、自前DBを従」という原則を徹底することです。
Webhook で受け取るイベントのうち、必ず処理すべきは次の4つです。
checkout.session.completed: 新規契約発生
customer.subscription.updated: プラン変更・有効期限更新
customer.subscription.deleted: 解約
invoice.payment_failed: 決済失敗(重要)
// 最小構成の Webhook ハンドラー
export async function handleStripeWebhook ( request : Request , env : Env ) {
const sig = request.headers. get ( "stripe-signature" ) ! ;
const body = await request. text ();
const event = stripe.webhooks. constructEvent (body, sig, env. STRIPE_WEBHOOK_SECRET );
switch (event.type) {
case "checkout.session.completed" : {
const session = event.data.object;
await env. USERS . put (session.client_reference_id, JSON . stringify ({
plan: "pro" ,
stripeCustomerId: session.customer,
subscriptionId: session.subscription,
startedAt: Date. now (),
}));
break ;
}
case "customer.subscription.deleted" : {
const sub = event.data.object;
const userId = await findUserByCustomer (sub.customer, env);
if (userId) {
await env. USERS . put (userId, JSON . stringify ({ plan: "free" , canceledAt: Date. now () }));
}
break ;
}
case "invoice.payment_failed" : {
// 即 Free に戻さず、Stripe が再試行する猶予を設ける
const invoice = event.data.object;
await notifyPaymentIssue (invoice.customer_email);
break ;
}
}
return new Response ( "ok" );
}
invoice.payment_failed を即座に解約扱いにすると、クレジットカードの一時的な不具合でユーザーを失うことになります。Stripe は自動で数日かけて再試行するので、その期間はアクセスを維持し、状況だけユーザーに通知するのが運用しやすい設計です。
解約防止の3つの軸
マイクロSaaS の月次解約率は、放っておくと10%前後に膨らむのが一般的です。個人開発者が3〜5% の水準まで下げられるかは、収益の天井を大きく左右します。
解約防止には大きく3つの軸があります。
軸1: オンボーディングの7日以内に「成功体験」を作る。
多くの解約は契約から1〜2ヶ月の間に発生します。その根本は、最初の1週間で「このサービスのおかげで何かが変わった」という体験ができていないことにあります。オンボーディング用のメールを3通(1日目・3日目・7日目)に自動で送り、各通で「今日これを試してみてください」という小さな課題を出すと、成功体験までの距離が短くなります。
軸2: 利用頻度が落ちたユーザーに能動的に声をかける。
前月比で利用回数が50%以下に落ちたユーザーに、短いアンケートメールを送ります。「何か使いにくかった点はありますか?」「どんな機能があったら再び使いたくなりますか?」の2問だけで十分です。返信率は低くても、返ってきた声には金塊が混じっています。
軸3: 解約直前の引き止めを「価値の再提示」で行う。
Stripe Customer Portal で解約ボタンを押す前に、1ページ挟みます。そのページには、過去1ヶ月のそのユーザーの利用実績(「〇件の記事を校正しました」「〇時間の作業を節約しました」)を具体的に出します。数字での再提示は、単なる引き止めメッセージよりもはるかに効きます。
// 解約前の価値再提示ページで使うデータ
export async function getRetentionContext ( userId : string ) {
const last30 = await getUsageRange (userId, thirtyDaysAgo (), Date. now ());
const totalCalls = last30. length ;
const estimatedTimeSavedMin = totalCalls * 20 ; // 1回あたり20分想定
return {
totalCalls,
estimatedTimeSavedMin,
heaviestDay: findHeaviestDay (last30),
};
}
年間プランとロックインの設計
月額プランに慣れてきたら、年間プランを用意します。年間プランは月額の10ヶ月相当(2ヶ月分割引)が無難な設計です。実質20% 割引です。
年間プランがもたらす効果は、売上のキャッシュフロー改善もさることながら、解約率の実質的な低下 です。月次で解約できないぶん、年間契約者は統計的にそのまま使い続ける傾向が強くなります。私のサービスでは、年間契約者の12ヶ月継続率は 70% を超えており、月額プランの同期間残存率より20ポイント以上高い結果になっています。
ただし、年間プランを最初の1ヶ月から押し付けるのは逆効果です。最低でも3ヶ月以上継続したユーザーにのみ、「年間プランに切り替えると2ヶ月分お得」という案内を出します。信頼が築けていない段階で年間契約を迫ると、むしろ警戒感を生みます。
サポート — 1通目の返信が9割
個人開発者の SaaS では、サポートの質がそのまま口コミを決めます。1通目の返信を 24時間以内・具体的・人間的 に出すだけで、ユーザーの印象は大きく変わります。
私が実践しているのは次の3点です。
定型文テンプレをできるだけ減らし、ユーザー名を入れた個別文面にする
「こちらで再現できませんでした」ではなく「〇〇の情報があれば再現できそうです」と次の一歩を一緒に提示する
バグと判明したら、修正完了時に必ず「ご指摘ありがとうございました、修正しました」と返信する
この「完了報告」の返信を徹底するだけで、そのユーザーが勝手に SNS で好意的に言及してくれる確率が目に見えて上がります。マイクロSaaS の成長には、広告よりも口コミのほうが効く — この原則を忘れないでいたいところです。
個人開発者の視点から(実体験メモ)
半年後に振り返るためのメトリクス
運営を始めて半年経った時点で、必ず振り返るべき指標は次の6つです。
| 指標 | 目標値の目安 | 補足 |
|---|---|---|
| 月次解約率 | 5% 以下 | 7% を超えるなら設計見直し |
| 平均LTV | プラン月額 × 20 以上 | 低いならオンボーディング改善 |
| 粗利率 | 70% 以上 | API コスト管理と上限設計の見直し |
| NPS | +20 以上 | 定期的な調査で把握 |
| 新規獲得経路の上位3つ | — | SEO・口コミ・SNS のどれが効いているか |
| 1人あたり月間 API コスト | プラン料金の25% 以下 | 超える場合は上限設計を再考 |
この指標を半年ごとに見直すと、次の6ヶ月に何に集中すべきかが自然と見えてきます。個人開発者のマイクロSaaS は、派手な成長は難しくても、半年ごとに確実に改善する 運営ができる限り、数年のスパンで必ず育っていきます。
Gemini API はまだ新しい市場で、ニッチ特化でも戦える余地がたくさん残っています。この記事の設計を1枚の地図として、自分のサービスの次の半年に使ってみてください。