RIDM Apps デプロイガイド
RIDM Apps は、つないだ GitHub リポジトリをビルド・デプロイし、<name>.ridm.ai で公開するホスティングです。申し込みはコンソールのマイアプリから、料金は料金ページでご確認ください。利用条件は利用規約 第3章(韓国語)で定めています。含まれる内容をざっと知りたい場合は、アプリホスティングの概要をご覧ください。
デプロイの流れ
- 申し込み — マイアプリでプランとアドレス(サブドメイン)を選びます。アドレスはあとから変更できません。
- GitHub 連携 — アプリ画面で GitHub App
ridm-apps-builderをインストールし、リポジトリとブランチを選びます。リポジトリには読み取り権限だけが付与されます。 - Push — つないだブランチに push するたびにビルドが始まります。ビルド履歴と失敗ログはアプリ画面に表示されます。
- 初回デプロイ — ビルドが成功すると、自動チェックを経てすぐに公開されます(後述)。以降の push も自動でデプロイされます。
Dockerfile の要件
- リポジトリのルートに
Dockerfileが必要です。ない場合、ビルドは失敗します。 - アプリはポート 3000 で HTTP に応答する必要があります。
PORT=3000とHOSTNAME=0.0.0.0は常に設定されます。コンテナの起動後、30秒以内に/へのリクエストに応答しなければなりません(ステータスコードは問いません)。応答がない場合は起動失敗とみなされ、新しいバージョンであれば前のバージョンが引き続き配信されます。 - ヘルスチェックパス(アプリ画面の「ヘルスチェックパス」欄、例:
/api/health)を設定すると、/の代わりにそのパスを確認し、2xx・3xx・404 で応答した場合だけ起動成功となります(5xx やその他の 4xx は失敗)。データベースも確認するパスにしておけば、ポートは開くのに実際には動かないバージョンが公開されることはありません。空欄のままなら上のルールが適用されます。 - 新しいバージョンが公開後 5分以内に3回以上再起動した場合(プロセスが落ち続ける場合)、最後に正常に動いていたバージョンへ自動で1回ロールバックし、メールでお知らせします(アプリが最後に残したログ付き)。ロールバックしたバージョンも落ち続ける場合はそのままにしますので、コードを修正して再度 push してください。
- コンテナはユーザー 1000:1000 で実行されます。このユーザーがアプリのファイルを読めるようにしてください(
node:*-alpineイメージのnodeユーザーは 1000 です)。 - ファイルシステムは読み取り専用です。書き込めるのは
/tmpと/app/.next/cacheだけです(どちらもメモリー上にあり、再起動で消えます)。残しておくデータはデータベースに保存してください。 - ビルドは x86_64(amd64)向けです。ビルドの制限時間は20分です。
- リポジトリにシークレットが見つかるとビルドが止まります。対象は AWS アクセスキー、GitHub トークン、Stripe の本番キー、秘密鍵ブロック、パスワードを含む PostgreSQL URL です。シークレットは環境変数に入れてください。
Next.js スターター
next.config.js に output: 'standalone' を追加し、下の Dockerfile をリポジトリのルートに置きます。public フォルダがない場合は、その行を削除してください。
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
FROM node:22-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build --chown=1000:1000 /app/public ./public
COPY --from=build --chown=1000:1000 /app/.next/standalone ./
COPY --from=build --chown=1000:1000 /app/.next/static ./.next/static
USER 1000
EXPOSE 3000
CMD ["node", "server.js"]
Next.js でなくてもかまいません。コンテナがポート 3000 で応答すれば、どんなスタックでも動きます。
実行環境
- 無料プランでは、誰かが訪問するとアプリが起動し、リクエストのない状態が15分続くと停止します。停止後の最初の訪問は数秒長くかかることがあります。検索クローラー、監視リクエスト、HEAD リクエストではアプリは起動しません。
- 有料プランは常時稼働します。アプリが落ちると自動で再起動します。
- アプリがメモリーを超えると強制終了されます。Node.js のヒープ上限は、プランのメモリーの75%に設定されます。
- WebSocket は有料プランで使えます(無料プランではアップグレードリクエストに
403が返ります)。アプリはポート 3000 でいつもどおりUpgrade: websocketを受け付けるだけです(パス、サブプロトコル、Origin、X-Forwarded-For、CF-Connecting-IPはそのまま渡され、アプリがアップグレードの代わりに返す 403 や 429 などのレスポンスもそのまま訪問者に届きます)。制限は次のとおりです。- 同時接続はアプリごとに 120 です。超えると、新しい接続は
503(Retry-After: 10)で拒否されます。 - 1メッセージあたり 64KB(全フラグメントの合計)です。超えると、接続は
1009で閉じられます。圧縮拡張(permessage-deflate)は使いません。 - アプリごとに各方向で毎秒 2,000 メッセージまで、訪問者1接続からの送信は毎秒 60 までです。超えた分は捨てられず、遅れて届きます。
- フレームのない状態が 100秒続くと、エッジ(Cloudflare)が接続を切ります。アプリまたはクライアントから、それより短い間隔で ping を送ってください。
- 同じアプリの Origin のみ:ブラウザからの接続は、
Originがそのアプリのアドレス(appname.ridm.aiまたはそのアプリにつないだドメイン)の場合だけ開きます。他のアプリや他のサイトからの接続には、同じパートナーのアプリであっても403が返ります。別々のアプリどうしが WebSocket で直接つながることはできません。 - ブラウザ以外のクライアント(サーバー、アプリ、CLI)は
Originなしで接続できますが、その場合Cookieはアプリに渡されません。Cookie の代わりに、ヘッダー(Authorization)またはクエリ文字列のトークンで認証してください。 - 接続が開いている間、アプリはスリープしません。新しいバージョンのデプロイやプラットフォームの再起動時には接続が
1001で閉じられるため、クライアントが再接続するようにしてください。
- 同時接続はアプリごとに 120 です。超えると、新しい接続は
- 外部への HTTPS リクエストは許可されています。メールポート(SMTP)と内部ネットワークはブロックされています。メールを送るには、
HRUM_API_KEYでアプリメールAPIを呼び出すか、HTTPS API のあるメールサービスを使ってください。 - セキュリティのため、各アプリの外部接続先を記録します。記録するのは、接続したアドレスとポート、名前解決したドメイン名、それぞれを最初と最後に確認した日時、接続回数です。リクエストやレスポンスの内容、完全な URL は記録しません。記録は3か月間保管したあと削除します。閲覧するのは、セキュリティ事故や禁止行為の調査(規約 第26条(韓国語))の場合か、法令で求められた場合だけです。
- アプリのログ(stdout と stderr)は、障害対応の際に運営者が閲覧することがあります。ホストの外に出る前に、環境変数の値、データベースのパスワード、トークン、メールアドレスなどはマスクされます(規約 第20条)。
Cookie
ridm.ai の下には多くのパートナーアプリがあるため、ゲートウェイは Cookie を各アプリ自身のアドレスにひも付けます。アプリのコードではいつもどおり Cookie を使えます。ただし、次の点に注意してください。
- Cookie は常に
SecureとPath=/になり、Domain属性は無視されます。 - Cookie の名前は57文字以下にしてください。それより長い名前の Cookie は破棄されます。
- ブラウザの JavaScript(
document.cookie)で作った Cookie はサーバーに送られません。サーバーが読む必要のある Cookie は、サーバーのレスポンスのSet-Cookieで作成してください。
データベース
アプリごとに専用の PostgreSQL データベースが付き、接続情報は環境変数で渡されます。DATABASE_URL(SSL 必須)に加え、PGHOST、PGPORT、PGDATABASE、PGUSER、PGPASSWORD、PGSSLMODE です。容量の上限はプランによって異なり、使用量はアプリ画面に表示されます。
データベース接続には、証明書を完全に検証する TLS を使います。認証局(Amazon RDS)の証明書はコンテナにあらかじめ入っているため(NODE_EXTRA_CA_CERTS、PGSSLROOTCERT)、コードに SSL オプションは不要です。new Pool({ connectionString: process.env.DATABASE_URL, max: 2 }) だけで十分です。接続はアプリごとに3つまでなので、プールサイズは2以下にしてください。
- 同時接続はアプリごとに3つまで、1つのクエリの実行時間は10秒までです。トランザクション内で60秒を超えてアイドル状態の接続は閉じられます。
- 使用量が80%を超えたときと、上限を超えたときにメールでお知らせします。
- 上限を超えた状態が 24時間続くと(150%を超えた場合はすぐに)、データベースは読み取り専用になります。データを削除するには、まずその接続で
SET default_transaction_read_only = offを実行してください。 - 読み取り専用の状態で110%を超えると、データベース接続が切断されます。データは削除されず、バックアップも引き続き作成できます。
- 上限の90%未満に下げるか、より大きなプランに変更すると、5分以内に制限が解除されます。
環境変数
- アプリ画面の「環境変数」で追加・削除します。保存した値は暗号化して保管され、あとから表示できません(名前だけが表示されます)。変更するには、新しい値で上書きしてください。
- 保存すると、新しい値でアプリが再起動します。
- 名前に使えるのは英大文字・数字・アンダースコアだけです(数字で始めることはできず、最大64文字)。値は2KB以下の1行で、アプリごとに50個までです。
- プラットフォームが使う名前は設定できません。
PORT、HOSTNAME、NODE_OPTIONS、DATABASE_URL、およびRIDM_やPGで始まる名前です。 - プラットフォームが提供する値:
RIDM_COMMIT(デプロイしたコミットの完全な SHA)、RIDM_COMMIT_SHORT(先頭7文字)、RIDM_DEPLOYED_AT(そのバージョンをデプロイした日時、ISO UTC)。たとえば/api/healthで実行中のバージョンを表示するのに便利です。以前のバージョンにロールバックした場合は、そのバージョンの値が使われます。 HRUM_API_KEY:コンソールの「APIキー」で発行したキーを入れます。AI連携をオンにしたアプリでは、プラットフォームが自動で設定するため、自分では設定できません。自分で値を設定していた場合は、AI連携をオンにする前に削除してください。他のアカウントのキーやアプリ専用キーは使えません。- 解約したアプリの環境変数は変更できません。
- 値は、どの画面や API からも読み返すことはできません。一覧や API のレスポンスに表示されるのは名前だけです。値をなくした場合は、発行元(決済事業者や外部サービスのコンソールなど)で新しく発行し、同じ名前で保存し直してください。
コンソールでシークレットを追加(人が操作)
コンソールでは、人がどんな名前でも追加できます。AIツールでは設定できない名前(後述)も、この方法で追加します。
- console.hrum.ai にログインし、左メニューのマイアプリを開きます。
- アプリカードを下にスクロールし、環境変数セクションまで移動します。保存済みの名前には、値の代わりに
••••••••が表示されます。 - 1つ目の欄名前(例:STRIPE_SECRET_KEY)に名前を入力します。小文字やその他の文字は、入力中に大文字とアンダースコアに変わります。
- 2つ目の欄値に値を貼り付けます。この欄はパスワード欄のように伏せ字で表示されます。
- 保存をクリックすると、変数が保存され、まもなくアプリが再起動するという確認が表示されます。同じ名前で再度保存すると、値が変わります。
- 削除するには、その行の削除をクリックし、確認ダイアログで削除をクリックします。削除するとアプリが再起動し、値は復元できません。
値を移すときは、次の点に注意してください。
- パスワードマネージャーや macOS のキーチェーンアクセスから値をコピーし、すぐに貼り付けてください。先にメモアプリ、チャット、メール、文書などに書き留めないでください。
- 貼り付けたあとはクリップボードを消去してください(macOS のターミナルなら
pbcopy < /dev/null、または別のテキストを一度コピーするだけでもかまいません)。 - ブラウザが値をパスワードとして保存するよう提案しても、断ってください。
- 画面共有や録画の最中には入力しないでください。
AIでシークレットを追加(AI運用トークン)
AIコーディングツールに環境変数を設定させるには、AI運用トークンが必要です。これは、発行時に人が hosting_ops 権限にチェックを入れたコンソール管理トークン(hrum_cm_…、AIでコンソールを管理を参照)です。この権限があれば、ツールは作業ごとに同意を求め直しません。環境変数の設定は HRUM MCP には含まれていないため、値が AI モデルを通ることはありません。下の HTTP パスを使ってください。
AI経路で設定できる名前は、末尾が _KEY・_SECRET・_TOKEN・_PASSWORD・_PASSWD・_PASS・_SALT・_CLIENT_ID のものだけです(例:STRIPE_SECRET_KEY、MASTER_KEY)。その中でも、実行環境や接続先アドレスを変える名前は拒否されます。末尾が _URL・_URI・_ENDPOINT・_HOST・_PROXY の名前、先頭が NODE_・LD_・DYLD_・PYTHON・JAVA_・_JAVA_・JDK_・RUBY・PERL・GIT_・NPM_CONFIG_・GLOBAL_AGENT_・AWS_ENDPOINT_URL の名前、そして HTTP_PROXY・HTTPS_PROXY・NODE_TLS_REJECT_UNAUTHORIZED・SSL_CERT_FILE・PATH・OPENAI_API_BASE などの名前です。それ以外の名前(例:ADMIN_PATH、SMTP_HOST)は、人がコンソールで追加します。削除はAI経路でもできます(予約済みの名前を除く)。
トークンの取得 — 有効期間は30日です。
- AIは認証なしで
POST https://api.hrum.ai/api/v1/partners/v1/console-ai/consentsに{"action":"token_issue","payload":{"scopes":["hosting_ops"],"label":"My app ops"}}を送ります。レスポンスのdataにはconsentUrl、userCode(8文字)、code、pollSecretが入っています。 - AIがユーザーに見せるのは
consentUrlと8文字のコードだけです。pollSecretは見せません。 - 10分以内に、人がコンソールにログインしたブラウザでリンクを開き、AIツールの画面に出ている8文字のコードを確認コード欄に入力し、
hosting_opsにチェックを入れて同意をクリックします。先にチェックボックスの下の警告を読んでください(トークンが漏れると、第三者が環境変数の上書き、デプロイ、再起動を行えるおそれがあります)。 - AIが
X-Consent-Poll-Secretヘッダーを付けてGET …/console-ai/consents/{code}を呼び出すと、承認後の最初のレスポンスに限り、data.token.valueにトークンが1回だけ入ります。画面に出さず、そのままシークレットストア(キーチェーン、権限 600 のファイルなど)に保存してください。 - 不要になったら、コンソールのAPIキー画面にある「AIコンソール管理トークン」で取り消しをクリックします。すぐに使えなくなります。有効なトークンは、アカウントごとに10個までです。
呼び出し — ベース URL は https://api.hrum.ai、ヘッダーは Authorization: Bearer hrum_cm_… です。アプリ ID は、GET /api/v1/partners/v1/console-ai/hosting のレスポンスにある data.apps[].id(UUID)です。
| 作業 | リクエスト | レスポンス |
|---|---|---|
| 名前の一覧 | GET /api/v1/partners/v1/console-ai/hosting/apps/{appId}/env | {"success":true,"data":{"env":{"names":[…]}}} — 値は含まれません |
| 追加・変更 | PUT …/hosting/apps/{appId}/env/{NAME}、本文 {"value":"…"} | 更新後の名前の一覧。アプリが再起動します |
| 削除 | DELETE …/hosting/apps/{appId}/env/{NAME} | 更新後の名前の一覧。アプリが再起動します |
例(bash または zsh)。値とトークンはコマンドライン引数として渡さず、値は標準入力から渡します。$APP_ID とキーチェーンの項目名は、ご自身のものに置き換えてください。
# 1) Read secrets into shell variables (nothing is printed)
HRUM_CM_TOKEN=$(security find-generic-password -s hrum-console-ops -w)
VALUE=$(security find-generic-password -s myapp-STRIPE_SECRET_KEY -w)
# 2) Value goes through stdin, token through a header file: neither shows up in ps or shell history
printf '%s' "$VALUE" | jq -Rs '{value: .}' | curl -s -X PUT \
-H @<(printf 'Authorization: Bearer %s' "$HRUM_CM_TOKEN") \
-H 'Content-Type: application/json' --data-binary @- \
"https://api.hrum.ai/api/v1/partners/v1/console-ai/hosting/apps/$APP_ID/env/STRIPE_SECRET_KEY" \
| jq '.data.env.names // .'
# 3) Forget the value
unset VALUE
保存されると、レスポンスには名前の一覧だけが表示されます。アプリのアドレスを開き、新しい値で再起動したことを確認してください。
AIでデプロイ・ログ確認(AI運用トークン)
同じAI運用トークン(hosting_ops)で、サイトを開かずにデプロイを開始し、デプロイ状況やアプリが出力したログを確認できます。
HRUM MCP 0.3.3 以降では、MCP ツールでも同じことができます。hrum_hosting_deploy(デプロイ開始)、hrum_hosting_deploys(デプロイ状況)、hrum_hosting_logs(実行ログ)です。先に hrum_ai_connect で hosting_ops 権限付きのトークンを取得し、hrum_hosting_status でアプリ ID を確認してください。MCP を使わないツールは、下の HTTP パスを使います。
| 作業 | リクエスト | レスポンス |
|---|---|---|
| デプロイ開始 | POST …/hosting/apps/{appId}/deploy、本文 {} または {"commit":"40-char sha"} | {"deployId","commitSha","retried","status":"queued"}。コンソールの「今すぐデプロイ」と同じく、つないだブランチの最新コミットをビルドします(初回デプロイの審査とプランのルールも同じ)。commit はその最新コミットの場合だけ受け付けます。古いコミットにはまだ対応していません(400 COMMIT_NOT_BRANCH_HEAD)。1分に2回まで |
| デプロイ状況 | GET …/hosting/apps/{appId}/deploys?limit=20 | 新しい順:id、commitSha、status、queuedAt、startedAt、finishedAt、liveAt、failReason、scanFindings(シークレットスキャンで検出されたファイル・行・ルールのみ)、logTail(ビルドログの末尾)、startFailReason、startFailLog |
| ログの要求 | POST …/hosting/apps/{appId}/logs、本文 {"lines":100}(1–200) | 202 {"requestId"} |
| ログの取得 | GET …/hosting/apps/{appId}/logs/{requestId} | status が pending なら2–3秒後に再試行し、delivered なら lines を読みます。3分を過ぎると expired になります |
ログと失敗理由は、返す前にマスクされます。そのアプリに保存された環境変数の値や、sk-…、AKIA…、Bearer …、JWT、…_live_… のようなキー形式の文字列は [redacted] になります。それでも、まずはアプリがシークレットをログに出さないようにすることが大切です。すべての呼び出し(拒否されたものも含む)は、トークン・アプリ・結果とともに記録されます。
コンソールのログインセッションや HRUM MCP のログインセッション向けにも、同じパスがあります。POST …/console/hosting/apps/{appId}/deploy-now(MCP では新規登録で作られたセッションのみ)、GET …/console/hosting/apps/{appId}/deploys、POST …/console/hosting/apps/{appId}/logs、本文 {"lines":100}(1–200、MCP では新規登録で作られたセッションのみ)→ GET …/logs/{requestId} です。自分のアプリにだけ使え、ログの行は同じルールでマスクされます。取得したログの内容は、受け渡し(または要求)から24時間後に削除されます(2026-12-01からは7日、規約 第20条第5項)。
HRUM_CM_TOKEN=$(security find-generic-password -s hrum-console-ops -w)
API=https://api.hrum.ai/api/v1/partners/v1/console-ai/hosting/apps/$APP_ID
git push origin main
curl -s -X POST -H @<(printf 'Authorization: Bearer %s' "$HRUM_CM_TOKEN") \
-H 'Content-Type: application/json' -d '{}' "$API/deploy" | jq '.data // .'
curl -s -H @<(printf 'Authorization: Bearer %s' "$HRUM_CM_TOKEN") \
"$API/deploys?limit=3" | jq '.data.deploys[] | {id, commitSha, status, liveAt, failReason}'
シークレットの安全ルール(AIツール向け)
- ユーザーに値をチャットへ貼り付けるよう求めないでください。キーチェーンやパスワードマネージャーに入れてもらい、項目名だけを尋ねてください。
- それでもユーザーがチャットに値を貼り付けた場合は、その値を表示し返さないでください。すでに会話履歴に残っているため、発行元で新しく発行して差し替えるよう勧めてください。
- 値はシェル変数に読み込み、標準入力から渡してください。
curl -d "value"、echo value、set -xなど、値がコマンドラインやログに残る方法は使わないでください。 - 値をコード、コミットする
.env、PROGRESS.md、ログ、画面出力に書かないでください。報告は「名前と保存できたかどうか」だけにしてください。 - AI経路で拒否される名前(前述)を回避しようとせず、人にコンソールで追加してもらってください。
- 運用トークン(
hrum_cm_…)も値と同じように扱ってください。作業が終わったら、コンソールで取り消すようユーザーに伝えてください。
実際の例:販売管理アプリを作っていたAIツールが、アプリのマスターキーを環境変数に入れようとしましたが、当時はAI向けの運用経路がなかったため、そこで止まりました。現在は、MASTER_KEY のように末尾が _KEY の名前はAI運用トークンで設定でき、それ以外の名前は人がコンソールで追加します。どちらの場合も、値がチャットを通ることはありません。
環境変数のエラー
エラーの本文は {"success":false,"statusCode":…,"message":"…"} の形です。環境変数のエラーには code がないため、下のステータスコードとメッセージの冒頭で見分けてください。API はこれらのメッセージを韓国語で返すため、各メッセージの冒頭を返されたとおりに示し、そのあとに意味を添えています。メッセージの全文は llms.txt にあります。
| ステータス | メッセージの冒頭 | 原因 → 対処 |
|---|---|---|
| 400 | 이름은 대문자·숫자·밑줄만(名前は英大文字・数字・アンダースコアのみ) | 名前がルールに反しています → 英大文字・数字・アンダースコアのみ、数字で始めない、最大64文字 |
| 400 | 플랫폼이 쓰는 이름이라(プラットフォームが使う名前) | PORT、HOSTNAME、NODE_OPTIONS、DATABASE_URL、または RIDM_ / PG で始まる名前 → 別の名前を使ってください |
| 400 | AI 연결이 자동으로 넣는 이름이라(AI連携が自動で設定) | HRUM_API_KEY → このアプリはAI連携がオンのため、プラットフォームが設定します。自分で設定するには、AI連携をオフにしてください |
| 400 | 이 계정에서 발급한 API 키만 넣을 수 있어요(このアカウントで発行したキーのみ) | ご自身のコンソールの「APIキー」で発行したキーを使ってください |
| 400 | AI 연결이 앱마다 만든 키는 넣을 수 없어요(AI連携のアプリ別キーは不可) | コンソールの「APIキー」で発行したキーを使ってください |
| 400 | 값을 입력해 주세요(値を入力してください) | 値が空です |
| 400 | 값에 줄바꿈을 넣을 수 없(値に改行は使えません) | 複数行の値(PEM 形式の秘密鍵など) → base64 の1行に変換し、アプリ側でデコードしてください |
| 400 | 값은 2048바이트까지(値は2048バイトまで) | 値が2KBを超えています → 大きな値はデータベースや外部のシークレットストアに保管してください |
| 400 | 실행 환경을 바꾸는 이름이라 AI 경로(実行環境を変える名前のためAI経路では不可) | AI経路で拒否される名前 → 人がコンソールで追加します |
| 400 | AI 경로(AI 운영 토큰·MCP)로는 _KEY(AI経路で使えるのは _KEY… のみ) | シークレット名の末尾ではありません → 名前を変えるか、人にコンソールで追加してもらってください |
| 400 | Validation failed (uuid is expected) | アプリ ID が UUID ではありません → GET /console-ai/hosting の apps[].id を使ってください |
| 401 | MGMT_TOKEN_INVALID | トークンがない、期限切れ、または取り消し済みです → 新しく発行してください |
| 403 | MGMT_SCOPE_DENIED (scope: "hosting_ops") | トークンに hosting_ops がありません → チェックを入れて新しく発行してください |
| 404 | 앱을 찾을 수 없(アプリが見つかりません) | 他のアカウントのアプリ、または削除済みのアプリです → アプリ ID を確認してください |
| 404 | 없는 환경변수(存在しない環境変数) | 削除しようとした名前がありません → 名前の一覧を確認してください |
| 409 | 해지된 앱은 환경변수를 바꿀 수 없(解約したアプリ) | アプリが解約手続き中です → 変更できません |
| 409 | 환경변수는 앱당 50개까지(アプリごとに50個まで) | 使っていない名前を削除してから再試行してください |
| 429 | — | 追加と削除は1分に30回までです → 少し待ってから再試行してください |
| 503 | 환경변수 저장소가 준비되지 않(変数ストアの準備ができていません) | サーバー側の問題です → 少し待ってから再試行し、続く場合はお問い合わせください |
AI連携
AI連携を使うと、アプリから四柱推命(サジュ)API と AI API を呼び出せます。プラットフォームが、このアプリ専用の APIキーを追加します。すべてのアカウントで使えます。
- オンにする — コンソールのアプリでアプリの詳細を開き、「AI連携を使う」をオンにします。アプリ専用のキーが作られて環境変数
HRUM_API_KEYとして追加され、アプリが再起動します。 - 呼び出す — アプリのサーバーコードで
process.env.HRUM_API_KEYを読み、X-Partner-Api-Keyヘッダーに入れます。キーの値はコンソールでも表示されません。ブラウザに送られるコードには入れないでください。
オンの間は、HRUM_API_KEY を自分で設定できません。自分で値を設定していた場合は、AI連携をオンにする前に削除してください(削除しないと、既存の HRUM_API_KEY 環境変数を先に削除するよう求めるエラーが出ます)。このキーの利用料はクレジットから差し引かれ、アプリの詳細で設定した1日の利用上限に達すると、その日は止まります。オフにするとキーはすぐに無効になり、再びオンにすると新しいキーが作られます。このキーでは決済APIを呼び出せません。アプリで決済を受けるには、コンソールで発行したキーを使います。
アプリで決済を受ける
HRUM決済の決済ページを通じて、アプリのユーザーから決済を受けられます。販売者(個人、個人事業者、法人)として登録し、承認されると販売を始められます。販売者登録はコンソールの精算で行います。条件は利用規約 第32–39条(韓国語)で定めています。無料プランのアプリでも販売できます。ただし、月間転送量の上限に達した、または停止されたためにアプリが配信されていない間は、新しい決済の開始が 409 APP_NOT_SERVING で拒否されます。すでに開始した決済、注文の照会、返金は引き続き使えます。
- APIキーを入れる — コンソールのAPIキーで発行したキーを環境変数(例:
HRUM_PARTNER_KEY)に入れます。AI連携が追加するHRUM_API_KEYでは決済APIを呼び出せません。キーはサーバーコードでだけ使い、ブラウザには絶対に送らないでください。 - 商品を登録する —
POST /api/v1/partners/v1/storefront/skus、本文{"appId","name","priceWon"}。決済は、ここで登録した価格でのみ行われます。 - 決済を開始する — サーバーから本文
{"appId","skuId","qty","returnUrl"}でPOST /api/v1/partners/v1/storefront/checkoutsを送り、レスポンスのcheckoutUrlへユーザーを移動させます。returnUrlは、このアプリの https アドレス(<name>.ridm.aiまたはつないだドメイン)でなければなりません。 - 結果を確認する — 支払いが終わると、ユーザーは
returnUrl?orderId=…に戻ってきます。サーバーでGET /api/v1/partners/v1/storefront/orders/{orderId}を呼び出し、statusがpaidであることを確認してから商品を提供してください。URL の値だけを信用しないでください。
すべてのリクエストに X-Partner-Api-Key ヘッダーが必要です。返金は、注文番号を [email protected] にメールでお送りください。返金されると、注文の status が canceled または partial_canceled に変わります。
const res = await fetch("https://api.hrum.ai/api/v1/partners/v1/storefront/checkouts", {
method: "POST",
headers: { "Content-Type": "application/json", "X-Partner-Api-Key": process.env.HRUM_PARTNER_KEY },
body: JSON.stringify({ appId, skuId, qty: 1, returnUrl: "https://myapp.ridm.ai/paid" }),
});
const { checkoutUrl } = (await res.json()).data;
キーの権限ごとに呼び出せるもの
キーを作成するときに権限を選べます。選んだ権限の外のパスを呼び出すと 403 KEY_SCOPE_MISSING が返ります。権限を選んでいない既存のキーは、引き続きすべてのパスを呼び出せます。すべてのパスは OpenAPI ドキュメントにあります。
payments— 商品登録POST /storefront/skus、決済開始POST /storefront/checkoutsorders.read— 注文の照会GET /storefront/orders/{orderId}refunds— 返金の結果は、注文のstatusとorder.refundedWebhook で届きますsettlement.read— 精算データGET /settlement/sales·/settlement/payouts·/settlement/summarywebhooks— 販売者向け Webhook のアドレス登録と配信記録の閲覧
精算データAPI
コンソール → 精算画面と同じ数値を、サーバーから取得できます。キーで見られるのは自分のデータだけです。期間 from / to は韓国の日付(YYYY-MM-DD、両端を含む)で指定し、空欄なら当月になります。一度に366日までです。
GET /api/v1/partners/v1/settlement/sales?from=&to=&skuId=&limit=&cursor=— 販売ごとの決済額(paidWon)、HRUM 販売手数料(salesCommissionWon、VAT込み)、注文ごとの手数料(perOrderFeeWon)、カード決済手数料(pgFeeWon)、支払予定額(sellerWon)、返金額(refundedWon)。すべて返金を差し引いた値です。totalsはこのページだけでなく、期間全体の合計です。GET /api/v1/partners/v1/settlement/payouts?from=&to=&status=scheduled|completed|unpaid— 支払いごとに、grossWon= 送金額(amountWon)+ 支払手数料(feeWon)+ その VAT(feeVatWon)+ 個人販売者の源泉徴収税 3.3%(withholdingWon)です。期間は支払日で区切ります。GET /api/v1/partners/v1/settlement/summary?from=&to=— 売上合計、支払予定・支払済みの合計、現在の支払手数料を1回の呼び出しで取得します。
const res = await fetch("https://api.hrum.ai/api/v1/partners/v1/settlement/summary?from=2026-11-01&to=2026-11-30", {
headers: { "X-Partner-Api-Key": process.env.HRUM_PARTNER_KEY },
});
const { sales, payouts, payoutFee } = (await res.json()).data;
販売者向け Webhook
決済の完了、返金、支払いがあったときに、サーバーへ通知します。受け付けるのは https:// のアドレスだけで(ポート 443 と 8443)、プライベートアドレスや内部アドレスは登録できません。
- アドレスを登録する —
PUT /api/v1/partners/v1/webhooks/endpoint、本文{"url":"https://myapp.ridm.ai/hrum/webhook"}。レスポンスのsecret(whsec_…)は、このとき1回しか表示されません。すぐにサーバーの環境変数HRUM_WEBHOOK_SECRETに入れてください。新しく発行するには{"url": …, "rotateSecret": true}を送ります。 - イベント —
order.paid(決済完了)、order.refunded(返金1件)、payout.scheduled(支払予定)、payout.completed(支払完了)、webhook.test(POST /webhooks/test)。本文は{"id","type","created","data"}で、購入者の情報は含みません。1つの出来事は同じidで1回だけ作られるため、idで重複を除いてください。 - 署名を検証する — ヘッダーは
Hrum-Signature: t=<unix seconds>,v1=<hex>です。v1は、(t + "." + the raw body as received)をシークレットで計算した HMAC-SHA256 です。5分より古いtは拒否してください。 - 再試行 — 10秒以内に 2xx で応答しない場合、1分後、5分後、30分後、2時間後、12時間後に再試行します(最大6回)。配信記録は
GET /webhooks/deliveriesで確認し、POST /webhooks/deliveries/{eventId}/redeliverで再配信できます。
const crypto = require("node:crypto");
function verifyHrumWebhook(rawBody, header, secret, toleranceSec = 300) {
const parts = Object.fromEntries(String(header || "").split(",").map((p) => p.trim().split("=")));
const t = Number(parts.t);
if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
const want = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
const got = Buffer.from(parts.v1 || "", "hex");
return got.length === want.length && crypto.timingSafeEqual(got, want);
}
// Express: verify the raw body before parsing it as JSON
app.post("/hrum/webhook", express.raw({ type: "application/json" }), (req, res) => {
const raw = req.body.toString("utf8");
if (!verifyHrumWebhook(raw, req.get("Hrum-Signature"), process.env.HRUM_WEBHOOK_SECRET)) return res.sendStatus(400);
const event = JSON.parse(raw); // if event.id was already handled, just return 200
res.sendStatus(200);
});
初回デプロイと自動チェック
最初のビルドが成功すると、自動チェックを経て自動で承認され、アドレスがすぐに公開されます。有料プランの料金は、最初のデプロイが公開されるまで差し引かれません。リポジトリにシークレット(キー、トークン、パスワード)が含まれている場合、ビルドは失敗します。新しい外部接続先や新しい危険なパターンが見つかった場合、またはリポジトリの所有者が変わった場合も、デプロイは自動で行われ、その変化は記録されます。自動承認とチェックの結果はすべて記録され、あとから運営者が確認し、問題があれば公開を停止することがあります。公開を停止した場合は、メールとアプリ画面で理由をお知らせします。禁止行為については規約 第26条(韓国語)をご覧ください。
料金と制限
各プランの料金・メモリー・データベース・月間転送量は料金ページにあります。有料プランの料金は、チャージしたクレジットから毎月差し引かれます。請求日に残高が足りないとアプリは停止し、チャージすると自動で再開します。月間転送量は、訪問者に送ったレスポンスとアプリが外部へ送るトラフィック(外部 API の呼び出し、アップロード、データベースへの書き込みを含む)の合計です。WebSocket では双方向のフレームが数えられます。上限に達すると、開いている WebSocket も 1008 で閉じられます。上限に達すると公開とアプリの両方が止まります(翌月1日に再開)。有料プランでは、アプリ画面で転送量ブロックを購入すれば数分以内に復旧します。80%に達したときと上限に達したときに、メールでお知らせします。
バックアップと解約
- アプリ画面の「データベースのバックアップ」でデータベースのダンプを作成します。1時間有効なダウンロードリンクが発行され、1日3回まで作成できます。
- 解約すると、有料プランは支払い済みの期間の終わりに停止し、その後30日間はデータのダウンロードやアプリの復元ができます。30日を過ぎると、アプリとデータベースは削除され、復元できません。すでに差し引かれた料金は返金されません。
お問い合わせ:contact@float.do