Skip to content
Hoody.com

The Wallet & Payments API lets you read account balances, transfer funds into AI credits, browse transactions and invoices, manage saved payment methods, and start or inspect top-up payments through Stripe (card) and NOWPayments (crypto). It also exposes the one-time GitHub connection bonus and fee history.

All endpoints below are authenticated against the control-plane API and use the hoody-sdk pattern client.api.wallet.*.

Read-only views over the user’s general USD balance, AI credit balance (limit/usage/remaining), or both at once.

Returns the general balance together with AI limit, usage, remaining, and a freshness flag (ai_usage_status).

This endpoint takes no parameters.

{
"statusCode": 200,
"message": "Balances retrieved successfully",
"data": {
"general_balance": "125.50",
"ai_limit": "50.00",
"ai_usage": "23.45",
"ai_remaining": "26.55",
"ai_usage_status": "live"
}
}
const balances = await client.api.wallet.getAggregateBalances();

Returns only the general USD balance plus row bookkeeping fields.

This endpoint takes no parameters.

{
"statusCode": 200,
"message": "General balance retrieved successfully",
"data": {
"id": "507f1f77bcf86cd799439100",
"user_id": "507f1f77bcf86cd799439011",
"general_balance": "125.50",
"created_at": "2025-01-10T08:30:00.000Z",
"updated_at": "2025-01-21T20:00:00.000Z"
}
}
const balance = await client.api.wallet.getGeneralBalance();

Returns only the AI balance triplet (limit, usage, remaining).

This endpoint takes no parameters.

{
"statusCode": 200,
"message": "AI balance retrieved successfully",
"data": {
"ai_limit": "50.00",
"ai_usage": "23.45",
"ai_remaining": "26.55"
}
}
const ai = await client.api.wallet.getAiBalance();

One-way transfers from the general balance to AI credit, plus the historical record of platform fees charged on those transfers (and on admin credits).

Debits amount USD from the general balance and credits AI credits after the platform fee. The fee is returned separately as a basis-points-aware value; send expected_fee_bps so the server rejects the transfer (rather than silently charging a different fee) if the displayed fee has changed.

This endpoint takes no parameters.

NameTypeRequiredDescription
amountstringYesUSD amount as a string with up to 2 decimals, e.g. "10.00". No exponent, no negatives. Pattern: `^(0
idempotency_keystringNoOptional caller idempotency key (1–128 chars, must contain a non-whitespace character). Retrying with the same key and same amount returns the original receipt without moving funds; the same key with a different amount is rejected (409 TRANSFER_IDEMPOTENCY_KEY_REUSED).
expected_fee_bpsintegerNoOptional. The platform fee in basis points the client displayed at confirmation. Range 09999. If it no longer matches the current server fee the transfer is rejected (409 TRANSFER_FEE_CHANGED) so an irreversible transfer is never charged a fee the user was not shown.
{
"statusCode": 200,
"message": "Transfer completed and AI credit limit synced",
"data": {
"gross_transferred": "25.00",
"net_ai_credit": "23.75",
"fee": "1.25",
"general_balance": "100.50",
"ai_balance": "75.00",
"key_created": false,
"limit_sync_pending": false,
"replayed": false
}
}
const result = await client.api.wallet.transferToAi({
amount: "25.00",
idempotency_key: "transfer-2026-06-10-001",
expected_fee_bps: 500,
});

Paginated list of fees charged on AI credit transfers and on admin credits.

NameInTypeRequiredDescription
pagequerynumberNoPage number (default 1).
limitquerynumberNoPage size (default 20).
sort_byquerystringNoOne of created_at, amount, transaction_id (default created_at).
sort_orderquerystringNoasc or desc (default desc).
{
"statusCode": 200,
"message": "AI fee history retrieved successfully",
"data": {
"fees": [
{
"id": "507f1f77bcf86cd799439200",
"transaction_id": "507f1f77bcf86cd799439110",
"amount": "1.25",
"created_at": "2025-01-21T20:00:00.000Z",
"transaction": {
"id": "507f1f77bcf86cd799439110",
"reason": "transfer_to_ai",
"amount": "25.00"
}
}
],
"pagination": {
"total": 14,
"page": 1,
"limit": 20,
"totalPages": 1
}
}
}
// One page:
const page = await client.api.wallet.listAiFeeHistoryIterator({ page: 1, limit: 20 });
// Stream across all pages:
for await (const fee of client.api.wallet.listAiFeeHistoryIterator({ limit: 50 })) {
// ...
}

Inspect the user’s wallet transactions. Transactions are immutable ledger entries produced by transfers, top-ups, fees, and refunds.

Lists wallet transactions, newest first by default.

NameInTypeRequiredDescription
limitquerynumberNoMaximum number of transactions to return (default 20).
sort_byquerystringNoOne of id, transaction_type, status, amount, created_at, updated_at (default created_at).
sort_orderquerystringNoasc or desc (default desc).
{
"statusCode": 200,
"message": "Transactions retrieved successfully"
}
// One page:
const txs = await client.api.wallet.listTransactionsIterator({ limit: 20 });
// Stream:
for await (const tx of client.api.wallet.listTransactionsIterator({ sort_order: "desc" })) {
// ...
}

Returns a single wallet transaction by ID.

NameInTypeRequiredDescription
idpathstringYesTransaction ID.
{
"statusCode": 200,
"message": "Transaction retrieved successfully"
}
const tx = await client.api.wallet.getTransaction("507f1f77bcf86cd799439110");

Browse, fetch, and generate invoices for transactions that warrant a receipt (typically completed payments). PDFs are produced on demand.

Lists all invoices belonging to the authenticated user, newest first.

NameInTypeRequiredDescription
pagequeryintegerNoPage number for pagination — starts from 1 (default 1).
limitqueryintegerNoNumber of invoices to return per page — maximum 100 (default 20).
sort_byquerystringNoField to sort by. One of id, invoice_number, status, amount, currency, issue_date, due_date, paid_date, created_at, updated_at, user_id, transaction_id. Unrecognised values fall back to created_at (default created_at).
sort_orderquerystringNoasc or desc (default desc).
filterquerystringNoJSON object string filtering by the sortable fields, e.g. {"status":"paid"} or {"amount":{"gte":10}}. Operators: eq, ne, gt, gte, lt, lte, like, in. Unknown fields or operators are rejected with 400.
{
"statusCode": 200,
"message": "Invoices retrieved successfully",
"data": {
"invoices": [
{
"id": "507f1f77bcf86cd799439120",
"user_id": "507f1f77bcf86cd799439011",
"transaction_id": "507f1f77bcf86cd799439110",
"invoice_number": "INV-1785439290709-4e2a8002866b32d1f473ffe0",
"status": "paid",
"amount": 50,
"currency": "USD",
"issue_date": "2025-01-20T15:30:00.000Z",
"due_date": "2025-02-20T15:30:00.000Z",
"paid_date": "2025-01-20T15:30:00.000Z",
"created_at": "2025-01-20T15:30:00.000Z",
"updated_at": "2025-01-20T15:30:00.000Z",
"transaction": {
"id": "507f1f77bcf86cd799439110",
"transaction_type": "payment",
"status": "completed",
"amount": 50,
"currency": "USD",
"created_at": "2025-01-20T15:30:00.000Z"
}
}
],
"pagination": {
"total": 23,
"page": 1,
"limit": 10,
"totalPages": 3
}
}
}
// One page, e.g. only paid invoices with amount ≥ 10:
const result = await client.api.wallet.listInvoicesIterator({
limit: 10,
sort_by: "created_at",
sort_order: "desc",
filter: JSON.stringify({ status: "paid", amount: { gte: 10 } }),
});
// Stream all pages:
for await (const inv of client.api.wallet.listInvoicesIterator({ limit: 50 })) {
// ...
}

Returns a single invoice (including billing_details, line items, and the underlying transaction).

NameInTypeRequiredDescription
idpathstringYesInvoice ID.
{
"statusCode": 200,
"message": "Invoice retrieved successfully",
"data": {
"id": "507f1f77bcf86cd799439120",
"user_id": "507f1f77bcf86cd799439011",
"transaction_id": "507f1f77bcf86cd799439110",
"invoice_number": "INV-1785439290709-4e2a8002866b32d1f473ffe0",
"status": "paid",
"amount": 50,
"currency": "USD",
"billing_details": {
"payment_method": "Card (Stripe Checkout)",
"payment_method_type": "card"
},
"items": [
{
"type": "general",
"description": "Credits",
"amount": 50
}
],
"issue_date": "2025-01-20T15:30:00.000Z",
"due_date": "2025-02-20T15:30:00.000Z",
"paid_date": "2025-01-20T15:30:00.000Z",
"created_at": "2025-01-20T15:30:00.000Z",
"updated_at": "2025-01-20T15:30:00.000Z",
"transaction": {
"id": "507f1f77bcf86cd799439110",
"transaction_type": "payment",
"status": "completed",
"amount": 50,
"currency": "USD",
"created_at": "2025-01-20T15:30:00.000Z"
}
}
}
const inv = await client.api.wallet.getInvoice("507f1f77bcf86cd799439120");

Downloads the invoice as a PDF file (application/pdf, binary).

NameInTypeRequiredDescription
idpathstringYesInvoice ID.

The response body is the raw PDF binary (Content-Type: application/pdf). Browser/SDK clients should stream it to disk or trigger a download.

%PDF-1.4
% binary PDF content...
const pdf = await client.api.wallet.downloadInvoicePdf("507f1f77bcf86cd799439120");

POST /api/v1/wallet/invoices/generate/{id}

Section titled “POST /api/v1/wallet/invoices/generate/{id}”

Generates an invoice for a specific transaction. Idempotent: if an invoice already exists for the transaction, the existing one is returned with statusCode 200 and a “Invoice already exists” message.

NameInTypeRequiredDescription
idpathstringYesTransaction ID.

This endpoint takes no body.

{
"statusCode": 200,
"message": "Invoice already exists",
"data": {
"invoice_id": "507f1f77bcf86cd799439120",
"invoice_number": "INV-1785439290709-4e2a8002866b32d1f473ffe0"
}
}
const invoice = await client.api.wallet.generateInvoice("507f1f77bcf86cd799439110");

Saved payment instruments on file for the authenticated user. Note: real cards added through Stripe Checkout or other tokenized providers are typically saved automatically by the provider integration — this resource primarily exposes saved metadata (e.g., labels, statuses, default flags) for the wallet UI.

Lists the user’s payment methods.

This endpoint takes no parameters.

{
"statusCode": 200,
"message": "Payment methods retrieved successfully",
"data": [
{
"id": "507f1f77bcf86cd799439130",
"user_id": "507f1f77bcf86cd799439011",
"type": "credit_card",
"name": "Visa ending in 4242",
"status": "active",
"details": {},
"is_default": true,
"created_at": "2025-01-15T10:00:00.000Z",
"updated_at": "2025-01-15T10:00:00.000Z"
}
]
}
for await (const pm of client.api.wallet.listPaymentMethodsIterator()) {
// ...
}

Returns a single payment method by ID.

NameInTypeRequiredDescription
idpathstringYesPayment method ID.
{
"statusCode": 200,
"message": "Payment method retrieved successfully",
"data": {
"id": "507f1f77bcf86cd799439130",
"user_id": "507f1f77bcf86cd799439011",
"type": "credit_card",
"name": "Visa ending in 4242",
"status": "active",
"details": {},
"is_default": true,
"created_at": "2025-01-15T10:00:00.000Z",
"updated_at": "2025-01-15T10:00:00.000Z"
}
}
const pm = await client.api.wallet.getPaymentMethod("507f1f77bcf86cd799439130");

Adds a new payment method for the user.

This endpoint takes no parameters.

NameTypeRequiredDescription
typestringYesProvider-specific type identifier (e.g. credit_card).
namestringYesDisplay name for the method.
detailsobjectNoFree-form provider metadata (e.g. last4, brand, exp_month, exp_year).
is_defaultbooleanNoWhether this method should be the user’s default. Defaults to false.
{
"statusCode": 201,
"message": "Payment method added successfully",
"data": {
"id": "507f1f77bcf86cd799439131",
"user_id": "507f1f77bcf86cd799439011",
"type": "credit_card",
"name": "Mastercard ending in 5555",
"status": "active",
"details": {},
"is_default": false,
"created_at": "2025-01-21T20:30:00.000Z",
"updated_at": "2025-01-21T20:30:00.000Z"
}
}
const pm = await client.api.wallet.addPaymentMethod({
type: "credit_card",
name: "Mastercard ending in 5555",
is_default: false,
});

Updates one or more fields of an existing payment method.

NameInTypeRequiredDescription
idpathstringYesPayment method ID.
NameTypeRequiredDescription
detailsobjectNoReplaces the free-form provider metadata.
statusstringNoOne of active, inactive.
is_defaultbooleanNoWhether this method should be the user’s default.
{
"statusCode": 200,
"message": "Payment method updated successfully",
"data": {
"id": "507f1f77bcf86cd799439131",
"user_id": "507f1f77bcf86cd799439011",
"type": "credit_card",
"name": "Mastercard (primary)",
"status": "active",
"details": {},
"is_default": true,
"created_at": "2025-01-21T20:30:00.000Z",
"updated_at": "2025-01-21T20:45:00.000Z"
}
}
const pm = await client.api.wallet.updatePaymentMethod("507f1f77bcf86cd799439131", {
is_default: true,
status: "active",
});

PUT /api/v1/wallet/payment-methods/{id}/default

Section titled “PUT /api/v1/wallet/payment-methods/{id}/default”

Marks an existing payment method as the user’s default. Equivalent to PUT /{id} with is_default: true, exposed as a dedicated convenience endpoint.

NameInTypeRequiredDescription
idpathstringYesPayment method ID.
{
"statusCode": 200,
"message": "Default payment method set successfully",
"data": {
"id": "507f1f77bcf86cd799439131",
"user_id": "507f1f77bcf86cd799439011",
"type": "credit_card",
"name": "Mastercard (primary)",
"status": "active",
"details": {},
"is_default": true,
"created_at": "2025-01-21T20:30:00.000Z",
"updated_at": "2025-01-21T21:00:00.000Z"
}
}
const pm = await client.api.wallet.setDefaultPaymentMethod("507f1f77bcf86cd799439131");

DELETE /api/v1/wallet/payment-methods/{id}

Section titled “DELETE /api/v1/wallet/payment-methods/{id}”

Removes a payment method from the user’s saved methods.

NameInTypeRequiredDescription
idpathstringYesPayment method ID.
{
"statusCode": 200,
"message": "Payment method deleted successfully"
}
await client.api.wallet.deletePaymentMethod("507f1f77bcf86cd799439131");

Returns which top-up providers are usable right now (enabled and fully configured), the per-provider min/max USD per top-up, and the current AI credit transfer fee in basis points. The wallet UI typically calls this before rendering a top-up affordance. No secrets are exposed.

This endpoint takes no parameters.

{
"statusCode": 200,
"message": "Payment availability retrieved successfully",
"data": {
"stripe": {
"enabled": true,
"min_usd": 5,
"max_usd": 10000
},
"nowpayments": {
"enabled": false,
"min_usd": 5,
"max_usd": 10000
},
"ai_credit_fee_bps": 0
}
}
const availability = await client.api.wallet.getPaymentAvailability();

Create a hosted crypto invoice and poll its status. The wallet is credited only after the provider confirms settlement via IPN, so status will move from pending to completed asynchronously.

POST /api/v1/wallet/payments/crypto/invoice

Section titled “POST /api/v1/wallet/payments/crypto/invoice”

Creates a payment intent and a hosted crypto payment invoice. Redirect the user to invoice_url; the wallet is credited after the payment provider confirms settlement.

This endpoint takes no parameters.

NameTypeRequiredDescription
amountstringYesUSD amount as a strict decimal string (e.g. "25" or "25.00"). Pattern: `^(0
idempotency_keystringNoOptional caller idempotency key (1–128 chars, must contain a non-whitespace character). Repeats return the original intent.
{
"statusCode": 201,
"message": "Crypto payment invoice created",
"data": {
"intent": {
"id": "665f1f77bcf86cd799439012",
"provider": "nowpayments",
"status": "pending",
"amount": 25,
"currency": "USD",
"redirect_url": "https://nowpayments.io/payment/?iid=4522625843",
"credited_at": null,
"expires_at": null,
"created_at": "2026-06-10T12:00:00.000Z",
"updated_at": "2026-06-10T12:00:00.000Z"
},
"invoice_url": "https://nowpayments.io/payment/?iid=4522625843"
}
}
const { intent, invoice_url } = await client.api.wallet.createCryptoInvoice({
amount: "25.00",
idempotency_key: "topup-2026-06-10-002",
});
// Redirect the user to invoice_url...

GET /api/v1/wallet/payments/crypto/intents

Section titled “GET /api/v1/wallet/payments/crypto/intents”

Lists the authenticated user’s crypto payment intents, newest first.

NameInTypeRequiredDescription
limitqueryintegerNoMaximum number of intents to return (default 20).
offsetqueryintegerNoNumber of intents to skip (default 0).
{
"statusCode": 200,
"message": "Payment intents retrieved successfully",
"data": {
"intents": [
{
"id": "665f1f77bcf86cd799439012",
"provider": "nowpayments",
"status": "pending",
"amount": 25,
"currency": "USD",
"redirect_url": "https://nowpayments.io/payment/?iid=4522625843",
"credited_at": null,
"expires_at": null,
"created_at": "2026-06-10T12:00:00.000Z",
"updated_at": "2026-06-10T12:00:00.000Z"
}
],
"total": 1
}
}
const intents = await client.api.wallet.listCryptoPaymentIntents({ limit: 20 });

GET /api/v1/wallet/payments/crypto/intents/{id}

Section titled “GET /api/v1/wallet/payments/crypto/intents/{id}”

Returns one of the authenticated user’s crypto payment intents. Poll this endpoint after the user returns from the hosted invoice to learn whether the payment settled.

NameInTypeRequiredDescription
idpathstringYesCrypto payment intent ID.
{
"statusCode": 200,
"message": "Payment intent retrieved successfully",
"data": {
"id": "665f1f77bcf86cd799439012",
"provider": "nowpayments",
"status": "pending",
"amount": 25,
"currency": "USD",
"redirect_url": "https://nowpayments.io/payment/?iid=4522625843",
"credited_at": null,
"expires_at": null,
"created_at": "2026-06-10T12:00:00.000Z",
"updated_at": "2026-06-10T12:00:00.000Z"
}
}
const intent = await client.api.wallet.getCryptoPaymentIntent("665f1f77bcf86cd799439012");

Create a hosted Stripe Checkout session and poll its status. The wallet is credited only after Stripe confirms settlement via webhook, so status moves from pending to completed asynchronously.

POST /api/v1/wallet/payments/stripe/checkout

Section titled “POST /api/v1/wallet/payments/stripe/checkout”

Creates a payment intent and a hosted Stripe Checkout session. Redirect the user to checkout_url; the wallet is credited after Stripe confirms settlement.

This endpoint takes no parameters.

NameTypeRequiredDescription
amountstringYesUSD amount as a strict decimal string (e.g. "25" or "25.00"). Pattern: `^(0
idempotency_keystringNoOptional caller idempotency key (1–128 chars, must contain a non-whitespace character). Repeats return the original intent.
{
"statusCode": 201,
"message": "Checkout session created",
"data": {
"intent": {
"id": "665f1f77bcf86cd799439011",
"provider": "stripe",
"status": "pending",
"amount": 25,
"currency": "USD",
"redirect_url": "https://checkout.stripe.com/c/pay/cs_test_a1b2c3",
"credited_at": null,
"expires_at": "2026-06-11T12:00:00.000Z",
"created_at": "2026-06-10T12:00:00.000Z",
"updated_at": "2026-06-10T12:00:00.000Z"
},
"checkout_url": "https://checkout.stripe.com/c/pay/cs_test_a1b2c3"
}
}
const { intent, checkout_url } = await client.api.wallet.createStripeCheckout({
amount: "25.00",
idempotency_key: "topup-2026-06-10-001",
});
// Redirect the user to checkout_url...

GET /api/v1/wallet/payments/stripe/intents

Section titled “GET /api/v1/wallet/payments/stripe/intents”

Lists the authenticated user’s Stripe payment intents, newest first.

NameInTypeRequiredDescription
limitqueryintegerNoMaximum number of intents to return (default 20).
offsetqueryintegerNoNumber of intents to skip (default 0).
{
"statusCode": 200,
"message": "Payment intents retrieved successfully",
"data": {
"intents": [
{
"id": "665f1f77bcf86cd799439011",
"provider": "stripe",
"status": "pending",
"amount": 25,
"currency": "USD",
"redirect_url": "https://checkout.stripe.com/c/pay/cs_test_a1b2c3",
"credited_at": null,
"expires_at": "2026-06-11T12:00:00.000Z",
"created_at": "2026-06-10T12:00:00.000Z",
"updated_at": "2026-06-10T12:00:00.000Z"
}
],
"total": 1
}
}
const intents = await client.api.wallet.listStripePaymentIntents({ limit: 20 });

GET /api/v1/wallet/payments/stripe/intents/{id}

Section titled “GET /api/v1/wallet/payments/stripe/intents/{id}”

Returns one of the authenticated user’s Stripe payment intents. Poll this endpoint after the user returns from checkout to learn whether the payment settled.

NameInTypeRequiredDescription
idpathstringYesStripe payment intent ID.
{
"statusCode": 200,
"message": "Payment intent retrieved successfully",
"data": {
"id": "665f1f77bcf86cd799439011",
"provider": "stripe",
"status": "pending",
"amount": 25,
"currency": "USD",
"redirect_url": "https://checkout.stripe.com/c/pay/cs_test_a1b2c3",
"credited_at": null,
"expires_at": "2026-06-11T12:00:00.000Z",
"created_at": "2026-06-10T12:00:00.000Z",
"updated_at": "2026-06-10T12:00:00.000Z"
}
}
const intent = await client.api.wallet.getStripePaymentIntent("665f1f77bcf86cd799439011");

The one-time grant credited when an authenticated user has a linked GitHub identity. Read local state only — GET never contacts GitHub. When the offer is disabled, the response carries only { enabled: false } (plus the caller’s own historical claim, if any).

Returns the bonus status, including whether it’s enabled, the offered amount, the linked GitHub identity, whether the caller has already claimed it, and the historical receipt (if any).

This endpoint takes no parameters.

{
"statusCode": 200,
"message": "GitHub bonus status retrieved successfully",
"data": {
"enabled": true,
"repo": "HoodyNetwork/hoody-sdk",
"amount_usd": "5.00",
"github_linked": true,
"github_username": "octocat",
"claimed": false,
"identity_claimed_elsewhere": false,
"claim": null
}
}
const status = await client.api.wallet.getGithubBonus();

Grants the one-time GitHub connection bonus to the authenticated user when eligible. Idempotent: a second call returns result: "already_claimed" and credits nothing. The HTTP status is always 200 — the outcome is encoded in data.result.

This endpoint takes no parameters.

{
"statusCode": 200,
"message": "GitHub bonus granted",
"data": {
"result": "granted",
"amount_usd": "5.00",
"transaction_id": "507f1f77bcf86cd799439110",
"claim": {
"amount_usd": "5.00",
"at": "2025-01-21T20:00:00.000Z",
"transaction_id": "507f1f77bcf86cd799439110"
}
}
}

Possible data.result values:

  • granted — credited now.
  • already_claimed — this account already received it.
  • identity_claimed_elsewhere — another account already claimed with this GitHub identity.
  • not_linked — no GitHub identity on this account.
  • offer_ended — offer is inactive.
  • retry — transient write conflict, safe to retry.
  • error — unexpected.
const outcome = await client.api.wallet.claimGithubBonus();
if (outcome.data.result === "granted") {
// The bonus has been credited to the general balance.
}