Rozliczenia (billing) — API v1 i MCP | Dokumentacja Nodea
Moduł Rozliczenia (billing) jest w API wyłącznie do odczytu. Eksponuje to, co web UI pokazuje klientowi: salda portfeli, księgę transakcji portfela oraz faktury wraz z pozycjami — i nic więcej. Dokument opisuje warstwę REST API v1 (/api/v1/billing/*) oraz narzędzia MCP z tego modułu.
Uwierzytelnianie i zakresy (scopes)
API v1 uwierzytelnia się osobistym tokenem Passport przez nagłówek Authorization: Bearer <token>. Wszystkie endpointy billing są chronione zakresem account:read (nie istnieje ścieżka zapisu). Cały moduł jest dodatkowo bramkowany flagą Pennant features:hosting-suite (odpowiada web'owym grupom /app/account/*). Limit zapytań: throttle:api-v1 = 240/min na użytkownika tokenu.
Read-only — co NIE jest eksponowane
Świadomie web-only (interaktywne i/lub powiązane ze Stripe) i nieobecne w API:
- Doładowania (topup, Stripe Checkout),
- zarządzanie metodami płatności (SetupIntents + Stripe Elements),
- konfiguracja auto-doładowania (mutacja — w API pola auto-recharge są tylko do odczytu),
- pobieranie PDF faktury (renderowanie binarne, trasa web-only).
Odpowiedzi nie zawierają żadnych identyfikatorów Stripe (customer / payment method / intent) ani wewnętrznych: whmcs_id, wfirma_id, pdf_path. Moduł nigdy nie rozwiązuje klienta Stripe.
Własność i widoczność (ownership)
Ani Wallet, ani Invoice nie mają klasy polityki — dostęp na poziomie wiersza jest owner-scoped inline. Cudzy lub nieznany identyfikator zwraca konsekwentnie 404 (nie 403), by API nie potwierdzało istnienia wiersza innego najemcy.
API v1 — endpointy
| Metoda | Ścieżka | Scope | Opis |
|---|---|---|---|
| GET | /api/v1/billing/wallets | account:read | Portfele użytkownika (jeden wiersz na walutę). Ściśle read-only: NIE tworzy leniwie pary PLN/EUR, więc bez historii lista może być pusta. |
| GET | /api/v1/billing/transactions | account:read | Księga transakcji portfeli, najnowsze pierwsze. Filtry wallet_id i/lub currency. Paginacja simple. |
| GET | /api/v1/billing/invoices | account:read | Faktury użytkownika (issued_at DESC, id DESC). Filtr status. Paginacja simple. |
| GET | /api/v1/billing/invoices/{invoice} | account:read | Jedna faktura z pozycjami (items[]). Ownership inline (cudza → 404). |
GET /billing/wallets — odpowiedź
Kolekcja WalletResource — pola: id, currency (PLN|EUR), balance_cents, balance_formatted (string, np. „123,45 PLN”), auto_recharge_enabled, auto_recharge_threshold_cents, auto_recharge_amount_cents, is_below_threshold, created_at, updated_at. Pola auto-recharge są tylko do odczytu (zmiana jest web-only).
GET /billing/transactions — parametry i semantyka filtrów
wallet_id(int, opcjonalny) — nazywa konkretny zasób, więc cudzy/nieznany id to404.currency(enumPLN|EUR, opcjonalny) — zwykły filtr: wartość spoza enum to422, poprawna waluta bez portfela zwraca pustą stronę.per_page(int 1–100, domyślnie 20),page(int ≥1).
Oba filtry razem się przecinają (np. wallet_id portfela PLN z currency=EUR zwraca pustą stronę, nie błąd). Odpowiedź: kolekcja WalletTransactionResource — pola: id, wallet_id, type, amount_cents (kwota ze znakiem), balance_after_cents, currency (gdy relacja portfela doładowana), note, created_at. Wewnętrzne pola (klucz idempotencji, referencja polimorficzna, autor-admin, legacy WHMCS id) pozostają po stronie serwera.
GET /billing/invoices — parametry i pola
status(enum, opcjonalny) — wartość spoza enum to422.per_page(int 1–100, domyślnie 20),page(int ≥1).
Kolekcja InvoiceResource — pola: id, number, status, currency, subtotal_cents (netto), vat_rate, vat_cents, total_cents (brutto), issued_at, due_at (data), paid_at, period_start, period_end. Na liście klucz items jest nieobecny; w szczegółach faktury items[] to InvoiceItemResource: id, description, quantity (decimal, serializowany jako string, np. „2.5000”), unit_price_cents, subtotal_cents, vat_cents, total_cents. Pominięte: whmcs_id, wfirma_id, pdf_path oraz jakiekolwiek URL-e PDF.
Kody błędów
401— brak/niepoprawny token.403— brak scopeaccount:readlub nieaktywna flagahosting-suite.404— cudzy/nieznanywallet_idlub faktura.422— walidacja filtrów (np.currency/statusspoza enum).
Przykłady — API
Portfele
curl -s https://app.nodea.io/api/v1/billing/wallets \
-H "Authorization: Bearer $NODEA_TOKEN" \
-H "Accept: application/json"
Transakcje z filtrem waluty
curl -s "https://app.nodea.io/api/v1/billing/transactions?currency=PLN&per_page=25" \
-H "Authorization: Bearer $NODEA_TOKEN" \
-H "Accept: application/json"
Faktury opłacone
curl -s "https://app.nodea.io/api/v1/billing/invoices?status=paid" \
-H "Authorization: Bearer $NODEA_TOKEN" \
-H "Accept: application/json"
Jedna faktura z pozycjami
curl -s https://app.nodea.io/api/v1/billing/invoices/123 \
-H "Authorization: Bearer $NODEA_TOKEN" \
-H "Accept: application/json"
MCP — narzędzia
Narzędzia MCP dzielą model uwierzytelniania i zakresów z API v1 (Bearer + scope sprawdzany w ensureScope) oraz flagę features:hosting-suite — narzędzie na nieaktywnej fladze znika z tools/list. Wszystkie są read-only (adnotacje IsReadOnly/IsIdempotent) i nigdy nie dotykają Stripe. Identyfikatory faktur to liczby całkowite; brakujący i cudzy id zwracają ten sam błąd (AuthorizationException: „Invoice not found or not accessible with your permissions.”) — brak enumeracji między najemcami.
| Narzędzie | Scope | Tryb | Opis |
|---|---|---|---|
| billing-wallets | account:read | read-only | Salda portfeli (jeden wiersz na walutę): balance_cents, balance_formatted, konfiguracja auto-recharge. Bez parametrów. Świeże konto może zwrócić pustą listę. |
| billing-transactions | account:read | read-only | Księga transakcji, najnowsze pierwsze. Filtry type i/lub currency, page, per_page (max 50, domyślnie 15). |
| billing-invoices | account:read | read-only | Faktury, najnowsze pierwsze (issued_at DESC, id DESC). Filtr status, page, per_page (max 50, domyślnie 15). |
| billing-invoice-get | account:read | read-only | Jedna faktura z pozycjami po id. Owner-scoped. |
Filtr type w billing-transactions przyjmuje wartości WalletTransactionType (m.in. topup_stripe, topup_admin, refund, debit_meter, debit_invoice, adjustment). Filtr status w fakturach przyjmuje wartości InvoiceStatus (m.in. draft, issued, paid, void, migrated_historical). Uwaga: shape'y MCP są nieco bogatsze niż API — billing-invoice-get zwraca w pozycjach dodatkowo vat_rate, period_start, period_end (których nie ma w InvoiceItemResource), a walletArray pomija id portfela oraz is_below_threshold. Wewnętrzne pola (referencja polimorficzna, klucz idempotencji, atrybucja admina, wfirma_id/whmcs_id, pdf_path) nigdy nie są zwracane.
Przykład — MCP (JSON-RPC)
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "billing-transactions",
"arguments": { "currency": "PLN", "type": "debit_meter", "per_page": 25 }
}
}
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "billing-invoice-get",
"arguments": { "id": 123 }
}
}