Vito API Documentation

Last updated: June 26, 2026
01

Overview

The Vito API lets your application work with a user's Vito balance from inside Discord — read it, charge it, or credit it back. Every charge is confirmed by the user with their PIN on vetox.io before any Vito moves, and your app is notified of the result. Vito is the Vetox in-ecosystem virtual currency; use the API to build Vito-based experiences — unlocking content, features, perks, or rewards. No real-money buying or selling takes place.

It is a REST API authenticated with a secret key. All endpoints return JSON and are versioned under /v1.

Base URLhttps://api.vetox.io/public/vito/v1
02

Balances & settlement

Your API key is tied to your Vetox account. Every charge settles to you: when your app deducts Vito from a user, that Vito is credited to your own balance (minus the platform fee), never burned. When your app adds Vito to a user, it is funded from your balance.

Vito always moves between balances — never to or from real money. The user is charged the full amount you request; you receive the amount after the platform fee; credits and rewards are funded from your balance via /add.

Platform fee — the same schedule as in-app Vito transfers, based on your membership tier: 7% (Silver / Gold), 6% (Platinum), 5% (Diamond). Charges of 5 Vito or less are fee-free. Credits via /add are never charged a fee.
03

Requirements

Access to the Vito API is granted per project. Before you can call it you need:

  • An active User Membership (Silver tier or higher). The API freezes automatically if the owner's membership lapses, and restores when they resubscribe.
  • An approved developer application. Submit one from this page; the Vetox team reviews it manually.
  • Accepted API Developer Terms — acknowledged when you submit your developer application.
  • The scopes your project needs. Scopes are granted by Vetox staff based on your application.

Available scopes

balance:read
Read a user's Vito balance.
deduct:create
Create a deduction — charge a user's Vito balance.
credit:create
Add Vito to a user — fund rewards or credits from your balance.
transfer:create
Create a transfer between two users.
transactions:read
List and read your project's transactions.
04

Authentication

Authenticate every request with your secret API key in the Authorization header as a Bearer token. Keys are prefixed with vito_live_.

http
Authorization: Bearer vito_live_xxxxxxxxxxxxxxxx

Two optional layers further protect your project:

  • IP allowlist — restrict requests to specific server IPs (configure it in the Settings tab).
  • Rate limits — per-project request ceilings that scale with your membership tier.
05

API keys — storage, rotation & security

Your key is generated when your application is approved. It is shown only once — reveal it from the API Keys tab and store it immediately.

Vetox never stores your key in readable form (only a salted hash). Keep it as a server-side secret — an environment variable or a secrets manager — never in client code, a git repository, or a browser.

Rotate from the API Keys tab. The previous key keeps working for a 24-hour grace period so you can roll out the new key with zero downtime, then it stops.

Treat the key like a password — anyone holding it can charge your users. If it leaks, rotate immediately and review your recent transactions.
06

Charge confirmation flow

Every charge goes through a confirmation the user approves with their PIN — your key alone never moves Vito:

  1. 1Your app calls POST /deduct with the user, amount, originating guildId, and item details.
  2. 2Vito creates a pending confirmation (valid for 10 minutes) and returns a confirmUrl. When a guildId is given, the user is also DM'd the details with a button to the confirm page.
  3. 3The user opens the confirmUrl on vetox.io and approves the charge with their wallet PIN.
  4. 4Vito deducts the Vito, records the transaction, and — if you have a webhook configured — sends a signed confirmation.completed event to your callback URL.
  5. 5Your app verifies the signature and completes its action (for example, unlocks the content).
PINs are entered only on vetox.io — never inside your app or Discord. If the user never confirms, the request expires after 10 minutes and you receive a confirmation.expired event.
07

Request parameters (/deduct)

POST/v1/deduct

The deduction endpoint accepts the following body fields:

discordId
The user's Discord user ID — a 17–20 digit snowflake. Required.
amount
Amount of Vito to charge — a positive integer. Required.
guildId
The Discord server the charge originates from (snowflake). Required — every charge must come from a specific server, which also enables the user DM.
reason
A short human-readable description (≤ 256 chars), shown to the user and echoed on the webhook.
merchantRef
Your own reference string (≤ 128 chars). Use it to match the webhook back to your records.
product
Item descriptor — { type, name, description?, imageUrl? }. Shown to the user on the confirm page and DM, and echoed on every webhook.
product.imageUrl
Optional product image — must be an https:// URL.
metadata
Up to 10 string key/value pairs, passed through verbatim on the webhook.

* required field.

Example request
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" }
}
Example response
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

Adding Vito — credits (/add)

POST/v1/add

POST /add credits a user from your own balance — for rewards or credits. It takes the same fields as /deduct except guildId and product. Unlike a charge it is instant and needs no user PIN — your API key is your authority — so the response already reflects the completed transfer. The user receives the full amount; no platform fee applies.

Requires the credit:create scope and enough Vito in your own balance — otherwise the call returns 402 INSUFFICIENT_OWNER_FUNDS.
Example request
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" }
}
Example response
json
{
  "success": true,
  "data": {
    "transactionId": "txn_91b2",
    "toDiscordId": "123456789012345678",
    "amount": 250,
    "ownerBalance": 48250,
    "recipientBalance": 1250,
    "status": "completed"
  },
  "requestId": "req_def456",
  "timestamp": 1735689300000
}
09

Webhooks & signature verification

Add one or more https callback URLs in the Settings tab. Vito delivers a signed POST to every configured URL whenever a confirmation reaches a terminal state, retrying up to 5 times with exponential backoff.

Webhooks fire only when your project has BOTH a callback URL configured AND a signing secret. Reveal your signing secret (whsec_…) once from the API Keys tab.

Verifying the signature

Each delivery carries an X-Vito-Signature header of the form ts=<unix>;h1=<hex>. Recompute the HMAC over "<ts>:<rawBody>" with your signing secret and compare in constant time. Always verify against the raw request body, before JSON parsing.

http
signature = HMAC_SHA256(signingSecret, "<ts>:<rawBody>")
header    = X-Vito-Signature: ts=<unix-seconds>;h1=<hex-signature>
Reference verifier (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 events & payloads

Vito sends one of four event types. They all share the same payload shape; data.status reflects the outcome.

confirmation.completed
The user confirmed and the Vito was deducted. Complete your action. Carries transactionId.
confirmation.failed
The charge could not be completed (for example, insufficient balance or an internal error). Carries failureReason.
confirmation.expired
The user did not confirm within 10 minutes. No Vito moved.
confirmation.cancelled
The user cancelled the request. No Vito moved.
credit.completed
An owner-funded /add credit settled. Carries the recipient and transactionId, and fires immediately — there is no confirmation step.
Example payload (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 codes

Errors return a non-2xx status with a JSON body carrying a top-level error.code. Common cases:

400VITO_VALIDATION_ERROR
A request field is missing or malformed (for example, an invalid guildId).
401invalid key
Missing, malformed, or unknown API key.
403OWNER_MEMBERSHIP_INACTIVE
The project owner's membership has lapsed — the API is frozen until they resubscribe.
403scope / IP
The key lacks the required scope, or the request IP is not allowlisted.
404not found
The user, transaction, or resource does not exist.
402insufficient balance
The user does not have enough Vito for the charge.
402INSUFFICIENT_OWNER_FUNDS
Your own (owner) balance is too low to fund an /add credit.
429rate limited
You exceeded your project's rate limit — back off and retry.
Error response shape
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

Integration examples

Create a charge request:

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" }
  }'

Check a user's balance:

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

Testing with the demo integration

A standalone demo integration is provided to exercise the full flow end-to-end without writing any code. It walks through request → confirmation → PIN → webhook → completion.

  1. 1Open the demo integration folder and add your Vito API key, a Discord bot token, and your test server (guild) ID to config.json.
  2. 2Run npm install && npm start, then open http://localhost:4000.
  3. 3Look up a user by Discord ID, pick an item, and start a charge — the demo calls /deduct and shows the confirmUrl.
  4. 4Approve the PIN on vetox.io; the demo detects completion (via webhook if configured, otherwise by polling) and completes the sample action.
Receiving the webhook needs a public https callback and signing secret; without them the demo falls back to polling your transactions, so it works with zero setup.
14

Best practices, limits & security

Security

  • Keep your API key and signing secret server-side only; rotate immediately if either leaks.
  • Always verify the webhook signature against the raw body, and reject deliveries older than ~5 minutes (replay protection).
  • Complete your action only on confirmation.completed — never on the /deduct response (the charge is not final at that point).
  • Use the IP allowlist and request only the scopes you actually need.

Limits

  • Confirmations expire after 10 minutes; treat unconfirmed requests as abandoned.
  • Rate limits are per-project and scale with the owner's membership tier.
  • amount must be a positive integer; metadata is capped at 10 keys.
Idempotency-Key dedupes retriesWebhooks retry 5× with backoffAck 2xx fast, fulfil async