Skip to main content
Requires a Silver Membership or above — both to apply and for every authenticated call. If the key owner’s Membership lapses, the project freezes until they resubscribe.
A REST API that lets your application work with a user’s Vito balance from inside Discord — read it, charge it, credit it, or move it between users. All endpoints return JSON and are versioned under /v1.
Vito never moves to or from real money. It only ever moves between Vetox balances.
Building on Node.js? Don’t hand-roll REST calls — use the official @vetox-bot/vito package. It covers all nine endpoints plus webhook verification, and handles idempotency and retries for you. See Official Node.js SDK below.

Getting access

Access is granted per project. You need all four:
1

An active Membership, Silver or above

Checked on every call, not just at approval.
2

An approved developer application

Submitted from the Vito API page in your dashboard. Reviewed manually by the Vetox team.
3

Accepted API Developer Terms

Acknowledged when you submit the application.
4

The scopes your project needs

Granted by Vetox staff based on what you described.

Scopes

Authentication

Send your secret key as a Bearer token:
Two optional layers harden a project further:
  • IP allowlist — restrict calls to specific server IPs
  • Rate limits — per-project ceilings that scale with the owner’s Membership tier

Keys, rotation and storage

Your key and signing secret are revealed exactly once. Once approved you have a 7-day window to reveal them from the API Keys tab. Vetox keeps only a hash and cannot show them again — miss the window and you must rotate to get new ones.
  • Keep it server-side only — anyone holding it can charge your users
  • Rotate from the API Keys tab. The previous key keeps working for a 24-hour grace period so you can deploy without downtime
  • The webhook signing secret rotates separately, with its own 24-hour overlap
  • If it leaks, rotate immediately

Official Node.js SDK

The official @vetox-bot/vito package wraps all nine endpoints plus webhook verification. It handles the Idempotency-Key header, retries with exponential backoff, timeouts, and error classification for you.

Installation

Requires Node.js 20 or newer. The package has zero runtime dependencies — it uses the built-in fetch and node:crypto — and ships both ESM and CommonJS with full TypeScript definitions.

Initialization

Omit apiKey and the SDK reads VITO_API_KEY from the environment. The key format is validated at construction, so a malformed key fails immediately instead of costing you a round trip and a 401.
The key moves money — always keep it server-side, never in a client bundle or a browser. console.log(vito) prints [redacted] instead of the key, and the SDK refuses any http:// baseUrl for a non-local host so the key cannot travel in plaintext.

Client options

Available methods

Every method returns the unwrapped data field directly — you never unpack success or data yourself. Each also accepts per-call options: { timeoutMs, maxRetries, signal, headers }, and the write methods additionally accept { idempotencyKey }.

Usage examples

Checking the key at startup

Reading a balance

Charging a user (selling an item)

Record the order as pending here, nothing more. The charge has not happened yet — fulfil on the confirmation.completed webhook, not on this response.

Crediting a user

Paging through transactions

Idempotency and retries

The SDK sends an Idempotency-Key header on every write (deduct, credit, transfer). If you do not supply one it generates the key once per call and replays that same key on every retry, so a retry can never settle the operation twice. Supply your own key when the same logical operation may be retried from a new process — a job runner, a queue redelivery, or a scheduled sweep:
auth.rotateKey() is excluded deliberately — a retry there mints a second key and invalidates the one the first attempt returned.
If Retry-After is longer than maxRetryDelayMs (your hourly allowance is genuinely exhausted) the SDK throws VitoRateLimitError immediately rather than sleeping through your request budget.

Verifying webhooks with the SDK

constructEvent checks the 5-minute replay window, compares in constant time against every h1 in the header — so it works through the 24-hour secret-rotation overlap automatically — then parses and returns the typed event. In a Next.js App Router route handler, use the variant that reads the raw body itself:
The signature covers the raw bytes. In Express, mount express.raw({ type: 'application/json' }) on the webhook route — express.json() consumes the body and every verification then fails. In Next.js, do not call request.json() before constructEventFromRequest.
Delivery is at-least-once. De-duplicate on event.eventId before you run any side effect.

Error handling

Everything the SDK throws extends VitoError and carries code, status, type, requestId and retryable.
Always log requestId — it is what support needs to trace a specific call.

Cancelling a call

Cancelling also stops any pending retry, and throws VitoConnectionError with code VITO_SDK_ABORTED.

Charging a user

Your key alone cannot move a user’s Vito. Every charge requires the user to approve it with their wallet PIN, on vetox.io — never inside your app or inside Discord.
1

Your app calls POST /v1/deduct

With the user, the amount, the originating guildId, and item details.
2

Vito returns a confirmUrl

A pending confirmation, valid for 10 minutes. The user is also DMed.
3

The user approves with their PIN

On vetox.io.
4

Vito settles and notifies

The balance is deducted, the transaction recorded, and a signed webhook sent if you have one configured.
5

Your app verifies and completes

Check the signature, then unlock the content or deliver the item.
Complete your action only on confirmation.completed — never on the /deduct response. The charge is not final at that point.

Request parameters — /v1/deduct

Crediting a user

POST /v1/add credits a user from your own balance — for rewards or refunds. Same fields as /deduct except guildId and product.
Unlike a charge, a credit has no confirmation step — it settles immediately. Requires the credit:create scope and sufficient balance, or the call returns 402 VITO_INSUFFICIENT_OWNER_FUNDS.

Fees

Every charge settles to you, minus the platform fee — the same schedule as in-app Vito transfers, based on your Membership tier:
Amounts of 5 Vito or less are fee-free, and credits via /v1/add are always fee-free.

Webhooks

Add one or more https callback URLs in the Settings tab. Vito delivers a signed POST whenever a confirmation reaches a terminal state.
Webhooks fire only when your project has both a callback URL and a signing secret. Reveal the secret (whsec_…) once from the API Keys tab.

Verifying the signature

Each delivery carries an X-Vito-Signature header:
Two more headers accompany every delivery — use X-Vito-Event-Id as your de-duplication key, since a retry re-sends the same id:
During a signing-secret rotation the header carries more than one signature, newest first:
Accept the delivery if any h1 matches. A verifier that reads only the first one rejects every webhook until it has redeployed the new secret — which defeats the whole point of the 24-hour overlap.
On Node.js: Webhooks.constructEvent from @vetox-bot/vito does all of the following for you — the replay window, matching every h1, and the constant-time comparison — and returns the typed event. The code below is for a manual implementation or another language.
Recompute the HMAC over <ts>:<rawBody> with your signing secret and compare in constant time.
Verify against the raw body, before any JSON parsing or middleware rewrites it.

Events

Five event types, all sharing one payload shape. data.status carries the outcome.
Webhooks retry 5 times with backoff. Acknowledge with a 2xx quickly and do your fulfilment asynchronously.

Error codes

Every response is wrapped in an envelope. A success carries data; a failure carries error, never both:
Every code is prefixed VITO_. Branch on the full string — a bare RATE_LIMITED or FORBIDDEN never appears on the wire.

Rate limits

There is also a per-IP limit of half your per-minute allowance, floored at 30.
Under load, write endpoints fail closed — a charge is rejected rather than risking a double-spend. Reads fail open. Treat a rejected write as “did not happen” and retry.
Send an Idempotency-Key header to de-duplicate retries safely.

Endpoints

Limits

  • Confirmations expire after 10 minutes — treat unconfirmed requests as abandoned
  • amount must be a positive integer
  • metadata is capped at 10 keys
  • Per-transaction and daily caps are set by Vetox staff and shown read-only in the Settings tab

Security checklist

The API key and signing secret never belong in client code. Rotate immediately if either leaks.
Check the signature against the raw body and reject deliveries older than ~5 minutes.
Never fulfil on the /deduct response — the charge is not final until confirmation.completed.
Request only the scopes you actually use, and enable the IP allowlist.

Troubleshooting

The owner’s Membership has lapsed. It is re-checked on every call.
It cannot be recovered — only a hash is stored. Rotate to get a new one.
Accept both secrets during the 24-hour overlap.
The user did not approve it. Confirmations expire after 10 minutes.
A project needs both a callback URL and a signing secret. With only one, nothing is delivered.
/v1/add is funded from your own balance, not created from nothing. Top up.

Vito

Balances, the PIN and fees.

Payment Requests

What the user sees when you charge them.

@vetox-bot/vito on npm

The official Node.js package — one install, full integration.