Saltà à u cuntenutu
Torna à Nymbot

Base di cunniscenza Sviluppatori

Saldu, ricariche è chjavi

Verificà ciò chì avete, ricaricà via Lightning, ricaricà automaticamente da u vostru propiu wallet, vede u coste di ogni dumanda, è gestisce e chjavi da u codice.

Verificà u saldu

I vostri dui saldi, è quanta parte di u capu di sta chìave hè utilizata.

GET https://nymbot.ai/api/v1/credits/balance — hà bisognu di una chìave API. POST funziona ancu, per i clienti chì u sperpulanu.

balance sì sò i dui saldi insiemu in dollari à u prissu attuale di u Bitcoin, per strumenti chì aspettanu un numeru solu (null se u prezzu ùn pò micca esse lettu). U restu hè in crediti è in sats, chì hè cumu i saldi sò effettivamente cunservati. key descrive a chìave chì hà dumandatu. Una chìave chì hà reachu u so capu pò ancu verificà u saldu.

Risposta

{
  "balance": 49.18,
  "balance_sats": 42037,
  "standard": { "credits": 120.4, "sats": 1204 },
  "pro": { "credits": 408.33, "sats": 40833 },
  "key": {
    "id": "4f0c9a1be27d3856",
    "name": "laptop scripts",
    "limit_sats": 20000,
    "period_used_sats": 3412,
    "total_used_sats": 18230,
    "reset_period": "monthly",
    "reset_at": "2026-10-01T00:00:00Z"
  }
}
StatutuQuandu
401A chìave hè mancante, sconosciuta, revucata o scaduta.

cURL

curl https://nymbot.ai/api/v1/credits/balance \
  -H "Authorization: Bearer $NYMBOT_API_KEY"

Python

import os
import requests

res = requests.get(
    "https://nymbot.ai/api/v1/credits/balance",
    headers={"Authorization": "Bearer " + os.environ["NYMBOT_API_KEY"]},
)
balance = res.json()
print(balance["standard"]["sats"], balance["pro"]["sats"])

JavaScript

const res = await fetch("https://nymbot.ai/api/v1/credits/balance", {
  headers: { "Authorization": "Bearer " + process.env.NYMBOT_API_KEY },
});
const balance = await res.json();
console.log(balance.standard.sats, balance.pro.sats);

Modi di pagamentu

Cumu pudete ricaricà, è i limiti. Lightning hè l'unicu metodu.

GET https://nymbot.ai/api/v1/topup/payment-methods — micca di chìave necessariu.

Un ricaricu hè da 10 à 1.000.000 di sats; una ricarica Pro deve comprà almeno un creditu Pro, dunque cumencia à 100 sats. I limiti in dollari seguendu u prezzu di u Bitcoin. bulk_bonus lista u creditu extra n'i ricariche più grandi, accussì cum'è in l'app: 10%, 15% o 20% di più n'e ricariche standard da 500, 1.000 o 5.000 sats, è n'e ricariche Pro da 5.000, 10.000 o 50.000 sats.

Risposta

{
  "supported_methods": [
    {
      "method": "btc-lightning",
      "display_name": "Bitcoin Lightning",
      "supported_currencies": ["SATS", "USD", "BTC"],
      "limits": {
        "SATS": { "min": 10, "max": 1000000 },
        "USD": { "min": 0.02, "max": 1170 },
        "BTC": { "min": 1e-7, "max": 0.01 }
      },
      "tiers": ["standard", "pro"],
      "default_tier": "pro",
      "tier_min_sats": { "standard": 10, "pro": 100 },
      "sats_per_credit": { "standard": 10, "pro": 100 },
      "bulk_bonus": [
        { "bonus": 0.1, "standard_sats": 500, "pro_sats": 5000 },
        { "bonus": 0.15, "standard_sats": 1000, "pro_sats": 10000 },
        { "bonus": 0.2, "standard_sats": 5000, "pro_sats": 50000 }
      ]
    }
  ]
}

cURL

curl https://nymbot.ai/api/v1/topup/payment-methods

Python

import requests

methods = requests.get("https://nymbot.ai/api/v1/topup/payment-methods").json()
print(methods["supported_methods"][0]["limits"])

JavaScript

const methods = await (await fetch("https://nymbot.ai/api/v1/topup/payment-methods")).json();
console.log(methods.supported_methods[0].limits);

Ricarica via Lightning

Fà una fattura Lightning chì aghjunghje u creditu à u nym à cui appartene a chìave. Pagheteghjà da qualsiasi wallet Lightning, dopu verificà it per avè u creditu aghjuntu.

POST https://nymbot.ai/api/v1/topup/create/btc-lightning — hà bisognu di una chìave API.

CampuTipuNecessariuDescrizzione
amountnumeruIèQuanto, in currencyUn numeru interu per i sats.
currencystringNoSATS (u per default), USD o BTC. I dollari sò cunvertiti à u prezzu attuale di u Bitcoin.
tierstringNopro (u per dëfaut) o u standard: quale bilanciu riceve u creditu.

Un credit standard hè di 10 sats è un credit Pro hè di 100 sats, più qualsìevu bonus per l'accumuļu; credits dice ciò chì sta à aghjunghje stu fattura.

Risposta

{
  "invoice_id": "b7d41e0c95a2f38e6c1d0e9a4b7f2c61d3e8a05f9b2c4d7e1a6f3b8c0d5e2a9f4",
  "payment_request": "lnbc100u1p5...",
  "amount_sats": 10000,
  "credits": 115,
  "tier": "pro",
  "expires_at": "2026-09-30T09:27:00Z",
  "status": "pending"
}
StatutuQuandu
400Un altru metodu in u percorsu (unsupported_method), una moneta sconosciuta (unsupported_currency) o un nivellu, una quantità mancante, o una quantità sottu u minimu (amount_too_small), sopra 1.000.000 di sats (amount_too_large) o rifiutatu da u portafoglio Lightning (amount_out_of_range).
429Più di 60 fatture per questu nime, o 120 da stu indirizzu, in un'ora (rate_limit_exceeded, cù Retry-After).
502Nessuna fattura ùn pò esse fatta avèntu (invoice_unavailable, cù Retry-After).

cURL

curl https://nymbot.ai/api/v1/topup/create/btc-lightning \
  -H "Authorization: Bearer $NYMBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amount": 10000, "currency": "SATS", "tier": "pro"}'

Python

import os
import requests

res = requests.post(
    "https://nymbot.ai/api/v1/topup/create/btc-lightning",
    headers={"Authorization": "Bearer " + os.environ["NYMBOT_API_KEY"]},
    json={"amount": 10000, "currency": "SATS", "tier": "pro"},
)
invoice = res.json()
print(invoice["payment_request"])

JavaScript

const res = await fetch("https://nymbot.ai/api/v1/topup/create/btc-lightning", {
  method: "POST",
  headers: {
    "Authorization": "Bearer " + process.env.NYMBOT_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ amount: 10000, currency: "SATS", tier: "pro" }),
});
const invoice = await res.json();
console.log(invoice.payment_request);

Verificà una ricarica

Chjamu s'a fattura hè stata pagata è, una volta chì u hè, si aghjunghje u creditu. Verificà hè ciò chì u creditata, dunque dopu à pagà, verificà finchè u statu ùn hè creditedVerificà di novu dopu hè sicuru: u creditu si deposita una volta sola, quantu una sia u vostru dumanda.

GET https://nymbot.ai/api/v1/topup/status/{invoice_id} — hà bisognu di una chiave da u nùmulu chì hà fattu a fattura.

status è pending (ancora ùn pagatu micca), paid (pagatu, ma micca ancu accreditatu; ricontrollate), credited (supra u vostru saldu) o u expired (micca pagatu in tempu). I campi di saldu sò per u livellu chì a fattura rimpiscia.

Risposta

{
  "invoice_id": "b7d41e0c95a2f38e6c1d0e9a4b7f2c61d3e8a05f9b2c4d7e1a6f3b8c0d5e2a9f4",
  "status": "credited",
  "amount_sats": 10000,
  "credits": 115,
  "tier": "pro",
  "expires_at": null,
  "balance_credits": 523.33,
  "balance_sats": 52333
}
StatutuQuandu
400L'ID ùn hè micca l'ID di 64 caratteri di a chjama di creazione.
404Nessuna fattura cù quell'ID per u vostru nym (invoice_not_found).

cURL

curl https://nymbot.ai/api/v1/topup/status/$INVOICE_ID \
  -H "Authorization: Bearer $NYMBOT_API_KEY"

Python

import os, time
import requests

headers = {"Authorization": "Bearer " + os.environ["NYMBOT_API_KEY"]}
url = "https://nymbot.ai/api/v1/topup/status/" + invoice["invoice_id"]

while True:
    status = requests.get(url, headers=headers).json()["status"]
    if status in ("credited", "expired"):
        break
    time.sleep(3)
print(status)

JavaScript

const headers = { "Authorization": "Bearer " + process.env.NYMBOT_API_KEY };
const url = "https://nymbot.ai/api/v1/topup/status/" + invoice.invoice_id;

let status;
do {
  await new Promise((r) => setTimeout(r, 3000));
  status = (await (await fetch(url, { headers })).json()).status;
} while (status !== "credited" && status !== "expired");
console.log(status);

Istoricu di e ricerche

Una riga per ogni dumanda: ch'è stata, quale mudellu, quantu token è ch'è costatu. Nisun prompt o risposta ùn hè conservatu, quindi nisunu hè rimandatu. E righe sò conservate per 90 ghjorni, e più novelle prima. Una chìave chì hà raggiuntu u so capu pò ancu legge a so storia.

GET https://nymbot.ai/api/v1/queries/history — hà bisognu di una chìave API, chì vede i so stessi richesti, o un richestu firmatu da u vostru nùme, chì vede ogni chìave.

CampuTipuNecessariuDescrizzione
pagenumeru interu (ricerca)NoPer difettu 1, un massimu di 1.000; una pagina più alta hè 400 invalid_value.
page_countnumeru interu (ricerca)NoRighe per pagina. Per difettu 20, al più 100.
start_date
end_date
stringa (ricerca)NoISO 8601 date o l'ore.
modelstringa (ricerca)NoSolamente stu mudellu.
typestringa (ricerca)Nochat, responses, messages, image, video, speech, transcription o embedding.
all_keysbulianu (ricerca)NoAvec una chiave: true include ogni chìave di u medemu nime. Default false.
key_idstringa (ricerca)NoCum una dumanda firmatu, o cù all_keys=true: sulamente questa chiave.

Risposta

{
  "data": [
    {
      "id": "q_71c4e9a0",
      "timestamp": "2026-09-30T08:12:00Z",
      "model": "anthropic/claude-sonnet-5",
      "type": "chat",
      "input_tokens": 1240,
      "output_tokens": 380,
      "cached_tokens": 0,
      "cost_sats": 16.2,
      "cost_usd": 0.01895,
      "balance": "pro",
      "key_id": "4f0c9a1be27d3856",
      "web_search": false,
      "status": "ok"
    }
  ],
  "pagination": { "page": 1, "page_count": 20, "total": 311, "total_pages": 16 }
}

cURL

curl "https://nymbot.ai/api/v1/queries/history?page_count=50&type=chat" \
  -H "Authorization: Bearer $NYMBOT_API_KEY"

Python

import os
import requests

res = requests.get(
    "https://nymbot.ai/api/v1/queries/history",
    headers={"Authorization": "Bearer " + os.environ["NYMBOT_API_KEY"]},
    params={"page_count": 50, "type": "chat"},
)
for row in res.json()["data"]:
    print(row["timestamp"], row["model"], row["cost_sats"])

JavaScript

const res = await fetch("https://nymbot.ai/api/v1/queries/history?page_count=50&type=chat", {
  headers: { "Authorization": "Bearer " + process.env.NYMBOT_API_KEY },
});
for (const row of (await res.json()).data) console.log(row.timestamp, row.model, row.cost_sats);

Firmà e dumande di contu

A creazione, a modificazione è a revoca di e chjavi, u riassuntu di u contu è i ricaricamenti automatici ùn pighenu micca una chjave API. Ellu pighena una firma di u vostru nym, dunque una chjave fuggiuta pò spende finu à u so capu ma ùn pò micca creà un'altri chjavi nè aumentà u so propiu capu.

L'applica fa questu per voi: tuttu in u so posto API u fogliu usa questi endpoint. Avete solu bisognu di questa sezione per gestisce i chìavi da u vostru codici.

A firma hè un eventi Nostr di tipu 27235 (NIP-98), mandatu in base64-encoded in u Authorization testata cù a parolla Nostr davanti:

L'eventu

{
  "kind": 27235,
  "created_at": 1790726400,
  "tags": [
    ["u", "https://nymbot.ai/api/v1/keys"],
    ["method", "POST"],
    ["nonce", "9c4e21f07a3b...16 random bytes in hex"],
    ["payload", "3f1a0d7c8e2b...sha256 of the exact request body in hex"]
  ],
  "content": "",
  "pubkey": "your public key in hex",
  "id": "...",
  "sig": "..."
}
  • u hè l'URL chjenu di a dumanda, string di ricerca inclusu, esattamente cum'è mandatu.
  • method hè u metodu HTTP.
  • payload hè u SHA-256 di u corpu di a richiesta bruta, in hex. Hè ضرورà nantu à POST è PATCH, è u corpu chì mandate deve esse byte per byte quellu chì avete hashatu.
  • created_at deve esse indistinu à 60 sicondi di l'orologiu di u servitore.
  • Ogni evenement hè travagliatu una volta, a GET inclu, dunque un intestazione catturata ùn pò micca esse ripurtata. Firmate una nova per ogni dumanda. Aghjettate un nonce tag cù un valore casale perchì dui richieste firmate in u stessu segundu sò dumandate diverse.
  • U corpu di una dumanda firmatu pò esse al più 64 KB, è un corpu hà bisognu di Content-Type: application/json.

Un eventi mancante torna 401 missing_nostr_auth; unu chì hè malformatu, malfirmatu, troppu vecchiu, o per una URL, un metodu o un corpu differente torna invalid_nostr_auth, cù a ragione in u messaghju; unu riutilizatu ritorna nostr_auth_replayed. Una chìave API mandata à sti endpoint hè rifiutata. A firma hè verificata prima chì u corpu sia lettu, è ogni indirizzu pò fallisce essa 30 volte par minutu (un indirizzu IPv6 conta cum'è u so tuttu /64); dopu chè u hè 429 cu u Retry-After.

I navigaturi ponu chjamà sti endpoint solu da i siti propi di Nymbot (https://nymbot.ai, https://nymchat.app è i so subdomini). Una pagina nantu à quìsi altru siti ùn riceve nisun header CORS, dunque ùn pò micca legge ciò chì elli ritornanu. I script è e app native, chì ùn mandanu micca Origin, ùn sò micca influenzati.

A vostra chìave segreta

A firma hà bisognu di a chìave segreta di u vostru nyme (u nsec), chì cuntrolla tuttu : a vostra identità, a vostra storia è u vostre saldu. Metteghitelo solu in un script nantu à una macchina chì vi fidate, leggetelu da l'ambiente invece di scriveghialu in u file, è preferite l'app quandu pudete.

Quessii aiuti u mbuidu u capu. L'esempi seguenti in sta pagina u utilizanu. Iddunu a chìave segreta in es in u NOSTR_SECRET_HEX; quellu di cURL usa u nacu strumentu di linea di cumandu, chì piglia una chìave nsec o hex, è sha256sum (su macOS, shasum -a 256).

cURL

nostr_auth() {
  method="$1"; url="$2"; body="$3"
  nonce=$(openssl rand -hex 16)
  if [ -n "$body" ]; then
    hash=$(printf '%s' "$body" | sha256sum | cut -d' ' -f1)
    event=$(nak event --sec "$NOSTR_SECRET_HEX" -k 27235 -t "u=$url" -t "method=$method" -t "nonce=$nonce" -t "payload=$hash")
  else
    event=$(nak event --sec "$NOSTR_SECRET_HEX" -k 27235 -t "u=$url" -t "method=$method" -t "nonce=$nonce")
  fi
  printf 'Nostr %s' "$(printf '%s' "$event" | base64 | tr -d '\n')"
}

Python

# pip install coincurve requests
import base64, hashlib, json, os, time
from coincurve import PrivateKey, PublicKeyXOnly

SECRET = bytes.fromhex(os.environ["NOSTR_SECRET_HEX"])

def nostr_auth(method, url, body=b""):
    pubkey = PublicKeyXOnly.from_secret(SECRET).format().hex()
    tags = [["u", url], ["method", method], ["nonce", os.urandom(16).hex()]]
    if body:
        tags.append(["payload", hashlib.sha256(body).hexdigest()])
    created_at = int(time.time())
    serialized = json.dumps([0, pubkey, created_at, 27235, tags, ""], separators=(",", ":"), ensure_ascii=False)
    event_id = hashlib.sha256(serialized.encode()).digest()
    event = {
        "id": event_id.hex(),
        "pubkey": pubkey,
        "created_at": created_at,
        "kind": 27235,
        "tags": tags,
        "content": "",
        "sig": PrivateKey(SECRET).sign_schnorr(event_id).hex(),
    }
    return "Nostr " + base64.b64encode(json.dumps(event).encode()).decode()

JavaScript

// npm install nostr-tools
import { createHash, randomBytes } from "node:crypto";
import { finalizeEvent } from "nostr-tools/pure";

const secret = Buffer.from(process.env.NOSTR_SECRET_HEX, "hex");

export function nostrAuth(method, url, body = "") {
  const tags = [["u", url], ["method", method], ["nonce", randomBytes(16).toString("hex")]];
  if (body) tags.push(["payload", createHash("sha256").update(body).digest("hex")]);
  const event = finalizeEvent(
    { kind: 27235, created_at: Math.floor(Date.now() / 1000), tags, content: "" },
    secret,
  );
  return "Nostr " + Buffer.from(JSON.stringify(event)).toString("base64");
}

U resumè di u compte

Cosa mostra u fogliu API di l'app in cima: a vostra chìàve pubblica, i dui saldi, quantu chìàvi sò attive (micca revocate o scadute), è u ricarica automatica impostazioni, o null quandu u servitore ùn offre micca i fan.

GET https://nymbot.ai/api/v1/account — ha bisognu di un richestu firmatu.

Risposta

{
  "data": {
    "pubkey": "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d",
    "balances": {
      "standard": { "credits": 120.4, "sats": 1204 },
      "pro": { "credits": 408.33, "sats": 40833 }
    },
    "keys_active": 3,
    "nwc_auto_topup": {
      "connected": true, "threshold_sats": 5000, "topup_sats": 20000, "tier": "pro",
      "last_topup_at": null, "last_topup_sats": null, "last_error": null
    }
  }
}

cURL

URL=https://nymbot.ai/api/v1/account
curl "$URL" -H "Authorization: $(nostr_auth GET "$URL")"

Python

import requests

url = "https://nymbot.ai/api/v1/account"
print(requests.get(url, headers={"Authorization": nostr_auth("GET", url)}).json())

JavaScript

const url = "https://nymbot.ai/api/v1/account";
const res = await fetch(url, { headers: { "Authorization": nostrAuth("GET", url) } });
console.log(await res.json());

Gestione di e chjavi

I punti di termina darettu à a lista di e chì chì di l'app. Tutti quanti hanno bisognu di un riconoscimentu richestuOgni chì hè rimandatu in sta forma, cù i tempi in ISO 8601 è i montanti in sats:

Oggettu chjave

{
  "id": "4f0c9a1be27d3856",
  "name": "laptop scripts",
  "hint": "sk-nymbot-Qm7x…c2Lw",
  "limit_sats": 20000,
  "reset_period": "monthly",
  "reset_at": "2026-10-01T00:00:00Z",
  "expire_at": null,
  "period_used_sats": 3412,
  "total_used_sats": 18230,
  "created_at": "2026-08-14T09:21:07Z",
  "updated_at": "2026-09-02T17:40:55Z",
  "last_used_at": "2026-09-30T08:12:00Z",
  "revoked_at": null
}

hint hè sufficienti per ricunoscì una chìave ma micca per usà ela. A chìave stissa hè rimessa una sola volta, quandu hè fatta.

Elencà e chiavi

GET https://nymbot.ai/api/v1/keys — firmatu.

CampuTipuNecessariuDescrizzione
include_revokedbulianu (ricerca)NoInclude e chjavi revocate. Difalsu false.

Risposta

{ "data": [ { "id": "4f0c9a1be27d3856", "name": "laptop scripts", "hint": "sk-nymbot-Qm7x…c2Lw", "...": "..." } ] }

cURL

URL=https://nymbot.ai/api/v1/keys
curl "$URL" -H "Authorization: $(nostr_auth GET "$URL")"

Python

import requests

url = "https://nymbot.ai/api/v1/keys"
for key in requests.get(url, headers={"Authorization": nostr_auth("GET", url)}).json()["data"]:
    print(key["id"], key["name"], key["period_used_sats"], key["limit_sats"])

JavaScript

const url = "https://nymbot.ai/api/v1/keys";
const { data } = await (await fetch(url, { headers: { "Authorization": nostrAuth("GET", url) } })).json();
for (const key of data) console.log(key.id, key.name, key.period_used_sats, key.limit_sats);

Fà una chjave

POST https://nymbot.ai/api/v1/keys — firmatu. Ritorne 201.

CampuTipuNecessariuDescrizzione
namestringIèDa 1 à 40 caratteri, diffirente da e vostre altre tasti attivi (ignorendu a maiuscola).
limit_satsintegruNoU capu di a spesa in sats, almenu 1. Lasciatè u fora per micca capu.
reset_periodstringNodaily, weekly o monthly. Bisogni limit_satsLacciatelu fora per un capu chì ùn si resetta mai.
expire_atstring o integerNoQuandu a chìave smette di funziona: un tempu ISO 8601, o millisecondi da u 1970.

Risposta (201)

{
  "data": {
    "id": "4f0c9a1be27d3856",
    "name": "laptop scripts",
    "hint": "sk-nymbot-Qm7x…c2Lw",
    "limit_sats": 20000,
    "reset_period": "monthly",
    "key": "sk-nymbot-Qm7x...c2Lw",
    "...": "the rest of the key object"
  }
}
StatutuQuandu
400Un nome mancante o troppo longu; un nome digià utilizatu (duplicate_name); un capu chì ùn hè micca un numeru interu di 至 least 1; una periodu di reset senza un capu; una scadenza in u passatu; un campu ùn cunsciutu (unknown_parameter); o 25 chjavi attive hè già (too_many_keys).
429Più di 60 chjavi fatte da stu nime, o 120 da stu indirizzu, in un'ura (rate_limit_exceeded, cù Retry-After).

cURL

URL=https://nymbot.ai/api/v1/keys
BODY='{"name":"laptop scripts","limit_sats":20000,"reset_period":"monthly"}'
curl "$URL" \
  -H "Authorization: $(nostr_auth POST "$URL" "$BODY")" \
  -H "Content-Type: application/json" \
  -d "$BODY"

Python

import json
import requests

url = "https://nymbot.ai/api/v1/keys"
body = json.dumps({"name": "laptop scripts", "limit_sats": 20000, "reset_period": "monthly"}).encode()
res = requests.post(
    url,
    data=body,
    headers={"Authorization": nostr_auth("POST", url, body), "Content-Type": "application/json"},
)
print(res.json()["data"]["key"])

JavaScript

const url = "https://nymbot.ai/api/v1/keys";
const body = JSON.stringify({ name: "laptop scripts", limit_sats: 20000, reset_period: "monthly" });
const res = await fetch(url, {
  method: "POST",
  headers: { "Authorization": nostrAuth("POST", url, body), "Content-Type": "application/json" },
  body,
});
console.log((await res.json()).data.key);

Leggenda una chiave

GET https://nymbot.ai/api/v1/keys/{id} — firmatu.

Ritorne {"data": {…}} cu l'oggettu chìa, o 404 key_not_found se nisuna chìave vostra hà quellu ID.

cURL

URL=https://nymbot.ai/api/v1/keys/4f0c9a1be27d3856
curl "$URL" -H "Authorization: $(nostr_auth GET "$URL")"

Python

import requests

url = "https://nymbot.ai/api/v1/keys/4f0c9a1be27d3856"
print(requests.get(url, headers={"Authorization": nostr_auth("GET", url)}).json()["data"])

JavaScript

const url = "https://nymbot.ai/api/v1/keys/4f0c9a1be27d3856";
console.log((await (await fetch(url, { headers: { "Authorization": nostrAuth("GET", url) } })).json()).data);

Chjangeà una chiave

PATCH https://nymbot.ai/api/v1/keys/{id} — firmatu.

Mandate tutt'ellu chì name, limit_sats, reset_period è expire_at, cù e stesse regole comu quandu si fà una chìave. null scurza un campu: senza limite, senza reset, senza scadenza. Cambià u periodu di reset cumencia un novu periodu da zero. Una chia revuocata ùn pò micca esse cambiata (400 key_revoked). Ritorna {"data": {…}} cum u oggettu di chiave aghjornatu.

cURL

URL=https://nymbot.ai/api/v1/keys/4f0c9a1be27d3856
BODY='{"limit_sats":50000,"expire_at":null}'
curl -X PATCH "$URL" \
  -H "Authorization: $(nostr_auth PATCH "$URL" "$BODY")" \
  -H "Content-Type: application/json" \
  -d "$BODY"

Python

import json
import requests

url = "https://nymbot.ai/api/v1/keys/4f0c9a1be27d3856"
body = json.dumps({"limit_sats": 50000, "expire_at": None}).encode()
res = requests.patch(
    url,
    data=body,
    headers={"Authorization": nostr_auth("PATCH", url, body), "Content-Type": "application/json"},
)
print(res.json()["data"])

JavaScript

const url = "https://nymbot.ai/api/v1/keys/4f0c9a1be27d3856";
const body = JSON.stringify({ limit_sats: 50000, expire_at: null });
const res = await fetch(url, {
  method: "PATCH",
  headers: { "Authorization": nostrAuth("PATCH", url, body), "Content-Type": "application/json" },
  body,
});
console.log((await res.json()).data);

Rivocà una chìave

DELETE https://nymbot.ai/api/v1/keys/{id} — firmatu.

Ferma a chìave subitu, per sempre. Resta in a lista cù revoked_at statu, è pò esse vistu cù include_revoked=trueRevoca una chiave chì hè già revucata risponde in u mediu modu. Sadece e più novelli 50 chiavi revucate sò mantenute; e più vecchie sò eliminate quandu una altra chiave hè revucata.

Risposta

{ "data": { "id": "4f0c9a1be27d3856", "revoked": true } }

cURL

URL=https://nymbot.ai/api/v1/keys/4f0c9a1be27d3856
curl -X DELETE "$URL" -H "Authorization: $(nostr_auth DELETE "$URL")"

Python

import requests

url = "https://nymbot.ai/api/v1/keys/4f0c9a1be27d3856"
print(requests.delete(url, headers={"Authorization": nostr_auth("DELETE", url)}).json())

JavaScript

const url = "https://nymbot.ai/api/v1/keys/4f0c9a1be27d3856";
const res = await fetch(url, { method: "DELETE", headers: { "Authorization": nostrAuth("DELETE", url) } });
console.log(await res.json());

Ricarica automatica NWC

Connettate un wallet Lightning cù Nostr Wallet Connect è Nymbot ricarica u saldu da sola quandu a spesa di l'API u rende bassu. U fogliu API di l'app hà e stessesi impostazioni; questi sò i punti d'accesso dietro di essa. Tutti quanti anu bisognu di una richestu firmatu.

Cumu funziona: dop chì una richiesta API sia decurtata da u saldu chì avete sceltu per monitorà, s'ellu questu saldu hè calatu sottu à a vostra soglia, Nymbot crea una fattura per a vostra quantità di ricarica, si dumanda à u vostru wallet di pagh'ellu, è aghjunghje u creditu. Ellu ricarica al più una volta ogni 5 minuti per ogni nym è saldu, cusì una serie di richieste ùn pò micca svuotà u wallet. A spesa in e app ùn attivà micca stu prucessu. U tempu è a dimensione di l'ultima ricarica, è l'ultimu errore, sò in i settings; s'ellu un pagamentu hè passatu dopu un errore, verificà a so fattura cù lu statu di a ricarica creditu it.

Prima di connettè un portafogliu

Una stringa di cunnessione permette à quellischi chì a tengenu di dumandà à u vostru wallet di pagà. Nymbot stocca essa criptata è a usa solu per pagà e i so scontrini di ricarica, ma fate una cunnessione solu per questu, cù un budget di spesa in u vostru wallet, in modu chì u massimu chì puderia pagà sia un numeru chì avete chjostu. U wallet deve esse supportà pay_invoice.

Connessione di un portafoglio

POST https://nymbot.ai/api/v1/nwc-auto-topup/connect — firmatu.

CampuTipuNecessariuDescrizzione
nwc_urlstringIèA stringa di cunnessione, cuminciendu nostr+walletconnect://. Nymbot chiede à u wallet get_info prima di salvà ellu, è u stocca criptatu.
threshold_satsintegruIèRicaricà quandu u saldu scende sottu à stu numeru di sats. Almenu 1,000.
topup_satsintegruIèQuantu à aghjunghje ogni volta. 1,000 à 1,000,000 sats.
tierstringNopro (u per dëfaut) o u standardu saldu da vede è da ricaricà.

Risposta

{
  "data": {
    "connected": true,
    "threshold_sats": 5000,
    "topup_sats": 20000,
    "tier": "pro",
    "last_topup_at": null,
    "last_topup_sats": null,
    "last_error": null
  }
}
StatutuQuandu
400Ùn hè micca una stringa di cunnessione (invalid_nwc_url); u portafoglio ùn risponde ùn u so relay (nwc_unreachable) o si rifiuta u check (nwc_rejected); a cunnessione ùn pò micca pagà e fatture (nwc_missing_permission); o un montante fora di i limiti.
501I ricaricamenti automatici ùn sò micca attivati per stu serviziu (nwc_unavailable). A a stessa cosa si applica hè à l'altri dui punti di terminazione.

cURL

URL=https://nymbot.ai/api/v1/nwc-auto-topup/connect
BODY='{"nwc_url":"nostr+walletconnect://...","threshold_sats":5000,"topup_sats":20000,"tier":"pro"}'
curl "$URL" \
  -H "Authorization: $(nostr_auth POST "$URL" "$BODY")" \
  -H "Content-Type: application/json" \
  -d "$BODY"

Python

import json, os
import requests

url = "https://nymbot.ai/api/v1/nwc-auto-topup/connect"
body = json.dumps({
    "nwc_url": os.environ["NWC_URL"],
    "threshold_sats": 5000,
    "topup_sats": 20000,
    "tier": "pro",
}).encode()
res = requests.post(
    url,
    data=body,
    headers={"Authorization": nostr_auth("POST", url, body), "Content-Type": "application/json"},
)
print(res.json())

JavaScript

const url = "https://nymbot.ai/api/v1/nwc-auto-topup/connect";
const body = JSON.stringify({
  nwc_url: process.env.NWC_URL,
  threshold_sats: 5000,
  topup_sats: 20000,
  tier: "pro",
});
const res = await fetch(url, {
  method: "POST",
  headers: { "Authorization": nostrAuth("POST", url, body), "Content-Type": "application/json" },
  body,
});
console.log(await res.json());

Leghenu i paramentri

GET https://nymbot.ai/api/v1/nwc-auto-topup — firmatu.

Riduca u stessu oggettu chì u cunnessione, cù connected: false è i resti di i campi null quandu ùn ci hè nente wallet cunnessu. A stringa di cunnessione in itself ùn hè mai rimessa.

cURL

URL=https://nymbot.ai/api/v1/nwc-auto-topup
curl "$URL" -H "Authorization: $(nostr_auth GET "$URL")"

Python

import requests

url = "https://nymbot.ai/api/v1/nwc-auto-topup"
print(requests.get(url, headers={"Authorization": nostr_auth("GET", url)}).json())

JavaScript

const url = "https://nymbot.ai/api/v1/nwc-auto-topup";
console.log(await (await fetch(url, { headers: { "Authorization": nostrAuth("GET", url) } })).json());

Diconnessione

DELETE https://nymbot.ai/api/v1/nwc-auto-topup/connection — firmatu.

Elimina a stringa di cunnessione sturata. Micca più ricariche sò fatte. Per esse sicuru, pudete ancu revoca a cunnessione in u vostru wallet.

Risposta

{ "data": { "connected": false, "threshold_sats": null, "topup_sats": null, "tier": null, "last_topup_at": null, "last_topup_sats": null, "last_error": null } }

cURL

URL=https://nymbot.ai/api/v1/nwc-auto-topup/connection
curl -X DELETE "$URL" -H "Authorization: $(nostr_auth DELETE "$URL")"

Python

import requests

url = "https://nymbot.ai/api/v1/nwc-auto-topup/connection"
print(requests.delete(url, headers={"Authorization": nostr_auth("DELETE", url)}).json())

JavaScript

const url = "https://nymbot.ai/api/v1/nwc-auto-topup/connection";
const res = await fetch(url, { method: "DELETE", headers: { "Authorization": nostrAuth("DELETE", url) } });
console.log(await res.json());

Pagamentu per dumanda senza una chiave

I punti terminali à u prezzu fissu pon trà paghà per una dumanda à la volta via Lightning, senza chì chìave, senza contu è senza saldu: POST /images/generations, POST /images/edits, POST /videos, POST /audio/speech, POST /audio/transcriptions, POST /audio/translations è POST /embeddings. Chat, Risposte è Messaggi anu sempre bisognu di una chìave. Una richiesta chì porta una chìave hè fatturata à u saldu cum'è u normale; u fluxu di pagamentu cumincia solu quandu ùn hè mandata nisuna chìave.

Nymbot parla dui versione di a stissa idea, da un solu backend: Lightning Labs' L402 (accettatu pure sottu u so vechi nomu, LSAT) è u draft di l'IETF Pagamentu Schema d'autenticazione HTTP cù u lightning metudu è charge intenzione. Usa quella chì u vostru cliente capisce.

Ghè un vostru servitore

Pagamentu senza una chìave hè attivatu solu quandu API_L402_SECRET tene almenu 32 byte randum, cum u hex (64 caratteri) o base64 (44). Fàne unu cù openssl rand -hex 32Un valore più curtu o di guessable spegne a funzione è registra u motivu. Per rotà ite, move u vechju valore à API_L402_SECRET_PREVIOUS per un ghjornu: e credenziali, i URL di statu è e sfide fatte sottu à ellu continuanu à funziona finchè ùn espiranu micca.

A sfida

Mandate a dumanda senza nente Authorization intestazione. S'ellu hè validu, niente ùn corre, è tù ricevi 402 Payment Required cu una fattura per esattamente ciò chì quella dumanda costa: u stessu prezzu chì una chìe pagherebbe, cunvertitu à 10 sats per un creditu standard o 100 sats per un creditu Pro è arrotondatu à un sat interu (almenu 1 sat, è almenu u minimu di 0.05 creditu). A risposta porta trè WWW-Authenticate sfide per a stissa fattura:

HTTP/1.1 402 Payment Required
Content-Type: application/problem+json; charset=utf-8
Cache-Control: no-store
WWW-Authenticate: L402 macaroon="AgJC...", invoice="lnbc2370n1..."
WWW-Authenticate: LSAT macaroon="AgJC...", invoice="lnbc2370n1..."
WWW-Authenticate: Payment id="kM9x...", realm="nymbot", method="lightning", intent="charge",
    request="eyJhbW91bnQiOiIyMzciLC...", description="Nymbot API POST /images/generations (237 sats)",
    digest="sha-256=:X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=:", expires="2026-09-30T12:15:00.000Z",
    opaque="eyJlbmRwb2ludCI6IlBPU1QgL2ltYWdlcy9nZW5lcmF0aW9ucyJ9"

{
  "type": "https://paymentauth.org/problems/payment-required",
  "title": "Payment Required",
  "status": 402,
  "detail": "This request costs 237 sats. Pay the Lightning invoice, then send the identical request again ...",
  "challengeId": "kM9x...",
  "amount_sats": 237,
  "invoice": "lnbc2370n1...",
  "payment_hash": "9db1370f...",
  "expires_at": "2026-09-30T12:15:00.000Z",
  "error": { "message": "This request costs 237 sats. ...", "type": "payment_required", "code": "payment_required", "param": null }
}

U pagamentu request u parametru hè un JSON base64url: {"amount":"237","currency":"sat","methodDetails":{"invoice":"lnbc...","network":"mainnet","paymentHash":"..."}}.

Una sfida hè ligata à u puntu terminale, à u Content-Type (u soitu di u media è, per multipart, u so limite) è à u SHA-256 di i byte esatti di u corpu chì avete mandatu, è dura 15 minuti. Dopu u pagamentu, mandate u identicu richiamate di novu: u stessu Content-Type è i stessi byte JSON, o per i termini multipart (/images/edits, /audio/transcriptions, /audio/translations) lu stessu corpu multipart cù u stessu limite. A maiur parti di e librerie HTTP pighenu un novu limite ogni volta chì elli codifichenu una forma, dunque codificatelu una volta è mandate ste byte dui volte.

Ogni indirizzu pò dumandà 30 sfide per minutu (un indirizzu IPv6 conta cum'è u so interu /64). E richieste u cui l'indirizzu ùn hè micca cunosciu condividenu una norma più stritta di 10 per minutu, è ci hè un capu generale supra e sfide chì Nymbot emette trà tutti l'indirizzi; una richiesta rifiutata prima chì una sfida sia fatta (per esempiu cù un corpu chì ùn hè micca un JSON validu) ùn conta micca per ellu. Oltre à isso a risposta hè 429 cu u Retry-After, mandatu prima chì u corpu sia lettu. I puntoni chjamati senza una chiave o un credenziali contanu ancu in u limitiu generale di 120 richieste senza autenticazione par minutu per indirizzu. Una credenziale hè verificata prima chì u corpu sia lettu, è una richiesta chì u Content-Type è micca application/json (o multipart/form-data per i caricamenti) hè rifiutatu cù 415 è ùn riceve mai una fattura.

I pizzi di l'embeddings sò basati nantu à una stima di i token in l'input, cù un margine di 1.5×, perchè u conteggiu reale hè saputu solu dopu. Vuje pagate per i token effettivamente utilizati, è a parti di u pagamentu micca utilizata torna versu di voi cum'è un rimbusu di u token.

Mandendu u pagamentu

Paghe a fattura cù quessiunque wallet Lightning. U wallet vi dà a preimage, 64 caratteri esadecimali. Poi mandate a stessa dumanda cù unu di questi:

SchemaIntitulatu
L402Authorization: L402 <macaroon>:<preimage> (LSAT funziona ancu )
PagamentuAuthorization: Payment <base64url JSON>, indu hè u JSON {"challenge": {every parameter of the challenge, as sent}, "payload": {"preimage": "<hex>"}}

Una dumanda pagata risponde esattamente cum'una fatta cù una chìave, eccettu chì a nymbot l'oggettu ùn hà micca campu di saldu: {"payment": "l402", "tier": "pro", "paid_sats": 237, "charged_sats": 237}, è ùn ci hè micca X-Nymbot-Balance-Sats intestazione. Una richesta pagata cù u schema di Pagamentu riceve ancu un Payment-Receipt intestazione (base64url JSON cù l'ID di a sfida, u hash di u pagamentu cum'è reference, status è timestamp). E dumande paghate ùn sò ligati à alcun nym, dunque ùn si vedanu micca in a storia di a ricerca.

StatutuQuandu
402 payment_already_usedOgni pagamentu paga per una dumanda. A risposta hè una nova sfida per sta dumanda, dunque un cliente chì mette in cache a so ultima credenziale (cum'à lnget paga simplicemente di novu.
402 payment_mismatchL'autenticazione hè stata emessa per un altru endpoint, Content-Type ou corpu, ou paga meno di ciò chì sta dumanda costa avà. Una nova sfida per sta dumanda vene cù essa; se u pagamentu era troppu picculu, ciò chì avete pagatu torna cum'è un rimbusu di u token (refund_token è refund_sats in u corpu).
402 payment_expiredPiù di 15 minuti sò passati da a sfida. Una nova sfida vene cù essa. S'a preimaghjine mostra chì avete pagatu, ciò chì avete pagatu torna cum'è un rimbusu di u token (refund_token è refund_sats in u corpu), una volta; a credenziale hè dopu à esse usata.
401 invalid_preimageL'imaghjina precedente ùn hè micca hashata per u hash di pagamentu di a fattura. U pagamentu ùn hè micca usatu.
401 invalid_payment_credentialA credenziale hè malformata, hè stata cambiata dopu à ciò chì Nymbot l'ha emessa, o nomina un hash di pagamentu per u quale Nymbot ùn hà mai emesso una fattura. Un macaroon cù una caveat chì Nymbot ùn conosce micca, o cù caveats cunflittuali, hè rifiutatu.
429 rate_limit_exceededPiù di 30 credenziali o chjavi chì anu fallitu a verificazione sò venuti da stu indirizzu in un minutu, o un token di rimpursu hè statu mandatu più di 60 volte in un minutu. Aspetta per Retry-After.

cURL

BODY='{"model":"nano-banana","prompt":"a lighthouse at dusk","response_format":"b64_json"}'
URL=https://nymbot.ai/api/v1/images/generations

# 1. Get the challenge
CH=$(curl -s -D - -o /dev/null "$URL" -H "Content-Type: application/json" -d "$BODY" | grep -i '^www-authenticate: L402')
MAC=$(echo "$CH" | sed -E 's/.*macaroon="([^"]+)".*/\1/')
INVOICE=$(echo "$CH" | sed -E 's/.*invoice="([^"]+)".*/\1/')

# 2. Pay $INVOICE with your wallet and copy the preimage
PREIMAGE=...

# 3. Send the identical request with the credential
curl "$URL" -H "Content-Type: application/json" -H "Authorization: L402 $MAC:$PREIMAGE" -d "$BODY"

lnget

# lnget (Lightning Labs) pays L402 challenges from your own lnd node and retries for you
lnget -X POST -H "Content-Type: application/json" \
  -d '{"model":"nano-banana","prompt":"a lighthouse at dusk","response_format":"b64_json"}' \
  https://nymbot.ai/api/v1/images/generations

Python

import base64, json, re
import requests

url = "https://nymbot.ai/api/v1/images/generations"
body = json.dumps({"model": "nano-banana", "prompt": "a lighthouse at dusk", "response_format": "b64_json"}).encode()
headers = {"Content-Type": "application/json"}

challenge = requests.post(url, data=body, headers=headers)
assert challenge.status_code == 402
info = challenge.json()
print("Pay", info["amount_sats"], "sats:", info["invoice"])

preimage = pay_with_your_wallet(info["invoice"])  # 64 hex characters

# L402
mac = re.search(r'L402 macaroon="([^"]+)"', challenge.headers["WWW-Authenticate"]).group(1)
res = requests.post(url, data=body, headers={**headers, "Authorization": f"L402 {mac}:{preimage}"})
print(res.json()["nymbot"])

JavaScript

const url = "https://nymbot.ai/api/v1/images/generations";
const body = JSON.stringify({ model: "nano-banana", prompt: "a lighthouse at dusk", response_format: "b64_json" });
const headers = { "Content-Type": "application/json" };

const challenge = await fetch(url, { method: "POST", headers, body });
const www = challenge.headers.get("www-authenticate");

// The Payment scheme: echo every challenge parameter back with the preimage
const start = www.indexOf("Payment ");
const params = Object.fromEntries([...www.slice(start + 8).matchAll(/(\w+)="((?:[^"\\]|\\.)*)"/g)].map((m) => [m[1], m[2]]));
const { invoice } = JSON.parse(Buffer.from(params.request, "base64url").toString()).methodDetails;
const preimage = await payWithYourWallet(invoice);

const credential = Buffer.from(JSON.stringify({ challenge: params, payload: { preimage } })).toString("base64url");
const res = await fetch(url, { method: "POST", headers: { ...headers, Authorization: `Payment ${credential}` }, body });
console.log((await res.json()).nymbot, res.headers.get("payment-receipt"));

Clienti bâtiti su mppx cu un handle di metodu Lightning gestisce a sfida di Pagamentu ellisi stessi; puntateli versu l'endpoint è lasciateli paghjà.

Vidii

Pagatu POST /videos risposte 202 cumtene una chìave, più un status_url: GET senza alcuna chìave per seguì u travagliu. Hè firmatu è funziona per 24 ore, tantu chì u travagliu sia mantenutu.

{
  "id": "vid_...",
  "status": "in_progress",
  "status_url": "https://nymbot.ai/api/v1/videos/vid_...?exp=1790000000&sig=...",
  "nymbot": {
    "payment": "l402", "tier": "pro", "paid_sats": 4800, "charged_sats": 4800,
    "refund_token": "REFUND-5E0B..."
  }
}

Mantene u refund_token da sta risposta: hè mostratu solu quì. Hè vacante mentra u video si renderizza (GET /api/v1/l402/refunds risposte "status": "pending"); si u render fallisce senza fatturatu, u pagamentu cade sopra ellu. L'URL di u statu mostra refund_sats per un travagli rimborsatu ma mai u token, dunque u cumpartuvaghju di l'URL di u statu ùn cumpartaghja micca u rimboru. U verificà u token risolve ancu un travagli fallitu chì nisun ùn hà interrogatu.

Rimborzi

Sì una dumanda pagata fallisce è u furnitore fattura à Nymbot per u tentativu, u pagamentu hè ritentu è l'erroru u dice, cù charged_sats, esattamente cum'è per una dumanda chjunta. S'ellu fallisce senza esse fatturatu, l'erroru porta un rimbusu di u token vale quantu avete pagatu:

{
  "error": {
    "message": "The image generator failed. Nothing was charged. Please try again. The 237 sats you paid are on refund token REFUND-...",
    "type": "api_error",
    "code": "upstream_error",
    "refund_token": "REFUND-0C15DBED145D59131BA70298A413ADEB3D9B9AB082C476EA70A5BD38FCA5DE6D",
    "refund_sats": 237,
    "refund_expires_at": "2026-10-30T12:00:00.000Z"
  }
}

I pezzi ùn utilizati ritornanu in u stessu modu: s'avìate dumandatu dui imaghjini è una hè fallitu senza esse fatturata, a risposta di successu di nymbot l'oggettu porta un tòccu di rimbuorsu per quellu ca manca; una trascrizione a chì a so lunghezza ùn pudia micca esse letta prima hè prissata per a lunghezza massima chì u file puderia avè (mai più di 30 minuti), è a differenza cù a lunghezza reale torna cum'è un tòccu di rimbuorsu; s'ellu si scopra chì hè più longu di 30 minuti, hè rifiutatu cù 413 è tuttu u pagamentu torna. L'embeddings rimandanu ciò chì a stima avìa ritiratu. Un videu fallimentu rimborsanu u token chì a so sottomissione avìa rimandatu.

Un token di rimborso hè un codice aleatoriu di 256 bit. Nymbot custodisce solu u so hash, è scade dopu 30 ghjorni. Ellu cuntene un saldu di sat, è pudete:

  • Paghe cù questu. Mandà Authorization: Bearer REFUND-… su quessiunque di i termini sopra citati (un SDK di OpenAI u piglia cum'è a so chìave API). U prezzu hè calcolatu in base à u token è ciò chì resta resta nantu à ellu (refund_token_sats in u nymbot oggettu). Un tòkemu chì vale meno di e risposte di a dumanda 402 refund_insufficient; un fallimentu micca fatturato rimette i sats nantu à u stessu token.
  • Verificà GET /api/v1/l402/refunds cum a stessa intestazione ritorna {"sats": 237, "status": "open", "expires_at": "..."}.
  • Un token pò esse usatu al più 60 volte par minutu.
  • Trasferiscila à un nym. Incollatu in Riscattà un regalu in u app Nymbot, o chjamate POST /api/v1/l402/refunds/redeem cu un u richestu firmatu è {"refund_token": "REFUND-...", "balance": "standard"} (o "pro"). I crediti interi andanu à u saldu (10 sats ciasun biri in standard, 100 in Pro); i sats chì ùn fanu micca un creditu interu restanu nantu à u token per e richieste API.

cURL

URL=https://nymbot.ai/api/v1/l402/refunds/redeem
BODY='{"refund_token":"REFUND-...","balance":"standard"}'
curl "$URL" \
  -H "Authorization: $(nostr_auth POST "$URL" "$BODY")" \
  -H "Content-Type: application/json" \
  -d "$BODY"

Risposta

{ "data": { "credited": 23, "tier": "standard", "balance_credits": 123, "remaining_sats": 7 } }