Documentación de Vito API

Última actualización: 26 de junio de 2026
01

Resumen

La API de Vito permite que tu aplicación trabaje con el saldo de Vito de un usuario desde dentro de Discord: leerlo, cobrarlo o abonarlo. El usuario confirma cada cobro con su PIN en vetox.io antes de que se mueva ningún Vito, y tu app recibe una notificación del resultado. Vito es la moneda virtual interna del ecosistema de Vetox; usa la API para crear experiencias basadas en Vito: desbloquear contenido, funciones, ventajas o recompensas. No se realiza ninguna compra ni venta con dinero real.

Es una API REST autenticada con una clave secreta. Todos los endpoints devuelven JSON y están versionados bajo /v1.

URL basehttps://api.vetox.io/public/vito/v1
02

Saldos y liquidación

Tu clave de API está vinculada a tu cuenta de Vetox. Cada cargo se liquida a tu favor: cuando tu aplicación deduce Vito de un usuario, ese Vito se abona a tu propio saldo (menos la comisión de la plataforma), nunca se destruye. Cuando tu aplicación añade Vito a un usuario, se financia desde tu saldo.

El Vito siempre se mueve entre saldos, nunca desde o hacia dinero real. Al usuario se le cobra el importe completo que solicitas; tú recibes el importe tras la comisión de la plataforma; los abonos y recompensas se financian desde tu saldo mediante /add.

Comisión de la plataforma — la misma escala que las transferencias de Vito en la app, según tu nivel de membresía: 7 % (Silver / Gold), 6 % (Platinum), 5 % (Diamond). Los cargos de 5 Vito o menos no tienen comisión. Los abonos mediante /add nunca tienen comisión.
03

Requisitos

El acceso a la API de Vito se concede por proyecto. Antes de poder llamarla necesitas:

  • Una membresía de usuario activa (nivel Silver o superior). La API se congela automáticamente si la membresía del propietario caduca, y se restaura cuando vuelve a suscribirse.
  • Una solicitud de desarrollador aprobada. Envía una desde esta página; el equipo de Vetox la revisa manualmente.
  • Términos para desarrolladores de la API aceptados: se confirman al enviar tu solicitud de desarrollador.
  • Los permisos que necesita tu proyecto. Los concede el equipo de Vetox según tu solicitud.

Permisos disponibles

balance:read
Leer el saldo de Vito de un usuario.
deduct:create
Crear una deducción: cobrar el saldo de Vito de un usuario.
credit:create
Añadir Vito a un usuario — financiar recompensas o abonos desde tu saldo.
transfer:create
Crear una transferencia entre dos usuarios.
transactions:read
Listar y leer las transacciones de tu proyecto.
04

Autenticación

Autentica cada solicitud con tu clave API secreta en la cabecera Authorization como token Bearer. Las claves llevan el prefijo vito_live_.

http
Authorization: Bearer vito_live_xxxxxxxxxxxxxxxx

Dos capas opcionales protegen aún más tu proyecto:

  • Lista de IP permitidas: restringe las solicitudes a IPs de servidor específicas (configúralo en la pestaña Ajustes).
  • Límites de tasa: topes de solicitudes por proyecto que escalan con tu nivel de membresía.
05

Claves API: almacenamiento, rotación y seguridad

Tu clave se genera cuando se aprueba tu solicitud. Se muestra una sola vez: revélala desde la pestaña Claves API y guárdala de inmediato.

Vetox nunca almacena tu clave en forma legible (solo un hash con sal). Guárdala como un secreto del servidor —una variable de entorno o un gestor de secretos— nunca en código del cliente, un repositorio git o un navegador.

Rótala desde la pestaña Claves API. La clave anterior sigue funcionando durante un periodo de gracia de 24 horas para que despliegues la nueva sin tiempo de inactividad, y luego deja de hacerlo.

Trata la clave como una contraseña: cualquiera que la tenga puede cobrar a tus usuarios. Si se filtra, rótala de inmediato y revisa tus transacciones recientes.
06

Flujo de confirmación del cobro

Cada cobro pasa por una confirmación que el usuario aprueba con su PIN: tu clave por sí sola nunca mueve Vito:

  1. 1Tu app llama a POST /deduct con el usuario, el importe, el guildId de origen y los detalles del elemento.
  2. 2Vito crea una confirmación pendiente (válida 10 minutos) y devuelve una confirmUrl. Si se indica un guildId, también se envía al usuario un DM con los detalles y un botón a la página de confirmación.
  3. 3El usuario abre la confirmUrl en vetox.io y aprueba el cargo con el PIN de su monedero.
  4. 4Vito deduce los Vito, registra la transacción y —si tienes un webhook configurado— envía un evento confirmation.completed firmado a tu URL de callback.
  5. 5Tu app verifica la firma y completa su acción (por ejemplo, desbloquea el contenido).
Los PIN se introducen solo en vetox.io, nunca dentro de tu app ni de Discord. Si el usuario no confirma, la solicitud caduca a los 10 minutos y recibes un evento confirmation.expired.
07

Parámetros de la solicitud (/deduct)

POST/v1/deduct

El endpoint de deducción acepta los siguientes campos del cuerpo:

discordId
El ID de usuario de Discord del usuario, un snowflake de 17 a 20 dígitos. Obligatorio.
amount
Cantidad de Vito a cobrar, un entero positivo. Obligatorio.
guildId
El servidor de Discord del que se origina el cobro (snowflake). Obligatorio: cada cobro debe provenir de un servidor concreto, lo que además habilita el DM al usuario.
reason
Una descripción breve y legible (≤ 256 caracteres), mostrada al usuario y repetida en el webhook.
merchantRef
Tu propia cadena de referencia (≤ 128 caracteres). Úsala para asociar el webhook a tus registros.
product
Descriptor del elemento: { type, name, description?, imageUrl? }. Se muestra al usuario en la página de confirmación y el DM, y se repite en cada webhook.
product.imageUrl
Imagen de producto opcional: debe ser una URL https://.
metadata
Hasta 10 pares clave/valor de texto, transmitidos tal cual en el webhook.

* campo obligatorio.

Solicitud de ejemplo
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" }
}
Respuesta de ejemplo
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

Añadir Vito — abonos (/add)

POST/v1/add

POST /add abona Vito a un usuario desde tu propio saldo — para recompensas o abonos. Acepta los mismos campos que /deduct salvo guildId y product. A diferencia de un cargo, es inmediato y no requiere el PIN del usuario — tu clave de API es tu autoridad — por lo que la respuesta ya refleja la transferencia completada. El usuario recibe el importe completo; no se aplica comisión de plataforma.

Requiere el ámbito credit:create y suficiente Vito en tu propio saldo — de lo contrario la llamada devuelve 402 INSUFFICIENT_OWNER_FUNDS.
Solicitud de ejemplo
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" }
}
Respuesta de ejemplo
json
{
  "success": true,
  "data": {
    "transactionId": "txn_91b2",
    "toDiscordId": "123456789012345678",
    "amount": 250,
    "ownerBalance": 48250,
    "recipientBalance": 1250,
    "status": "completed"
  },
  "requestId": "req_def456",
  "timestamp": 1735689300000
}
09

Webhooks y verificación de firma

Añade una o más URLs de callback https en la pestaña Ajustes. Vito entrega un POST firmado a cada URL configurada cada vez que una confirmación alcanza un estado final, reintentando hasta 5 veces con retroceso exponencial.

Los webhooks solo se disparan cuando tu proyecto tiene a la vez una URL de callback configurada Y un secreto de firma. Revela tu secreto de firma (whsec_…) una vez desde la pestaña Claves API.

Verificar la firma

Cada entrega lleva una cabecera X-Vito-Signature con la forma ts=<unix>;h1=<hex>. Recalcula el HMAC sobre "<ts>:<rawBody>" con tu secreto de firma y compara en tiempo constante. Verifica siempre contra el cuerpo de la solicitud sin procesar, antes de analizar el JSON.

http
signature = HMAC_SHA256(signingSecret, "<ts>:<rawBody>")
header    = X-Vito-Signature: ts=<unix-seconds>;h1=<hex-signature>
Verificador de referencia (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

Eventos de webhook y cargas útiles

Vito envía uno de cuatro tipos de evento. Todos comparten la misma forma de carga útil; data.status refleja el resultado.

confirmation.completed
El usuario confirmó y se dedujo el Vito. Completa tu acción. Lleva transactionId.
confirmation.failed
No se pudo completar el cargo (por ejemplo, saldo insuficiente o un error interno). Lleva failureReason.
confirmation.expired
El usuario no confirmó en 10 minutos. No se movió ningún Vito.
confirmation.cancelled
El usuario canceló la solicitud. No se movió ningún Vito.
credit.completed
Se liquidó un abono /add financiado por el propietario. Incluye el destinatario y transactionId, y se dispara de inmediato — no hay paso de confirmación.
Carga útil de ejemplo (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

Códigos de error

Los errores devuelven un estado no 2xx con un cuerpo JSON que lleva un error.code de nivel superior. Casos comunes:

400VITO_VALIDATION_ERROR
Un campo de la solicitud falta o está mal formado (por ejemplo, un guildId no válido).
401invalid key
Clave API ausente, mal formada o desconocida.
403OWNER_MEMBERSHIP_INACTIVE
La membresía del propietario del proyecto ha caducado: la API está congelada hasta que vuelva a suscribirse.
403scope / IP
La clave carece del permiso requerido, o la IP de la solicitud no está en la lista permitida.
404not found
El usuario, la transacción o el recurso no existe.
402insufficient balance
El usuario no tiene suficiente Vito para el cargo.
402INSUFFICIENT_OWNER_FUNDS
Tu propio saldo (el del propietario) es demasiado bajo para financiar un abono /add.
429rate limited
Has superado el límite de tasa de tu proyecto: espera y reintenta.
Forma de la respuesta de error
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

Ejemplos de integración

Crear una solicitud de cobro:

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

Consultar el saldo de un usuario:

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

Pruebas con la integración de demostración

Se proporciona una integración de demostración independiente para ejercitar el flujo completo de principio a fin sin escribir código. Recorre solicitud → confirmación → PIN → webhook → finalización.

  1. 1Abre la carpeta de la integración de demostración y añade tu clave de la API de Vito, un token de bot de Discord y el ID de tu servidor (guild) de prueba a config.json.
  2. 2Ejecuta npm install && npm start y abre http://localhost:4000.
  3. 3Busca un usuario por su ID de Discord, elige un elemento e inicia un cobro: la demo llama a /deduct y muestra la confirmUrl.
  4. 4Aprueba el PIN en vetox.io; la demo detecta la finalización (por webhook si está configurado, si no por sondeo) y completa la acción de ejemplo.
Recibir el webhook requiere un callback https público y un secreto de firma; sin ellos, la demo recurre al sondeo de tus transacciones, así que funciona sin configuración.
14

Buenas prácticas, límites y seguridad

Seguridad

  • Mantén tu clave API y el secreto de firma solo en el servidor; rota cualquiera de ellos de inmediato si se filtra.
  • Verifica siempre la firma del webhook contra el cuerpo sin procesar y rechaza las entregas de más de ~5 minutos (protección contra reenvíos).
  • Completa tu acción solo con confirmation.completed, nunca con la respuesta de /deduct (en ese punto el cobro aún no es definitivo).
  • Usa la lista de IP permitidas y solicita solo los permisos que realmente necesitas.

Límites

  • Las confirmaciones caducan a los 10 minutos; trata las solicitudes no confirmadas como abandonadas.
  • Los límites de tasa son por proyecto y escalan con el nivel de membresía del propietario.
  • amount debe ser un entero positivo; metadata está limitado a 10 claves.
Idempotency-Key evita duplicar reintentosLos webhooks reintentan 5× con retrocesoResponde 2xx rápido, cumple de forma asíncrona