> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vetox.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Vito API i zakupy

> Pozwól zatwierdzonym aplikacjom obciążać Twoje Vito z potwierdzeniem PIN-em i zarządzaj żądaniami płatności ze strony Purchases.

<Info>
  Wymaga członkostwa **Silver** lub wyższego — zarówno do złożenia wniosku, jak i przy każdym uwierzytelnionym wywołaniu. Jeśli członkostwo właściciela klucza wygaśnie, projekt zostaje zamrożony do czasu odnowienia.
</Info>

REST API pozwalające Twojej aplikacji operować na saldzie [Vito](/pl/members/vito) użytkownika prosto z Discorda — odczytywać je, obciążać, uznawać albo przenosić między użytkownikami. Wszystkie endpointy zwracają JSON i są wersjonowane pod `/v1`.

<Warning>
  **Vito nigdy nie zamienia się w prawdziwe pieniądze ani z nich nie pochodzi.** Krąży wyłącznie między saldami Vetox.
</Warning>

## Uzyskanie dostępu

Dostęp przyznawany jest **osobno dla każdego projektu**. Potrzebne są wszystkie cztery warunki:

<Steps>
  <Step title="Aktywne członkostwo, Silver lub wyższe">
    Sprawdzane przy każdym wywołaniu, nie tylko przy zatwierdzaniu.
  </Step>

  <Step title="Zatwierdzony wniosek deweloperski">
    Składany ze strony Vito API w panelu. Rozpatrywany ręcznie przez zespół Vetox.
  </Step>

  <Step title="Zaakceptowane warunki dla deweloperów API">
    Potwierdzane w momencie składania wniosku.
  </Step>

  <Step title="Scope'y, których potrzebuje Twój projekt">
    Przyznawane przez zespół Vetox na podstawie Twojego opisu.
  </Step>
</Steps>

<Tip>
  Pisz konkretnie, co budujesz i jak będziesz przechowywać klucz. Odrzucane są właśnie wnioski ogólnikowe.
</Tip>

### Scope'y

| Scope               | Pozwala                                                        |
| ------------------- | -------------------------------------------------------------- |
| `balance:read`      | Odczytać saldo Vito użytkownika                                |
| `deduct:create`     | Obciążyć saldo użytkownika                                     |
| `credit:create`     | Dodać Vito użytkownikowi, finansowane z Twojego własnego salda |
| `transfer:create`   | Przenieść Vito między dwoma użytkownikami                      |
| `transactions:read` | Wyświetlać i odczytywać transakcje Twojego projektu            |

## Uwierzytelnianie

Wyślij swój tajny klucz jako token Bearer:

```bash theme={null}
Authorization: Bearer vito_live_xxxxxxxxxxxxxxxx
```

Dwie opcjonalne warstwy dodatkowo wzmacniają projekt:

* **Lista dozwolonych IP** — ogranicza wywołania do konkretnych adresów IP serwerów
* **Limity zapytań** — pułapy na projekt, rosnące wraz z poziomem członkostwa właściciela

### Klucze, rotacja i przechowywanie

<Warning>
  **Klucz i sekret podpisu pokazywane są dokładnie raz.** Po zatwierdzeniu masz **okno 7 dni**, aby odsłonić je w zakładce kluczy API. Vetox przechowuje tylko skrót i nie może pokazać ich ponownie — jeśli przegapisz okno, trzeba wykonać rotację.
</Warning>

* Trzymaj go **wyłącznie po stronie serwera** — kto go ma, może obciążać Twoich użytkowników
* Rotuj w zakładce kluczy API. Poprzedni klucz działa jeszcze przez **24 godziny** jako okres przejściowy, żebyś mógł wdrożyć zmianę bez przestoju
* Sekret podpisu webhooków rotuje się osobno, z własnym 24-godzinnym nakładaniem
* W razie wycieku rotuj natychmiast

## Obciążenie użytkownika

<Warning>
  **Sam klucz nie przeniesie Vito użytkownika.** Każde obciążenie wymaga zatwierdzenia przez użytkownika PIN-em jego portfela, na `vetox.io` — nigdy w Twojej aplikacji ani w Discordzie.
</Warning>

<Steps>
  <Step title="Twoja aplikacja wywołuje POST /v1/deduct">
    Z użytkownikiem, kwotą, źródłowym `guildId` i szczegółami przedmiotu.
  </Step>

  <Step title="Vito zwraca confirmUrl">
    Oczekujące potwierdzenie, ważne **10 minut**. Użytkownik dostaje też wiadomość prywatną.
  </Step>

  <Step title="Użytkownik zatwierdza swoim PIN-em">
    Na `vetox.io`.
  </Step>

  <Step title="Vito rozlicza i powiadamia">
    Saldo zostaje obciążone, transakcja zapisana, a podpisany webhook wysłany, jeśli go skonfigurowałeś.
  </Step>

  <Step title="Twoja aplikacja weryfikuje i finalizuje">
    Sprawdź podpis, a potem odblokuj treść lub wydaj przedmiot.
  </Step>
</Steps>

<Warning>
  **Finalizuj swoje działanie wyłącznie przy `confirmation.completed`** — nigdy na podstawie odpowiedzi `/deduct`. W tym momencie obciążenie nie jest jeszcze ostateczne.
</Warning>

### Parametry żądania — `/v1/deduct`

| Pole          | Wymagane | Uwagi                                                                                                  |
| ------------- | -------- | ------------------------------------------------------------------------------------------------------ |
| `discordId`   | **Tak**  | Discord ID użytkownika — snowflake o długości 17–20 cyfr                                               |
| `amount`      | **Tak**  | Dodatnia liczba całkowita                                                                              |
| `guildId`     | **Tak**  | Serwer Discord, z którego pochodzi obciążenie. Każde obciążenie musi pochodzić z serwera               |
| `reason`      | Nie      | Do 256 znaków. Pokazywane użytkownikowi i zwracane w webhooku                                          |
| `merchantRef` | Nie      | Twoje własne odniesienie, do 128 znaków. Służy do dopasowania webhooka do Twoich rekordów              |
| `product`     | Nie      | `{ type, name, description?, imageUrl? }` — widoczne na stronie potwierdzenia i w wiadomości prywatnej |
| `imageUrl`    | Nie      | Musi zaczynać się od `https://`                                                                        |
| `metadata`    | Nie      | Do **10** tekstowych par klucz/wartość, przekazywanych bez zmian                                       |

## Uznanie użytkownika

`POST /v1/add` dodaje użytkownikowi Vito **z Twojego własnego salda** — na nagrody lub zwroty. Te same pola co `/deduct`, poza `guildId` i `product`.

<Note>
  W odróżnieniu od obciążenia, uznanie **nie ma kroku potwierdzenia** — rozliczane jest od razu. Wymaga scope'a `credit:create` i wystarczającego salda, w przeciwnym razie wywołanie zwróci **402 `VITO_INSUFFICIENT_OWNER_FUNDS`**.
</Note>

## Opłaty

Każde obciążenie trafia do Ciebie pomniejszone o opłatę platformy — według tego samego cennika co przelewy Vito w aplikacji, zależnie od **Twojego** poziomu członkostwa:

| Twoje członkostwo    | Opłata |
| -------------------- | ------ |
| Normal, Silver, Gold | 7%     |
| Platinum             | 6%     |
| Diamond              | 5%     |

<Note>
  Kwoty **5 Vito lub mniejsze są bez opłat**, a uznania przez `/v1/add` są bez opłat zawsze.
</Note>

## Webhooki

Dodaj jeden lub więcej adresów zwrotnych `https` w zakładce ustawień. Vito wysyła podpisany `POST` za każdym razem, gdy potwierdzenie osiąga stan końcowy.

<Warning>
  Webhooki wychodzą tylko wtedy, gdy projekt ma **jednocześnie** adres zwrotny **i** sekret podpisu. Odsłoń sekret (`whsec_…`) jednorazowo w zakładce kluczy API.
</Warning>

### Weryfikacja podpisu

Każda dostawa niesie nagłówek `X-Vito-Signature`:

```text theme={null}
X-Vito-Signature: ts=<unix>;h1=<hex>
```

Każdej dostawie towarzyszą jeszcze dwa nagłówki — użyj `X-Vito-Event-Id` jako klucza deduplikacji, bo ponowna próba wysyła to samo id:

| Nagłówek            | Zawiera                                                           |
| ------------------- | ----------------------------------------------------------------- |
| `X-Vito-Event-Id`   | Stałe id tego zdarzenia — identyczne przy wszystkich ponowieniach |
| `X-Vito-Event-Type` | Na przykład `confirmation.completed`                              |

<Warning>
  **Podczas rotacji sekretu podpisu nagłówek niesie więcej niż jeden podpis**, najnowszy jako pierwszy:

  ```text theme={null}
  X-Vito-Signature: ts=<unix>;h1=<nowy>;h1=<stary>
  ```

  Przyjmij dostawę, jeśli zgadza się **którykolwiek** `h1`. Weryfikator czytający tylko pierwszy odrzuci każdy webhook, dopóki nie wdroży nowego sekretu — a to niweczy cały sens 24-godzinnego nakładania.
</Warning>

Przelicz HMAC z `<ts>:<rawBody>` swoim sekretem podpisu i porównaj w stałym czasie.

```js theme={null}
const crypto = require('crypto');

function verify(rawBody, header, secret) {
  const parts = header.split(';');
  const ts = parts.find(p => p.startsWith('ts='))?.slice(3);
  const sigs = parts.filter(p => p.startsWith('h1=')).map(p => p.slice(3));
  if (!ts || sigs.length === 0) return false;

  // Reject anything older than ~5 minutes — replay protection.
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;

  const expected = Buffer.from(
    crypto.createHmac('sha256', secret).update(`${ts}:${rawBody}`).digest('hex'),
  );

  // Any matching signature is valid — a rotation emits several.
  return sigs.some(sig => {
    const actual = Buffer.from(sig);
    // timingSafeEqual throws when the lengths differ, so check first.
    return (
      actual.length === expected.length && crypto.timingSafeEqual(actual, expected)
    );
  });
}
```

<Warning>
  Weryfikuj wobec **surowego ciała żądania**, zanim parsowanie JSON albo middleware je przepisze.
</Warning>

### Zdarzenia

Pięć typów zdarzeń, wszystkie o tej samej strukturze ładunku. Pole `data.status` niesie wynik.

| Zdarzenie                | Znaczenie                                                                                        |
| ------------------------ | ------------------------------------------------------------------------------------------------ |
| `confirmation.completed` | Zatwierdzone i obciążone. Zawiera `transactionId`. **Tutaj finalizuj swoje działanie**           |
| `confirmation.failed`    | Nie udało się dokończyć — za małe saldo lub błąd wewnętrzny. Zawiera `failureReason`             |
| `confirmation.expired`   | Nie potwierdzono w ciągu 10 minut. Żadne Vito się nie przemieściło                               |
| `confirmation.cancelled` | Użytkownik anulował. Żadne Vito się nie przemieściło                                             |
| `credit.completed`       | Rozliczono `/add` finansowane przez właściciela. Wyzwalane natychmiast — bez kroku potwierdzenia |

<Note>
  Webhooki są ponawiane **5 razy z narastającym odstępem**. Odpowiadaj szybko kodem 2xx, a realizację wykonuj asynchronicznie.
</Note>

## Kody błędów

Każda odpowiedź jest opakowana. Sukces niesie `data`, błąd niesie `error` — nigdy oba naraz:

```json theme={null}
{
  "success": false,
  "error": {
    "type": "Forbidden",
    "message": "API key lacks the required scope for this operation.",
    "code": "VITO_INSUFFICIENT_SCOPE"
  },
  "requestId": "…",
  "timestamp": 1735161600000
}
```

<Warning>
  **Każdy kod ma przedrostek `VITO_`.** Porównuj cały ciąg — samo `RATE_LIMITED` czy `FORBIDDEN` nigdy nie pojawia się w odpowiedzi.
</Warning>

| Kod                                   | Status | Znaczenie                                                    |
| ------------------------------------- | ------ | ------------------------------------------------------------ |
| `VITO_VALIDATION_ERROR`               | 400    | Brakuje pola albo ma zły format                              |
| `VITO_INVALID_AMOUNT`                 | 400    | `amount` nie jest dodatnią liczbą całkowitą                  |
| `VITO_AMOUNT_LIMIT_EXCEEDED`          | 400    | Powyżej Twojego limitu na transakcję                         |
| `VITO_DAILY_VOLUME_EXCEEDED`          | 400    | To wywołanie przekroczyłoby Twój dzienny limit obrotu        |
| `VITO_SELF_TRANSFER`                  | 400    | Nadawca i odbiorca to ten sam użytkownik                     |
| `VITO_INVALID_API_KEY`                | 401    | Klucz brakujący, zniekształcony, unieważniony lub nieznany   |
| `VITO_INSUFFICIENT_FUNDS`             | 402    | Użytkownik nie ma środków na obciążenie                      |
| `VITO_INSUFFICIENT_OWNER_FUNDS`       | 402    | **Twoje** saldo jest za niskie, by sfinansować `/add`        |
| `VITO_INSUFFICIENT_SCOPE`             | 403    | Klucz nie ma scope'a wymaganego przez endpoint               |
| `VITO_IP_NOT_ALLOWED`                 | 403    | Wywołujący adres IP nie jest na liście dozwolonych           |
| `VITO_OWNER_MEMBERSHIP_INACTIVE`      | 403    | Członkostwo właściciela wygasło — zamrożone do odnowienia    |
| `VITO_GUILD_NOT_ALLOWED`              | 403    | `guildId` nie jest na liście dozwolonych serwerów projektu   |
| `VITO_TOS_NOT_ACCEPTED`               | 403    | Warunki dla deweloperów nieprzyjęte albo czeka nowsza wersja |
| `VITO_PROJECT_FROZEN`                 | 403    | Zamrożone — zwykle przez wygasłe członkostwo właściciela     |
| `VITO_PROJECT_SUSPENDED`              | 403    | Zawieszone przez zespół Vetox                                |
| `VITO_PROJECT_BANNED`                 | 403    | Zbanowane przez zespół Vetox                                 |
| `VITO_USER_BLACKLISTED`               | 403    | Użytkownik jest wykluczony z operacji na Vito                |
| `VITO_ACCOUNT_LOCKED`                 | 403    | Portfel użytkownika zablokowany po nieudanych próbach PIN    |
| `VITO_USER_NOT_FOUND`                 | 404    | Brak konta Vito dla tego Discord ID                          |
| `VITO_CONFIRMATION_NOT_FOUND`         | 404    | Nieznany token potwierdzenia                                 |
| `VITO_IDEMPOTENCY_CONFLICT`           | 409    | Ten sam `Idempotency-Key`, inne ciało żądania                |
| `VITO_IDEMPOTENCY_IN_PROGRESS`        | 409    | Identyczne żądanie wciąż trwa — ponów za chwilę              |
| `VITO_CONFIRMATION_ALREADY_PROCESSED` | 409    | To potwierdzenie osiągnęło już stan końcowy                  |
| `VITO_KEY_ROTATION_IN_PROGRESS`       | 409    | Rotacja już trwa                                             |
| `VITO_CONFIRMATION_EXPIRED`           | 410    | Dziesięciominutowe okno minęło                               |
| `VITO_RATE_LIMITED`                   | 429    | Zwolnij i spróbuj ponownie                                   |
| `VITO_INTERNAL_ERROR`                 | 500    | Nieoczekiwana awaria po naszej stronie                       |

## Limity zapytań

| Członkostwo właściciela | Na minutę | Na godzinę |
| ----------------------- | --------- | ---------- |
| Brak                    | 60        | 1 000      |
| Silver lub Gold         | 180       | 5 000      |
| Platinum lub Diamond    | 600       | 15 000     |

Obowiązuje też limit na adres IP równy połowie Twojego limitu minutowego, nie mniej niż 30.

<Warning>
  Pod obciążeniem **endpointy zapisu zawodzą „na zamknięte”** — obciążenie zostaje odrzucone, zamiast ryzykować podwójne wydanie. Endpointy odczytu zawodzą „na otwarte”. Traktuj odrzucony zapis jako „nie wydarzył się” i ponów go.
</Warning>

<Note>
  Wysyłaj nagłówek `Idempotency-Key`, aby bezpiecznie deduplikować ponowienia.
</Note>

## Endpointy

| Endpoint                        | Scope               |
| ------------------------------- | ------------------- |
| `GET /v1/auth/verify`           | dowolny             |
| `POST /v1/auth/rotate-key`      | dowolny             |
| `GET /v1/balance/:discordId`    | `balance:read`      |
| `POST /v1/deduct`               | `deduct:create`     |
| `POST /v1/add`                  | `credit:create`     |
| `POST /v1/transfer`             | `transfer:create`   |
| `GET /v1/transactions` i `/:id` | `transactions:read` |
| `GET /v1/webhooks/events`       | `transactions:read` |

## Ograniczenia

* Potwierdzenia wygasają po **10 minutach** — traktuj niepotwierdzone żądania jako porzucone
* `amount` musi być dodatnią liczbą całkowitą
* `metadata` jest ograniczona do 10 kluczy
* Limity na transakcję i dzienne ustala zespół Vetox; widnieją tylko do odczytu w zakładce ustawień

## Lista kontrolna bezpieczeństwa

<AccordionGroup>
  <Accordion title="Trzymaj sekrety po stronie serwera" icon="lock">
    Klucz API i sekret podpisu nigdy nie należą do kodu klienta. Jeśli którykolwiek wycieknie, rotuj natychmiast.
  </Accordion>

  <Accordion title="Weryfikuj każdy webhook" icon="signature">
    Sprawdzaj podpis wobec surowego ciała żądania i odrzucaj dostawy starsze niż \~5 minut.
  </Accordion>

  <Accordion title="Rozliczaj tylko przy completed" icon="circle-check">
    Nigdy nie wydawaj na podstawie odpowiedzi `/deduct` — obciążenie jest ostateczne dopiero przy `confirmation.completed`.
  </Accordion>

  <Accordion title="Minimalne uprawnienia" icon="key">
    Proś tylko o te scope'y, których faktycznie używasz, i włącz listę dozwolonych IP.
  </Accordion>
</AccordionGroup>

## Rozwiązywanie problemów

<AccordionGroup>
  <Accordion title="Każde wywołanie zwraca brak autoryzacji">
    Członkostwo właściciela wygasło. Jest sprawdzane przy każdym wywołaniu.
  </Accordion>

  <Accordion title="Zgubiłem swój klucz">
    Nie da się go odzyskać — przechowywany jest tylko skrót. Wykonaj rotację, aby dostać nowy.
  </Accordion>

  <Accordion title="Podpisy webhooków zawodzą po rotacji">
    Akceptuj oba sekrety przez 24-godzinne nakładanie.
  </Accordion>

  <Accordion title="Obciążenie nigdy się nie kończy">
    Użytkownik go nie zatwierdził. Potwierdzenia wygasają po 10 minutach.
  </Accordion>

  <Accordion title="Nie przychodzą żadne webhooki">
    Projekt potrzebuje adresu zwrotnego i sekretu podpisu. Przy tylko jednym z nich nic nie jest dostarczane.
  </Accordion>

  <Accordion title="Dotarło mniej Vito, niż obciążyłem">
    To opłata rozliczeniowa. Używaj kwot 5 Vito lub mniejszych, aby jej uniknąć, albo wliczaj ją w cenę.
  </Accordion>

  <Accordion title="402 VITO_INSUFFICIENT_OWNER_FUNDS">
    `/v1/add` finansowane jest z Twojego własnego salda, a nie tworzone z niczego. Doładuj je.
  </Accordion>
</AccordionGroup>

<CardGroup cols={2}>
  <Card title="Vito" icon="coins" href="/pl/members/vito">
    Salda, PIN i opłaty.
  </Card>

  <Card title="Prośby o płatność" icon="receipt" href="/pl/account/payment-requests">
    Co widzi użytkownik, gdy go obciążasz.
  </Card>
</CardGroup>
