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
두 사용자 간 송금 생성.
transactions:read
프로젝트의 거래 목록 조회 및 읽기.
04

인증

모든 요청을 Authorization 헤더에 비밀 API 키를 Bearer 토큰으로 넣어 인증하세요. 키에는 vito_live_ 접두사가 붙습니다.

http
Authorization: Bearer vito_live_xxxxxxxxxxxxxxxx

두 가지 선택적 계층이 프로젝트를 추가로 보호합니다:

  • 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를 차감하고 거래를 기록하며 — 웹훅을 구성했다면 — 서명된 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
사용자에게 표시되고 웹훅에서 반복되는 짧고 읽기 쉬운 설명(256자 이하).
merchantRef
자체 참조 문자열(128자 이하). 웹훅을 귀하의 기록과 매칭하는 데 사용하세요.
product
항목 설명자 — { type, name, description?, imageUrl? }. 확인 페이지와 DM에서 사용자에게 표시되고 모든 웹훅에서 반복됩니다.
product.imageUrl
선택적 상품 이미지 — https:// URL이어야 합니다.
metadata
웹훅에서 그대로 전달되는 최대 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

웹훅 및 서명 검증

설정 탭에서 하나 이상의 https 콜백 URL을 추가하세요. Vito는 확인이 최종 상태에 도달할 때마다 구성된 각 URL로 서명된 POST를 전달하며, 지수 백오프로 최대 5회 재시도합니다.

웹훅은 프로젝트에 구성된 콜백 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

웹훅 이벤트 및 페이로드

Vito는 네 가지 이벤트 유형 중 하나를 전송합니다. 모두 동일한 페이로드 형식을 공유하며 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 → 웹훅 → 완료 과정을 거칩니다.

  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을 승인하면 데모가 완료를 감지하고(구성된 경우 웹훅, 아니면 폴링) 샘플 작업을 완료합니다.
웹훅 수신에는 공개 https 콜백과 서명 시크릿이 필요합니다. 이것들이 없으면 데모는 거래 폴링으로 대체되므로 설정 없이 작동합니다.
14

모범 사례, 제한 및 보안

보안

  • API 키와 서명 시크릿을 서버 측에만 보관하고, 둘 중 하나라도 유출되면 즉시 교체하세요.
  • 웹훅 서명을 항상 원시 본문에 대해 검증하고 약 5분보다 오래된 전달은 거부하세요(재생 방지).
  • 자체 작업은 confirmation.completed에서만 완료하세요 — /deduct 응답에서는 절대 하지 마세요(그 시점에 청구는 확정되지 않았습니다).
  • IP 허용 목록을 사용하고 실제로 필요한 스코프만 요청하세요.

제한

  • 확인은 10분 후 만료됩니다. 확인되지 않은 요청은 포기된 것으로 처리하세요.
  • 속도 제한은 프로젝트별이며 소유자의 멤버십 등급에 따라 확장됩니다.
  • amount는 양의 정수여야 하며 metadata는 최대 10개 키로 제한됩니다.
Idempotency-Key로 재시도 중복 제거웹훅은 백오프로 5회 재시도2xx를 빠르게 응답하고 비동기로 이행