Docs/plan-api

Plan Management API

プラン管理と変更を行うAPIです。

エンドポイント一覧

メソッドエンドポイント説明認証
GET/api/plan/simulateプラン変更のシミュレーション必須
POST/api/planプランの変更必須
GET/api/admin/planプラン変更のシミュレーション(管理者用)管理者
POST/api/admin/planプランの変更(管理者用)管理者

プラン変更のシミュレーション

プランを変更した場合の影響を事前に確認します。実際にはプランを変更しません。

Endpoint: GET /api/plan/simulate?plan=HOBBY

認証: 必須(セッションまたはAPIキー)

クエリパラメータ:

パラメータ必須説明
planstring新しいプラン (FREE, HOBBY, PRO)

リクエスト例:

curl -X GET "https://your-domain.com/api/plan/simulate?plan=FREE" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

レスポンス例(ダウングレード時):

{
  "success": true,
  "message": "プラン変更のシミュレーション結果",
  "data": {
    "currentPlan": "HOBBY",
    "newPlan": "FREE",
    "isDowngrade": true,
    "jobs": {
      "current": 8,
      "limit": 5,
      "willBeDisabled": 4,
      "jobsToDisable": [
        {
          "id": "job_1",
          "name": "High Frequency Job",
          "reason": "実行間隔 5分 < 最小 30分"
        },
        {
          "id": "job_2",
          "name": "Old Job 1",
          "reason": "ジョブ数制限超過(上限: 5個)"
        },
        {
          "id": "job_3",
          "name": "Old Job 2",
          "reason": "ジョブ数制限超過(上限: 5個)"
        },
        {
          "id": "job_4",
          "name": "Old Job 3",
          "reason": "ジョブ数制限超過(上限: 5個)"
        }
      ]
    },
    "apiKeys": {
      "current": 3,
      "limit": 10,
      "willBeDisabled": 0,
      "keysToDisable": []
    }
  }
}

レスポンス例(アップグレード時):

{
  "success": true,
  "message": "プラン変更のシミュレーション結果",
  "data": {
    "currentPlan": "FREE",
    "newPlan": "HOBBY",
    "isDowngrade": false,
    "jobs": {
      "current": 5,
      "limit": 20,
      "willBeDisabled": 0,
      "jobsToDisable": []
    },
    "apiKeys": {
      "current": 2,
      "limit": 10,
      "willBeDisabled": 0,
      "keysToDisable": []
    }
  }
}

プランの変更

ユーザーのプランを変更します。ダウングレードの場合、制限を超えるリソースが自動的に無効化されます。

Endpoint: POST /api/plan

認証: 必須(セッションまたはAPIキー)

リクエストボディ:

{
  "plan": "HOBBY"
}

フィールド説明:

フィールド必須説明
planstring新しいプラン (FREE, HOBBY, PRO)

リクエスト例:

curl -X POST https://your-domain.com/api/plan \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "plan": "FREE"
  }'

レスポンス例:

{
  "success": true,
  "message": "プランを HOBBY から FREE に変更しました",
  "data": {
    "changed": true,
    "oldPlan": "HOBBY",
    "newPlan": "FREE",
    "restrictions": {
      "plan": {
        "old": "HOBBY",
        "new": "FREE"
      },
      "jobs": {
        "total": 8,
        "disabled": 4,
        "disabledByCount": 3,
        "disabledByInterval": 1,
        "disabledJobIds": [
          "job_1",
          "job_2",
          "job_3",
          "job_4"
        ]
      },
      "apiKeys": {
        "total": 3,
        "disabled": 0,
        "disabledKeyIds": []
      }
    },
    "disabledResources": {
      "jobs": [
        {
          "id": "job_1",
          "name": "High Frequency Job",
          "schedule": "*/5 * * * *",
          "url": "https://api.example.com/endpoint",
          "updatedAt": "2025-11-04T10:00:00.000Z"
        },
        {
          "id": "job_2",
          "name": "Old Job 1",
          "schedule": "0 * * * *",
          "url": "https://api.example.com/endpoint",
          "updatedAt": "2025-11-04T10:00:00.000Z"
        }
      ],
      "apiKeys": []
    }
  }
}

プラン降格時の動作

自動無効化の条件

プランをダウングレードした場合、以下のリソースが自動的に無効化されます:

1. ジョブの無効化

以下の条件のいずれかに該当するジョブが無効化されます:

a) 実行間隔の制限

  • 新しいプランの最小実行間隔を満たさないジョブ
  • 例: HOBBYからFREEにダウングレード(30分未満の間隔のジョブが無効化)

b) ジョブ数の制限

  • 新しいプランのジョブ数上限を超える場合
  • 古い順に自動選択されて無効化
  • 実行間隔で無効化されるジョブは除外してカウント

無効化の優先順位:

  1. 実行間隔が短すぎるジョブ
  2. 作成日時が古いジョブ

2. APIキーの無効化

  • APIキー数が上限(10個)を超える場合
  • 古い順に自動選択されて無効化

プラン別の制限

プランジョブ数最小実行間隔API呼び出し
FREE5個30分100回/日
HOBBY20個5分500回/日
PRO100個1分2,000回/日

無効化されたリソースの確認

無効化されたリソースは以下の方法で確認できます:

# ジョブ一覧を取得(enabled: false のジョブを確認)
curl -X GET https://your-domain.com/api/jobs \
  -H "Authorization: Bearer YOUR_API_TOKEN"

# APIキー一覧を取得(enabled: false のキーを確認)
curl -X GET https://your-domain.com/api/keys \
  -H "Authorization: Bearer YOUR_API_TOKEN"

無効化されたリソースの再有効化

プランをアップグレードしても、自動的には再有効化されません。手動で再有効化する必要があります:

# ジョブを有効化
curl -X PATCH https://your-domain.com/api/jobs/JOB_ID \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"enabled": true}'

管理者用API

プラン変更のシミュレーション(管理者)

Endpoint: GET /api/admin/plan?userId=xxx&plan=HOBBY

認証: 管理者権限(X-Admin-Token ヘッダー)

リクエスト例:

curl -X GET "https://your-domain.com/api/admin/plan?userId=user_123&plan=FREE" \
  -H "X-Admin-Token: YOUR_ADMIN_TOKEN"

プランの変更(管理者)

Endpoint: POST /api/admin/plan

認証: 管理者権限(X-Admin-Token ヘッダー)

リクエストボディ:

{
  "userId": "user_123",
  "plan": "HOBBY"
}

リクエスト例:

curl -X POST https://your-domain.com/api/admin/plan \
  -H "X-Admin-Token: YOUR_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "userId": "user_123",
    "plan": "FREE"
  }'

ユースケース

プラン変更前の確認

// プラン変更の影響を確認
async function checkPlanChange(newPlan) {
  const response = await fetch(
    `https://your-domain.com/api/plan/simulate?plan=${newPlan}`,
    {
      headers: {
        'Authorization': `Bearer ${apiToken}`
      }
    }
  );
  
  const { data } = await response.json();
  
  if (data.isDowngrade) {
    console.log(`警告: ${data.jobs.willBeDisabled} 個のジョブが無効化されます`);
    
    data.jobs.jobsToDisable.forEach(job => {
      console.log(`- ${job.name}: ${job.reason}`);
    });
    
    // ユーザーに確認を求める
    const confirmed = confirm('プランをダウングレードしますか?');
    if (!confirmed) return;
  }
  
  // プランを変更
  await changePlan(newPlan);
}

プランの変更

async function changePlan(newPlan) {
  const response = await fetch('https://your-domain.com/api/plan', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${apiToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ plan: newPlan })
  });
  
  const { data } = await response.json();
  
  if (data.changed) {
    console.log(`プランを変更しました: ${data.oldPlan}${data.newPlan}`);
    
    if (data.restrictions.jobs.disabled > 0) {
      console.log(`${data.restrictions.jobs.disabled} 個のジョブが無効化されました`);
      
      // 無効化されたジョブの一覧を表示
      data.disabledResources.jobs.forEach(job => {
        console.log(`- ${job.name} (${job.schedule})`);
      });
    }
  }
}

無効化されたジョブの再有効化

async function reEnableJobs() {
  // すべてのジョブを取得
  const response = await fetch('https://your-domain.com/api/jobs', {
    headers: { 'Authorization': `Bearer ${apiToken}` }
  });
  
  const { data: jobs } = await response.json();
  
  // 無効化されているジョブを抽出
  const disabledJobs = jobs.filter(job => !job.enabled);
  
  console.log(`${disabledJobs.length} 個の無効化されたジョブが見つかりました`);
  
  // 再有効化するジョブを選択
  for (const job of disabledJobs) {
    const shouldEnable = confirm(`"${job.name}" を有効化しますか?`);
    
    if (shouldEnable) {
      await fetch(`https://your-domain.com/api/jobs/${job.id}`, {
        method: 'PATCH',
        headers: {
          'Authorization': `Bearer ${apiToken}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ enabled: true })
      });
      
      console.log(`"${job.name}" を有効化しました`);
    }
  }
}

エラーレスポンス

400 Bad Request

無効なプランが指定されました。

{
  "success": false,
  "message": "有効なプランを指定してください: FREE, HOBBY, PRO",
  "data": null
}

401 Unauthorized

認証が必要です。

{
  "success": false,
  "message": "認証が必要です",
  "data": null
}

500 Internal Server Error

サーバーエラーが発生しました。

{
  "success": false,
  "message": "プラン変更に失敗しました",
  "data": null
}