Vito API ドキュメント

最終更新日:2026年6月26日
01

概要

Vito APIを使うと、アプリはDiscord内からユーザーのVito残高を扱えます — 残高の読み取り、課金、または払い戻し(クレジット)です。ユーザーはVitoが移動する前に、vetox.ioでPINを使って各課金を確認し、結果がアプリに通知されます。Vitoは Vetox のエコシステム内仮想通貨です。APIを使って Vito ベースの体験を構築できます — コンテンツ・機能・特典・報酬のアンロックなど。実際のお金による売買は一切行われません。

これは秘密キーで認証するREST APIです。すべてのエンドポイントはJSONを返し、/v1の下でバージョン管理されています。

ベースURLhttps://api.vetox.io/public/vito/v1
02

残高と精算

APIキーはあなたのVetoxアカウントに紐づいています。すべての課金はあなたに精算されます。アプリがユーザーからVitoを差し引くと、そのVitoは(プラットフォーム手数料を差し引いて)あなた自身の残高に入金され、消滅することはありません。アプリがユーザーにVitoを追加する場合は、あなたの残高から支払われます。

Vitoは常に残高間で移動し、実際のお金との間で出入りすることはありません。ユーザーにはあなたが請求した全額が課金され、あなたはプラットフォーム手数料を差し引いた額を受け取ります。クレジットや報酬は /add を通じてあなたの残高から賄われます。

プラットフォーム手数料 — アプリ内のVito送金と同じ体系で、メンバーシップ等級に基づきます:7%(Silver / Gold)、6%(Platinum)、5%(Diamond)。5 Vito以下の課金は手数料無料です。/add によるクレジットには手数料が一切かかりません。
03

要件

Vito APIへのアクセスはプロジェクトごとに付与されます。呼び出す前に必要なもの:

  • 有効なユーザーメンバーシップ(Silver以上)。所有者のメンバーシップが失効するとAPIは自動的に凍結され、再登録すると復旧します。
  • 承認された開発者申請。このページから申請してください。Vetoxチームが手動で審査します。
  • API 開発者規約への同意 — 開発者申請を送信した時点で承認されます。
  • プロジェクトに必要なスコープ。申請内容に基づきVetoxチームが付与します。

利用可能なスコープ

balance:read
ユーザーのVito残高を読み取る。
deduct:create
差し引きを作成する — ユーザーのVito残高に課金する。
credit:create
ユーザーにVitoを追加 — 残高から報酬やクレジットを賄います。
transfer:create
2人のユーザー間で送金を作成する。
transactions:read
プロジェクトの取引を一覧表示・読み取る。
04

認証

すべてのリクエストを、Authorizationヘッダーに秘密APIキーをBearerトークンとして付けて認証します。キーには vito_live_ のプレフィックスが付きます。

http
Authorization: Bearer vito_live_xxxxxxxxxxxxxxxx

2つの任意のレイヤーがプロジェクトをさらに保護します:

  • IP許可リスト — リクエストを特定のサーバーIPに制限します(設定タブで構成)。
  • レート制限 — メンバーシップに応じて拡張されるプロジェクトごとのリクエスト上限。
05

APIキー — 保管、ローテーション、セキュリティ

キーは申請が承認されたときに生成されます。一度だけ表示されるので、APIキータブから表示してすぐに保管してください。

Vetoxはキーを読み取り可能な形では決して保存しません(ソルト付きハッシュのみ)。環境変数やシークレットマネージャーなどサーバー側のシークレットとして保管し、クライアントコード・gitリポジトリ・ブラウザには絶対に置かないでください。

APIキータブからローテーションします。古いキーは新しいキーを無停止で展開できるよう24時間の猶予期間中は動作し続け、その後停止します。

キーはパスワードのように扱ってください — 持っている人は誰でもユーザーに課金できます。漏洩した場合はすぐにローテーションし、最近の取引を確認してください。
06

課金確認フロー

すべての課金は、ユーザーがPINで承認する確認を経ます — キーだけでは決してVitoは動きません:

  1. 1アプリはユーザー・金額・発生元のguildId・アイテム詳細を付けてPOST /deductを呼び出します。
  2. 2Vitoは保留中の確認(10分間有効)を作成し、confirmUrlを返します。guildIdが指定されると、ユーザーには詳細と確認ページへのボタンを含むDMも送信されます。
  3. 3ユーザーはvetox.ioでconfirmUrlを開き、ウォレットのPINで課金を承認します。
  4. 4VitoはVitoを差し引き、取引を記録し — Webhookを構成していれば — 署名付きのconfirmation.completedイベントをコールバックURLに送信します。
  5. 5アプリは署名を検証し、自分のアクションを完了します(例: コンテンツをアンロック)。
PINはvetox.ioでのみ入力します — アプリやDiscord内では決して入力しません。ユーザーが確認しない場合、リクエストは10分後に期限切れになり、confirmation.expiredイベントを受け取ります。
07

リクエストパラメータ(/deduct)

POST/v1/deduct

差し引きエンドポイントは次のボディフィールドを受け付けます:

discordId
ユーザーのDiscordユーザーID — 17〜20桁のsnowflake。必須。
amount
課金するVitoの量 — 正の整数。必須。
guildId
課金が発生したDiscordサーバー(snowflake)。必須 — すべての課金は特定のサーバーから発生する必要があり、これによりユーザーへのDMも有効になります。
reason
ユーザーに表示され、Webhookで繰り返される短い読みやすい説明(256文字以下)。
merchantRef
独自の参照文字列(128文字以下)。Webhookを自分の記録に対応付けるために使用します。
product
アイテム記述子 — { type, name, description?, imageUrl? }。確認ページとDMでユーザーに表示され、各Webhookで繰り返されます。
product.imageUrl
任意の商品画像 — https:// のURLである必要があります。
metadata
Webhookでそのまま渡される最大10個の文字列キー/値ペア。

* 必須フィールド。

リクエスト例
http
POST https://api.vetox.io/public/vito/v1/deduct
Authorization: Bearer vito_live_xxxxxxxxxxxxxxxx
Content-Type: application/json
Idempotency-Key: req_9a1f2b   # optional, dedupes retries for 24h

{
  "discordId": "123456789012345678",
  "amount": 500,
  "guildId": "1470389021162209280",
  "reason": "Unlock premium theme",
  "merchantRef": "ref_1042",
  "product": {
    "type": "feature",
    "name": "Premium theme",
    "description": "Unlocks the premium theme pack",
    "imageUrl": "https://cdn.example.com/theme.png"
  },
  "metadata": { "ref": "1042" }
}
レスポンス例
json
{
  "success": true,
  "data": {
    "confirmationId": "f3c9...e1",
    "confirmUrl": "https://vetox.io/confirm/vito/f3c9...e1",
    "amount": 500,
    "expiresAt": 1735689600000,
    "status": "pending"
  },
  "requestId": "req_abc123",
  "timestamp": 1735689300000
}
08

Vitoの追加 — クレジット(/add)

POST/v1/add

POST /add は、あなた自身の残高からユーザーにVitoを入金します(報酬やクレジット用)。guildId と product を除き /deduct と同じフィールドを受け取ります。課金と異なり即時で、ユーザーのPINは不要です(APIキーがあなたの権限です)。そのため応答にはすでに完了した送金が反映されます。ユーザーは全額を受け取り、プラットフォーム手数料はかかりません。

credit:create スコープと、あなた自身の残高に十分なVitoが必要です。不足する場合、呼び出しは 402 INSUFFICIENT_OWNER_FUNDS を返します。
リクエスト例
http
POST https://api.vetox.io/public/vito/v1/add
Authorization: Bearer vito_live_xxxxxxxxxxxxxxxx
Content-Type: application/json
Idempotency-Key: credit_7f3a   # optional, dedupes retries for 24h

{
  "discordId": "123456789012345678",
  "amount": 250,
  "reason": "Event reward",
  "merchantRef": "reward_1042",
  "metadata": { "ref": "1042" }
}
レスポンス例
json
{
  "success": true,
  "data": {
    "transactionId": "txn_91b2",
    "toDiscordId": "123456789012345678",
    "amount": 250,
    "ownerBalance": 48250,
    "recipientBalance": 1250,
    "status": "completed"
  },
  "requestId": "req_def456",
  "timestamp": 1735689300000
}
09

Webhookと署名検証

設定タブで1つ以上のhttpsコールバックURLを追加します。Vitoは確認が最終状態に達するたびに、署名付きPOSTを各構成済みURLに配信し、指数バックオフで最大5回まで再試行します。

Webhookは、プロジェクトに構成済みのコールバックURLと署名シークレットの両方がある場合にのみ発火します。署名シークレット(whsec_…)をAPIキータブから一度表示してください。

署名の検証

各配信には ts=<unix>;h1=<hex> 形式のX-Vito-Signatureヘッダーが付きます。署名シークレットで "<ts>:<rawBody>" に対してHMACを再計算し、一定時間で比較してください。JSONを解析する前に、必ず生のリクエストボディに対して検証してください。

http
signature = HMAC_SHA256(signingSecret, "<ts>:<rawBody>")
header    = X-Vito-Signature: ts=<unix-seconds>;h1=<hex-signature>
リファレンス検証(Node / Express):
javascript
import crypto from 'crypto';
import express from 'express';

const app = express();
// IMPORTANT: verify against the RAW body, before JSON parsing.
app.post('/webhooks/vito', express.raw({ type: '*/*' }), (req, res) => {
  const sig = req.header('X-Vito-Signature') || '';   // "ts=<unix>;h1=<hmac>[;h1=<hmac>]"
  const pairs = sig.split(';').map(p => p.split('='));
  const ts = pairs.find(([k]) => k === 'ts')?.[1];
  const sigs = pairs.filter(([k]) => k === 'h1').map(([, v]) => v);
  const rawBody = req.body.toString('utf8');

  const expected = crypto
    .createHmac('sha256', process.env.VITO_SIGNING_SECRET)     // whsec_...
    .update(`${ts}:${rawBody}`)
    .digest('hex');
  const expectedBuf = Buffer.from(expected, 'hex');

  // During a 24h secret rotation the header carries TWO h1= values — accept ANY.
  // Length-guard FIRST: crypto.timingSafeEqual throws on a length mismatch.
  const ok = sigs.some(h => {
    const got = Buffer.from(h, 'hex');
    return (
      got.length === expectedBuf.length &&
      crypto.timingSafeEqual(got, expectedBuf)
    );
  });
  // Reject anything older than ~5 minutes to stop replays.
  const fresh = ts && Math.abs(Date.now() / 1000 - Number(ts)) < 300;
  if (!ok || !fresh) return res.status(401).end();

  const event = JSON.parse(rawBody);
  if (event.eventType === 'confirmation.completed') {
    grantPremiumTheme(event.data.merchantRef, event.data.fromDiscordId);
  }
  res.status(200).end();   // ack fast; run your action async if slow
});
10

Webhookイベントとペイロード

Vitoは4種類のイベントのいずれかを送信します。すべて同じペイロード形式を共有し、data.statusが結果を反映します。

confirmation.completed
ユーザーが確認し、Vitoが差し引かれました。自分のアクションを完了してください。transactionIdを含みます。
confirmation.failed
課金を完了できませんでした(例: 残高不足や内部エラー)。failureReasonを含みます。
confirmation.expired
ユーザーが10分以内に確認しませんでした。Vitoは動いていません。
confirmation.cancelled
ユーザーがリクエストをキャンセルしました。Vitoは動いていません。
credit.completed
オーナーが賄った /add 入金が精算されました。受取人と transactionId を含み、即座に発火します — 確認ステップはありません。
ペイロード例(confirmation.completed)
http
POST https://your-app.com/webhooks/vito
X-Vito-Signature: ts=1735689600;h1=4f8b2c...e9
X-Vito-Event-Id: evt_3a2b1c
X-Vito-Event-Type: confirmation.completed
Content-Type: application/json

{
  "eventId": "evt_3a2b1c",
  "eventType": "confirmation.completed",
  "timestamp": 1735689600000,
  "data": {
    "confirmationId": "f3c9...e1",
    "type": "deduct",
    "fromDiscordId": "123456789012345678",
    "toDiscordId": null,
    "amount": 500,
    "reason": "Unlock premium theme",
    "merchantRef": "ref_1042",
    "product": { "type": "feature", "name": "Premium theme" },
    "status": "completed",
    "transactionId": "txn_77a2",
    "metadata": { "ref": "1042" },
    "failureReason": null
  }
}
11

エラーコード

エラーは、トップレベルのerror.codeを含むJSONボディと共に2xx以外のステータスを返します。よくあるケース:

400VITO_VALIDATION_ERROR
リクエストフィールドが欠落または不正です(例: 無効なguildId)。
401invalid key
欠落、不正、または不明なAPIキー。
403OWNER_MEMBERSHIP_INACTIVE
プロジェクト所有者のメンバーシップが失効しています — 所有者が再登録するまでAPIは凍結されます。
403scope / IP
キーに必要なスコープがない、またはリクエストのIPが許可リストにありません。
404not found
ユーザー、取引、またはリソースが存在しません。
402insufficient balance
ユーザーに課金分のVitoが足りません。
402INSUFFICIENT_OWNER_FUNDS
あなた自身(オーナー)の残高が低すぎて /add 入金を賄えません。
429rate limited
プロジェクトのレート制限を超えました — 待ってから再試行してください。
エラーレスポンスの形式
json
{
  "success": false,
  "error": { "type": "Bad Request", "message": "guildId must be a valid Discord server ID", "code": "VITO_VALIDATION_ERROR" },
  "requestId": "req_abc123",
  "timestamp": 1735689300000
}
12

統合の例

課金リクエストを作成:

POST/v1/deduct
bash
curl -X POST https://api.vetox.io/public/vito/v1/deduct \
  -H "Authorization: Bearer $VITO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "discordId": "123456789012345678",
    "amount": 500,
    "guildId": "1470389021162209280",
    "merchantRef": "ref_1042",
    "product": { "type": "feature", "name": "Premium theme" }
  }'

ユーザーの残高を確認:

GET/v1/balance/:discordId
bash
curl https://api.vetox.io/public/vito/v1/balance/123456789012345678 \
  -H "Authorization: Bearer $VITO_API_KEY"
13

デモ統合でのテスト

コードを書かずに全フローをエンドツーエンドで試せるスタンドアロンのデモ統合が用意されています。リクエスト→確認→PIN→Webhook→完了を一通り体験できます。

  1. 1デモ統合フォルダーを開き、Vito APIキー、Discordボットトークン、テストサーバー(guild)IDをconfig.jsonに追加します。
  2. 2npm install && npm start を実行し、http://localhost:4000 を開きます。
  3. 3Discord IDでユーザーを検索し、アイテムを選んで課金を開始します — デモは/deductを呼び出してconfirmUrlを表示します。
  4. 4vetox.ioでPINを承認すると、デモは完了を検出し(構成済みならWebhook、なければポーリング)、サンプルのアクションを完了します。
Webhookの受信には公開のhttpsコールバックと署名シークレットが必要です。これらがない場合、デモは取引のポーリングにフォールバックするため、設定なしで動作します。
14

ベストプラクティス、制限、セキュリティ

セキュリティ

  • APIキーと署名シークレットはサーバー側のみに保持し、いずれかが漏洩したらすぐにローテーションしてください。
  • Webhook署名は常に生のボディに対して検証し、約5分より古い配信は拒否してください(リプレイ保護)。
  • 自分のアクションの完了はconfirmation.completedのときだけにしてください — /deductのレスポンスでは決して行わないでください(その時点で課金は確定していません)。
  • IP許可リストを使用し、本当に必要なスコープだけを要求してください。

制限

  • 確認は10分後に期限切れになります。未確認のリクエストは放棄されたものとして扱ってください。
  • レート制限はプロジェクトごとで、所有者のメンバーシップに応じて拡張されます。
  • amountは正の整数である必要があります。metadataは最大10キーまでです。
Idempotency-Keyで再試行を重複排除Webhookはバックオフで5回再試行2xxを素早く返し、非同期で履行