DEV Community

Cover image for Heroku API の使い方:完全統合ガイド (2026年)
Akira
Akira

Posted on • Originally published at apidog.com

Heroku API の使い方:完全統合ガイド (2026年)

TL;DR

Heroku API を使うと、デプロイ自動化やアプリ管理、アドオン構成、インフラのスケーリングをプログラムで実現できます。OAuth 2.0/トークン認証で、アプリ・ダイノ・ビルド・パイプライン操作が RESTful エンドポイントで可能。レート制限はアカウント単位で1時間10,000リクエスト。本記事では、認証設定から主要エンドポイント、CI/CD連携、本番デプロイ戦略まで具体的な手順とコード例で解説します。

今すぐ Apidog を試す

はじめに

Heroku は世界170カ国・400万超のアプリを支えるPaaSです。デプロイ自動化やCI/CDパイプライン運用、多数のアプリ管理を目指す開発者にとって、Heroku API の活用は必須です。

実際、10以上の Heroku アプリを運用しているチームは、手動デプロイや設定変更に毎週8〜12時間を消費しがち。API統合で、デプロイの自動化・トラフィックに応じたダイノスケール・環境間の設定同期が短時間で実現できます。

本記事は、Heroku API の認証、アプリ/ダイノ/ビルド/パイプライン/アドオン管理からエラートラブル対応まで、実装重視で徹底解説します。読み終わる頃には、本番対応の Heroku API 連携が即実装できるはずです。

💡 Apidog は Heroku API テストや認証フロー検証、レスポンス検査、設定デバッグを一元管理できます。API 仕様インポート、モックレスポンス、チームシェアも強力にサポート。

Heroku API とは?

Heroku API は RESTful プラットフォーム API として、Heroku 上のアプリ・インフラ管理を自動化できます。

  • アプリ作成/設定/削除
  • ダイノのスケーリング・プロセス管理
  • ビルド/リリース管理
  • アドオンプロビジョニング
  • パイプライン管理・昇格
  • カスタムドメイン・SSL証明書管理
  • ログ/監視・チーム管理

主な機能

機能 説明
RESTful デザイン JSONレスポンス・標準HTTPメソッド
トークン認証 OAuth 2.0/ベアラートークン対応
レンジリクエスト 大量データはページネーション対応
レート制限 アカウント単位で1時間10,000リクエスト
멱等な作成 書き込み系は安全なリトライ設計
Gzip 圧縮 帯域節約のためレスポンス圧縮

API アーキテクチャ概要

  • ベースURL: https://api.heroku.com/
  • JSON:API 仕様に準拠、リソース/関係性パターンが統一

API バージョン比較

バージョン ステータス 認証 ユースケース
プラットフォーム API (v3) 現在 ベアラートークン すべての新規統合
GitHub 統合 現在 OAuth 2.0 GitHub連携アプリ
コンテナレジストリ 現在 Docker認証 コンテナデプロイ

はじめに:認証設定

ステップ1:Heroku アカウント作成

  1. Heroku公式サイトへアクセス
  2. 「Sign Up」でアカウント登録
  3. メール認証
  4. プロファイル設定

Herokuサインアップページ

ステップ2:Heroku CLI インストール

CLIでAPIトークン生成やコマンドテストが可能。

# macOS
brew tap heroku/brew && brew install heroku

# Windows
npm install -g heroku

# Linux
curl https://cli-assets.heroku.com/install.sh | sh
Enter fullscreen mode Exit fullscreen mode

ステップ3:API トークン生成

# ログイン(ブラウザで認証)
heroku login

# 一時トークン発行
heroku authorizations:create --short-lived

# CI/CD用途など長期トークン
heroku authorizations:create --description "CI/CD Pipeline" --expires-in "1 year"
Enter fullscreen mode Exit fullscreen mode

セキュリティ: トークンは .env 等の環境変数で管理し、コードに直書きしないこと。

# .env
HEROKU_API_KEY="your_api_key_here"
HEROKU_APP_NAME="your-app-name"
Enter fullscreen mode Exit fullscreen mode

ステップ4:トークン認証ヘッダー

全APIリクエストで以下ヘッダーを必須指定。

Authorization: Bearer {api_key}
Accept: application/vnd.heroku+json; version=3
Enter fullscreen mode Exit fullscreen mode

ステップ5:最初のAPIコール

認証テスト例:

curl -n https://api.heroku.com/account \
  -H "Accept: application/vnd.heroku+json; version=3" \
  -H "Authorization: Bearer $HEROKU_API_KEY"
Enter fullscreen mode Exit fullscreen mode

期待レスポンス例:

{
  "id": "user-id-here",
  "email": "developer@example.com",
  "name": "Developer Name",
  "created_at": "2024-01-15T10:30:00Z",
  "updated_at": "2026-03-20T14:22:00Z"
}
Enter fullscreen mode Exit fullscreen mode

ステップ6:コードで認証実装

Node.js用の再利用APIクライアント例:

const HEROKU_API_KEY = process.env.HEROKU_API_KEY;
const HEROKU_BASE_URL = 'https://api.heroku.com';

const herokuRequest = async (endpoint, options = {}) => {
  const response = await fetch(`${HEROKU_BASE_URL}${endpoint}`, {
    ...options,
    headers: {
      'Authorization': `Bearer ${HEROKU_API_KEY}`,
      'Accept': 'application/vnd.heroku+json; version=3',
      'Content-Type': 'application/json',
      ...options.headers
    }
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Heroku API Error: ${error.message}`);
  }

  return response.json();
};

// 使用例
const account = await herokuRequest('/account');
console.log(`ログイン済みユーザー: ${account.email}`);
Enter fullscreen mode Exit fullscreen mode

アプリケーション管理

新規アプリ作成

const createApp = async (appName, region = 'us') => {
  const response = await herokuRequest('/apps', {
    method: 'POST',
    body: JSON.stringify({ name: appName, region }),
  });
  return response;
};

// 使用例
const app = await createApp('my-awesome-app-2026');
console.log(`作成: ${app.name}`);
console.log(`Git URL: ${app.git_url}`);
console.log(`Web URL: ${app.web_url}`);
Enter fullscreen mode Exit fullscreen mode

レスポンス例

{
  "id": "app-uuid-here",
  "name": "my-awesome-app-2026",
  "region": { "name": "us" },
  "created_at": "2026-03-25T10:00:00Z",
  "updated_at": "2026-03-25T10:00:00Z",
  "git_url": "https://git.heroku.com/my-awesome-app-2026.git",
  "web_url": "https://my-awesome-app-2026.herokuapp.com",
  "owner": { "email": "developer@example.com" },
  "build_stack": { "name": "heroku-24" }
}
Enter fullscreen mode Exit fullscreen mode

アプリ一覧取得

const listApps = async (limit = 50) => {
  return await herokuRequest(`/apps?limit=${limit}`);
};

// 使用例
const apps = await listApps();
apps.forEach(app => console.log(`${app.name} - ${app.web_url}`));
Enter fullscreen mode Exit fullscreen mode

アプリ詳細取得

const getApp = async (appName) => {
  return await herokuRequest(`/apps/${appName}`);
};

// 使用例
const app = await getApp('my-awesome-app-2026');
console.log(`スタック: ${app.build_stack.name}`);
console.log(`リージョン: ${app.region.name}`);
Enter fullscreen mode Exit fullscreen mode

アプリ設定更新

const updateApp = async (appName, updates) => {
  return await herokuRequest(`/apps/${appName}`, {
    method: 'PATCH',
    body: JSON.stringify(updates)
  });
};

// アプリ名変更例
const updated = await updateApp('old-app-name', { name: 'new-app-name' });
Enter fullscreen mode Exit fullscreen mode

アプリ削除

const deleteApp = async (appName) => {
  await herokuRequest(`/apps/${appName}`, { method: 'DELETE' });
  console.log(`アプリケーション ${appName} を削除しました`);
};
Enter fullscreen mode Exit fullscreen mode

ダイノ管理

ダイノのスケーリング

const scaleDyno = async (appName, processType, quantity) => {
  const response = await herokuRequest(`/apps/${appName}/formation/${processType}`, {
    method: 'PATCH',
    body: JSON.stringify({ quantity }),
  });
  return response;
};

// Webダイノを3個にスケール
const formation = await scaleDyno('my-app', 'web', 3);
console.log(`Scaled to ${formation.quantity} ${processType} dynos`);
Enter fullscreen mode Exit fullscreen mode

ダイノ構成取得

const getFormation = async (appName, processType = null) => {
  const endpoint = processType
    ? `/apps/${appName}/formation/${processType}`
    : `/apps/${appName}/formation`;
  return await herokuRequest(endpoint);
};

// 使用例
const formation = await getFormation('my-app');
formation.forEach(proc => console.log(`${proc.type}: ${proc.quantity} dynos (@ ${proc.size})`));
Enter fullscreen mode Exit fullscreen mode

利用可能なダイノサイズ

ダイノタイプ ユースケース 月額費用
eco ホビープロジェクト・デモ 5ドル
basic 小規模本番アプリ 7ドル
standard-1x 標準的なワークロード 25ドル
standard-2x 高性能アプリ 50ドル
performance 重要本番アプリ 250ドル以上
private エンタープライズ分離 カスタム

ダイノ再起動

const restartDynos = async (appName, processType = null) => {
  const endpoint = processType
    ? `/apps/${appName}/formation/${processType}`
    : `/apps/${appName}/dynos`;

  await herokuRequest(endpoint, { method: 'DELETE' });
  console.log(`ダイノが ${appName} で再起動されました`);
};
Enter fullscreen mode Exit fullscreen mode

ワンオフダイノ実行

const runCommand = async (appName, command) => {
  const response = await herokuRequest(`/apps/${appName}/dynos`, {
    method: 'POST',
    body: JSON.stringify({ command, size: 'standard-1x' })
  });
  return response;
};

// 例: データベースマイグレーション
const dyno = await runCommand('my-app', 'npm run migrate');
console.log(`ダイノが開始されました: ${dyno.id}`);
Enter fullscreen mode Exit fullscreen mode

設定変数

環境変数取得

const getConfigVars = async (appName) => {
  return await herokuRequest(`/apps/${appName}/config-vars`);
};

// 使用例
const config = await getConfigVars('my-app');
console.log(`DATABASE_URL: ${config.DATABASE_URL}`);
console.log(`NODE_ENV: ${config.NODE_ENV}`);
Enter fullscreen mode Exit fullscreen mode

環境変数設定

const setConfigVars = async (appName, variables) => {
  return await herokuRequest(`/apps/${appName}/config-vars`, {
    method: 'PATCH',
    body: JSON.stringify(variables)
  });
};

// 使用例
const updated = await setConfigVars('my-app', {
  NODE_ENV: 'production',
  API_SECRET: 'your-secret-key',
  LOG_LEVEL: 'info'
});
Enter fullscreen mode Exit fullscreen mode

環境変数のベストプラクティス

  1. シークレットはコミットしない:すべて環境変数で管理
  2. 環境ごとに変える:ステージング/本番で切替
  3. 定期的なローテーション:APIキーは四半期ごとに更新
  4. プレフィックス付与:例 STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET

ビルドとリリース管理

ビルド作成(API経由デプロイ)

const createBuild = async (appName, sourceBlobUrl) => {
  const response = await herokuRequest(`/apps/${appName}/builds`, {
    method: 'POST',
    body: JSON.stringify({
      source_blob: { url: sourceBlobUrl }
    })
  });
  return response;
};

// 使用例
const build = await createBuild('my-app', 'https://storage.example.com/source.tar.gz');
console.log(`ビルド開始: ${build.id}`);
console.log(`ステータス: ${build.status}`);
Enter fullscreen mode Exit fullscreen mode

ビルドステータス取得・ポーリング

const getBuild = async (appName, buildId) => {
  return await herokuRequest(`/apps/${appName}/builds/${buildId}`);
};

const checkBuildStatus = async (appName, buildId, maxAttempts = 30) => {
  for (let i = 0; i < maxAttempts; i++) {
    const build = await getBuild(appName, buildId);

    if (build.status === 'succeeded') {
      console.log('ビルド成功!');
      return build;
    } else if (build.status === 'failed') {
      throw new Error(`ビルド失敗: ${build.output}`);
    }

    console.log(`ビルド進行中... ${i + 1} 回目`);
    await new Promise(resolve => setTimeout(resolve, 5000));
  }
  throw new Error('ビルドタイムアウト');
};
Enter fullscreen mode Exit fullscreen mode

リリース履歴表示

const listReleases = async (appName, limit = 10) => {
  return await herokuRequest(`/apps/${appName}/releases?limit=${limit}`);
};

// 使用例
const releases = await listReleases('my-app');
releases.forEach(release => {
  console.log(`v${release.version} - ${release.description} - ${release.created_at}`);
});
Enter fullscreen mode Exit fullscreen mode

リリースロールバック

const rollback = async (appName, releaseId) => {
  const response = await herokuRequest(`/apps/${appName}/releases`, {
    method: 'POST',
    body: JSON.stringify({ rollback: releaseId })
  });
  return response;
};

// 例: バージョン42へロールバック
const rollbackRelease = await rollback('my-app', 42);
console.log(`v${rollbackRelease.version} にロールバックしました`);
Enter fullscreen mode Exit fullscreen mode

パイプライン管理

パイプライン作成

const createPipeline = async (pipelineName) => {
  const response = await herokuRequest('/pipelines', {
    method: 'POST',
    body: JSON.stringify({ name: pipelineName })
  });
  return response;
};

// 使用例
const pipeline = await createPipeline('my-app-pipeline');
console.log(`パイプライン作成: ${pipeline.id}`);
Enter fullscreen mode Exit fullscreen mode

パイプラインにアプリを追加

const addAppToPipeline = async (pipelineId, appName, stage) => {
  return await herokuRequest('/pipeline-couplings', {
    method: 'POST',
    body: JSON.stringify({
      pipeline: pipelineId,
      app: appName,
      stage // 'development', 'staging', 'production'
    })
  });
};

// ステージ追加例
await addAppToPipeline(pipelineId, 'my-app-dev', 'development');
await addAppToPipeline(pipelineId, 'my-app-staging', 'staging');
await addAppToPipeline(pipelineId, 'my-app-prod', 'production');
Enter fullscreen mode Exit fullscreen mode

スラグの昇格

const promoteSlug = async (slugId, toApp) => {
  await herokuRequest('/promotions', {
    method: 'POST',
    body: JSON.stringify({
      from: toApp,
      to: toApp,
      slug: slugId
    })
  });
  console.log(`スラグ ${slugId}${toApp} に昇格`);
};
Enter fullscreen mode Exit fullscreen mode

アドオン管理

アドオンのプロビジョニング

const provisionAddon = async (appName, addonPlan, config = {}) => {
  const response = await herokuRequest('/addon-attachments', {
    method: 'POST',
    body: JSON.stringify({
      app: appName,
      plan: addonPlan,
      config: config
    })
  });
  return response;
};

// PostgreSQL プロビジョニング例
const db = await provisionAddon('my-app', 'heroku-postgresql:mini', {});
console.log(`DBプロビジョニング: ${db.addon.name}`);
console.log(`DATABASE_URL: ${db.addon.config_vars.DATABASE_URL}`);
Enter fullscreen mode Exit fullscreen mode

人気アドオン

アドオン プラン 開始価格 ユースケース
heroku-postgresql mini 月額5ドル 本番DB
heroku-redis mini 月額5ドル キャッシュ/セッション
papertrail choklad 月額7ドル ログ集約
sentry developer 無料 エラートラッキング
mailgun sandbox 無料 メール配信
newrelic lite 無料 アプリ監視

アドオン一覧取得

const listAddons = async (appName) => {
  return await herokuRequest(`/apps/${appName}/addons`);
};

// 使用例
const addons = await listAddons('my-app');
addons.forEach(addon => {
  console.log(`${addon.plan.name} - $${addon.pricing.plan.price} - ${addon.state}`);
});
Enter fullscreen mode Exit fullscreen mode

アドオン削除

const removeAddon = async (appName, addonId) => {
  await herokuRequest(`/apps/${appName}/addons/${addonId}`, { method: 'DELETE' });
  console.log(`アドオン ${addonId} 削除 (${appName})`);
};
Enter fullscreen mode Exit fullscreen mode

ドメインとSSL管理

カスタムドメイン追加

const addDomain = async (appName, domainName) => {
  const response = await herokuRequest(`/apps/${appName}/domains`, {
    method: 'POST',
    body: JSON.stringify({ hostname: domainName })
  });
  return response;
};

// 使用例
const domain = await addDomain('my-app', 'api.example.com');
console.log(`CNAMEターゲット: ${domain.cname}`);
Enter fullscreen mode Exit fullscreen mode

SSL証明書の設定

const addSslCertificate = async (appName, domainId, certificateChain, privateKey) => {
  const response = await herokuRequest(`/apps/${appName}/domains/${domainId}/ssl_endpoint`, {
    method: 'PATCH',
    body: JSON.stringify({
      ssl_cert: {
        cert_chain: certificateChain,
        private_key: privateKey
      }
    })
  });
  return response;
};
Enter fullscreen mode Exit fullscreen mode

ACMによる自動SSL

const enableACM = async (appName, domainName) => {
  const response = await herokuRequest(`/apps/${appName}/domains/${domainName}/sni_endpoint`, {
    method: 'POST',
    body: JSON.stringify({ kind: 'acm' })
  });
  return response;
};
Enter fullscreen mode Exit fullscreen mode

レート制限とクォータ

レート制限の概要

  • 標準制限: 1時間あたり10,000リクエスト/アカウント
  • ウィンドウ: 60分ローリング
  • リセット: 時間経過で自動

超過時は HTTP 429 (Too Many Requests) が返ります。

レート制限処理(指数バックオフ)

const makeRateLimitedRequest = async (endpoint, options = {}, maxRetries = 3) => {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await herokuRequest(endpoint, options);
      const remaining = response.headers.get('RateLimit-Remaining');
      const resetTime = response.headers.get('RateLimit-Reset');
      if (remaining < 100) {
        console.warn(`残りクォータ少: ${remaining}, リセット: ${resetTime}`);
      }
      return response;
    } catch (error) {
      if (error.message.includes('429') && attempt < maxRetries) {
        const delay = Math.pow(2, attempt) * 1000;
        console.log(`レート制限。${delay}ms後にリトライ`);
        await new Promise(resolve => setTimeout(resolve, delay));
      } else {
        throw error;
      }
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

レート制限ヘッダー

ヘッダー 説明
RateLimit-Limit 1時間あたりの最大リクエスト数
RateLimit-Remaining ウィンドウ内の残りリクエスト数
RateLimit-Reset リセットされるUnixタイムスタンプ

よくある問題のトラブルシューティング

問題:認証が 401 エラー

  • API キー・トークンが正しいか heroku authorizations で確認
  • トークン有効期限切れをチェック
  • 環境変数に余計な空白がないか
  • 必要ならトークン再生成

問題:アプリ名が既に利用中

  • グローバル一意な名前にする(チーム名やランダム接尾辞付与)
  • UUIDやタイムスタンプの利用
  • チーム名-アプリ名-環境 で名前空間化
const generateUniqueAppName = (baseName) => {
  const timestamp = Date.now().toString(36);
  const random = Math.random().toString(36).substring(2, 6);
  return `${baseName}-${timestamp}-${random}`;
};
Enter fullscreen mode Exit fullscreen mode

問題:ダイノ構成が失敗

  • Procfile に該当プロセスがあるか
  • ダイノクォータ残があるか
  • アプリが停止状態でないか
  • heroku ps --app=my-app で稼働状況確認

問題:ビルドがタイムアウト

  • 最適なビルドパックを選択
  • 依存キャッシュ活用
  • 大きなビルドは小分け
  • 事前ビルド済みスラグで高速化

問題:レート制限超過

  • リクエストキューイングを実装
  • 指数バックオフでリトライ
  • バッチ処理
  • レート制限ヘッダーの積極監視
// シンプルなレートリミッター
class HerokuRateLimiter {
  constructor(requestsPerMinute = 150) {
    this.queue = [];
    this.interval = 60000 / requestsPerMinute;
    this.processing = false;
  }

  async add(requestFn) {
    return new Promise((resolve, reject) => {
      this.queue.push({ requestFn, resolve, reject });
      this.process();
    });
  }

  async process() {
    if (this.processing || this.queue.length === 0) return;
    this.processing = true;

    while (this.queue.length > 0) {
      const { requestFn, resolve, reject } = this.queue.shift();
      try {
        const result = await requestFn();
        resolve(result);
      } catch (error) {
        reject(error);
      }
      if (this.queue.length > 0) {
        await new Promise(r => setTimeout(r, this.interval));
      }
    }
    this.processing = false;
  }
}
Enter fullscreen mode Exit fullscreen mode

本番デプロイチェックリスト

本番前に必ず確認:

  • [ ] CI/CDは有効期間の長いAPIトークンを利用
  • [ ] シークレット管理サービス(Vault, AWS Secrets Manager等)で安全に保管
  • [ ] レート制限・リクエストキューイング実装
  • [ ] 例外処理・ロギング追加
  • [ ] ダイノ使用時間監視
  • [ ] 外部ロギング用ログドレイン設定
  • [ ] パイプラインプロモーション設定
  • [ ] 自動SSL証明書管理有効化
  • [ ] DBバックアップ戦略

監視とアラート

const metrics = {
  apiCalls: { total: 0, successful: 0, failed: 0, rateLimited: 0 },
  dynoHours: { used: 0, quota: 1000 },
  deployments: { successful: 0, failed: 0, avg_duration: 0 }
};

const failureRate = metrics.apiCalls.failed / metrics.apiCalls.total;
if (failureRate > 0.05) sendAlert('Heroku API failure rate above 5%');
if (metrics.dynoHours.used > metrics.dynoHours.quota * 0.8) sendAlert('Dyno hour usage above 80%');
Enter fullscreen mode Exit fullscreen mode

実際のユースケース

CI/CD自動化パイプライン

  • 課題:手動デプロイでエラー・遅延多発
  • 解決:GitHub Actions + Heroku API
  • 結果:ダウンタイムゼロ、リリース90%高速化

実装フロー:

  1. GitHubプッシュでワークフロートリガー
  2. CIでテスト
  3. Heroku APIでビルド作成
  4. ステージング→本番へ昇格
  5. 成否でチーム通知

マルチ環境管理

  • 課題:手動設定同期の非効率
  • 解決:APIによる中央設定管理
  • 結果:一貫運用・週8時間削減

主な統合:

  • 環境変数同期
  • アドオン自動プロビジョニング
  • バルク操作でクライアントオンボーディング

トラフィック連動自動スケール

  • 課題:セール時の手動スケール対応困難
  • 解決:Heroku APIとメトリクスAPIで自動スケール
  • 結果:トラフィック10倍でもダウンタイムゼロ

実装例:

  • p95レイテンシ監視でスケールアップ/ダウン
  • 高負荷持続時はアラート

結論

Heroku API でプラットフォーム全体を効率的に自動化できます。

  • ベアラートークン認証は安全管理・定期ローテ
  • レート制限(1時間1万件)は事前監視
  • パイプラインAPIで堅牢なCI/CD
  • エラー処理で信頼性向上
  • Apidogは統合APIテストとチーム連携を効率化

FAQ セクション

Heroku API は何に使う?

アプリ/ダイノ/アドオン/インフラのプログラム管理。CI/CD自動化や多アプリ管理ツール、自動スケール、監視ダッシュボードに活用。

Heroku APIキー取得方法は?

Heroku CLIインストール→heroku loginheroku authorizations:createでトークン発行し、必ず環境変数で安全保管。

Heroku API は無料?

API自体は無料。ダイノ/アドオン等リソース利用は通常料金。レート制限は1時間1万リクエスト/アカウント。

認証方式は?

ベアラートークン認証。全リクエストでAuthorization: Bearer {api_key}が必須。トークンの有効期限は1時間/1年の2種。

レート制限はどう処理?

RateLimit-Remainingを監視し、リクエストキュー・指数バックオフで制御。1分150リクエスト以下推奨。

Gitなしデプロイは可能?

はい。Builds APIでソースブロブURLから直接デプロイ可能。S3/GCS等にコードを置きURL指定でOK。

デプロイ自動化の方法は?

Pipeline APIでCI/CDを設計。ビルド作成→スラグ昇格→GitHubや独自CIと連携。

リリースとビルドの違いは?

ビルド=ソース→スラグ作成。リリース=スラグ+設定変数でアプリバージョン生成。

失敗したデプロイのロールバック方法は?

Releases APIで履歴取得→/releasesrollback:<release_id>指定でPOST。自動で新リリース生成。

複数Herokuアカウント管理は?

アカウントごとにAPIトークンを切り替え、HEROKU_API_KEY環境変数で管理可能。

Top comments (0)