Base di conoscenza sviluppatori
Balance, top-up e chiavi
Controllate ciò che avete, superate Lightning, superate automaticamente dal vostro portafoglio, vedete quanto costa ogni richiesta e gestite le chiavi dal codice.
Questa pagina è tradotta automaticamente per comodità. Si applica l'originale inglese.
Controllare il bilancio
Entrambi i tuoi bilanci, e quanto del cappello di questa chiave è utilizzato.
GET https://nymbot.ai/api/v1/credits/balance Necessita di una chiave API. POST Questo vale anche per i clienti che si aspettano.
balance è i due saldi insieme in dollari al prezzo corrente Bitcoin, per gli strumenti che si aspettano un singolo numero (null se il prezzo non può essere letto). il resto è in crediti e sats, che è come i saldi sono effettivamente mantenuti. key Una chiave che ha raggiunto il suo limite può ancora controllare il saldo.
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"
}
}
| Statuto | Quando |
|---|---|
401 | La chiave è mancante, sconosciuta, revocata 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);
Metodi di pagamento
Come si può top up, e i limiti. Lightning è l'unico metodo.
GET https://nymbot.ai/api/v1/topup/payment-methods Nessuna chiave necessaria.
Un top-up è da 10 a 1.000.000 sats; un top-up Pro deve acquistare almeno un credito Pro, quindi inizia a 100 sats. bulk_bonus elenca il credito aggiuntivo sui top-up più grandi, lo stesso che nell'app: 10%, 15% o 20% in più sui top-up standard da 500, 1.000 o 5.000 sats, e sui top-up 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);
Scalare sopra il fulmine
Crea una fattura Lightning che aggiunge credito alla nicchia alla quale appartiene la chiave. Pagala da qualsiasi portafoglio Lightning, quindi Verificare il Per avere il credito aggiunto.
POST https://nymbot.ai/api/v1/topup/create/btc-lightning Necessita di una chiave API.
| campo | Tipo | richiesto | Descrizione |
|---|---|---|---|
amount | Numero | SÌ | Quanto, in currencyUn numero intero per scommessa. |
currency | Stringa | non | SATS e il default, USD o BTCI dollari sono convertiti al prezzo corrente di Bitcoin. |
tier | Stringa | non | pro (il default) o standard: a quale bilancio va il credito. |
Un credito standard è 10 sats e un credito Pro 100 sats, più qualsiasi bonus di massa;
credits Dice cosa aggiungerà questa fattura.
Risposta
{
"invoice_id": "b7d41e0c95a2f38e6c1d0e9a4b7f2c61d3e8a05f9b2c4d7e1a6f3b8c0d5e2a9f4",
"payment_request": "lnbc100u1p5...",
"amount_sats": 10000,
"credits": 115,
"tier": "pro",
"expires_at": "2026-09-30T09:27:00Z",
"status": "pending"
}
| Statuto | Quando |
|---|---|
400 | Un altro metodo nel cammino (unsupported_method), una moneta sconosciuta (unsupported_currencyb) un importo inferiore o un importo inferiore al minimo (amount_too_small, superiore a 1 000 000 sats (amount_too_large) o rifiutato dal portafoglio Lightning (amount_out_of_range). |
429 | Più di 60 fatture per questo nym, o 120 da questo indirizzo, in un'ora (rate_limit_exceeded, con Retry-After). |
502 | Nessuna fattura può essere effettuata in questo momento (invoice_unavailable, con 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);
Controllare il top-up
Chiede se la fattura è stata pagata e, una volta che lo ha, aggiunge il credito. creditedControllare di nuovo dopo è sicuro: il credito atterra una volta, non importa quante volte chiedi.
GET https://nymbot.ai/api/v1/topup/status/{invoice_id} - ha bisogno di una chiave dal nym che ha fatto la fattura.
status è pending (non è ancora stato pagato) paid (pagato, ma non ancora credito; controllare di nuovo), credited (sulla vostra bilancia) o expired (non pagato in tempo).I campi di bilancio sono per il livello in cui la fattura supera.
Risposta
{
"invoice_id": "b7d41e0c95a2f38e6c1d0e9a4b7f2c61d3e8a05f9b2c4d7e1a6f3b8c0d5e2a9f4",
"status": "credited",
"amount_sats": 10000,
"credits": 115,
"tier": "pro",
"expires_at": null,
"balance_credits": 523.33,
"balance_sats": 52333
}
| Statuto | Quando |
|---|---|
400 | L'id non è l'id a 64 caratteri della chiamata creata. |
404 | Nessuna fattura per quell'id per il tuo 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);
Vogliamo la storia
Una riga per richiesta: che cos'era, quale modello, quanti token e quanto costa. Non vengono conservati suggerimenti o risposte, quindi nessuna viene restituita. Le riga vengono conservate per 90 giorni, la più recente prima. Una chiave che ha raggiunto il suo limite può ancora leggere la sua storia.
GET https://nymbot.ai/api/v1/queries/history - ha bisogno di una chiave API, che vede le proprie richieste, o La richiesta firmata dal tuo nim, che vede ogni chiave.
| campo | Tipo | richiesto | Descrizione |
|---|---|---|---|
page | Integrazione (query) | non | Impostazione predefinita 1, al massimo 1000; una pagina più alta è 400 invalid_value. |
page_count | Integrazione (query) | non | Rane per pagina. Impostazione predefinita 20, al massimo 100. |
start_dateend_date | String di ricerca (Query) | non | ISO 8601 Date o orari. |
model | String di ricerca (Query) | non | Solo questo modello. |
type | String di ricerca (Query) | non | chat, responses, messages, image, video, speech, transcription o embedding. |
all_keys | Il booleano (query) | non | Con una chiave: true Include tutte le chiavi dello stesso nym. false. |
key_id | String di ricerca (Query) | non | Con una richiesta firmata, o con all_keys=trueSolo 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);
Firmare le richieste di conto
Creare, cambiare e revocare le chiavi, il riassunto dell'account e i top-up automatici non prendono una chiave API. Prendono una firma dal tuo nym, in modo che una chiave fuoriuscita possa spendere fino alla sua copertura ma non può mai fare un'altra chiave o aumentare la propria copertura.
L’app fa questo per te: tutto nel suo Fuoco Sheet utilizza questi endpoint. Hai solo bisogno di questa sezione per gestire le chiavi dal tuo codice.
La firma è un evento Nostr del tipo 27235 (NIP-98), inviato base64-codificato nel
Authorization Header con la parola Nostr Di fronte a:
L’evento
{
"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è l'URL completa della richiesta, la stringa di query inclusa, esattamente come inviata.methodQuesto è il metodo HTTP.payloadè il SHA-256 del corpo della richiesta cruda, in es.POSTEPATCH, e il corpo che hai inviato deve essere byte per byte quello che hai hashato.created_atdeve essere entro 60 secondi dall'orologio del server.- Ogni evento funziona una volta, a
GETinclusa, in modo che un intestazione catturata non possa essere riprodotta. Scrivi una nuova per ogni richiesta. Aggiungi unnoncetag con un valore casuale in modo che due richieste firmate nello stesso secondo siano ancora diverse. - Il corpo di una richiesta firmata può essere massimo 64 KB, e un corpo ha bisogno
Content-Type: application/json.
Un evento mancante ritorna 401 missing_nostr_authUno che è male formato, male firmato, troppo vecchio, o per un URL, metodo o corpo diverso restituisce
invalid_nostr_auth, con la ragione nel messaggio; un riutilizzato uno ritorna
nostr_auth_replayedLa firma viene controllata prima di leggere il corpo, e ogni indirizzo può fallire 30 volte al minuto (un indirizzo IPv6 conta come il suo intero /64); dopo di che ottiene 429 con
Retry-After.
I browser possono chiamare questi endpoint solo dai siti propri di Nymbot (https://nymbot.ai,
https://nymchat.app Una pagina su qualsiasi altro sito non riceve alcun titolo CORS indietro, quindi non può leggere ciò che restituiscono.
OriginNon sono colpiti.
La firma ha bisogno della chiave segreta del tuo nim (la nsec), che controlla tutto: la tua identità, la tua storia e il tuo saldo. Solo metterlo in uno script su una macchina di cui ti fidi, leggerlo dall'ambiente piuttosto che scriverlo nel file, e preferire l'app quando puoi.
Questi aiutanti costruiscono l'intestazione. Gli esempi successivi su questa pagina li usano. Leggono la chiave segreta in esatto da NOSTR_SECRET_HEX; il cURL uno utilizza il
Il Nak strumento di riga di comando, che prende una chiave nsec o hex, e 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");
}
Il riassunto del conto
Quello che la scheda API dell'app mostra in alto: la chiave pubblica, entrambi gli equilibri, quante chiavi sono attive (non revocate o scadute) e la Top-up automatico delle impostazioni, o
null quando il server non li offre.
GET https://nymbot.ai/api/v1/account ha bisogno di a La richiesta firmata.
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 delle chiavi
Gli endpoint dietro l'elenco chiave dell'app. Tutti hanno bisogno di un La richiesta firmataOgni chiave viene restituita in questa forma, con tempi in ISO 8601 e somme in sats:
oggetto chiave
{
"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 È sufficiente riconoscere una chiave ma non utilizzarla.La chiave stessa viene restituita solo una volta, quando viene fatta.
Elenco chiavi
GET https://nymbot.ai/api/v1/keys – ha firmato
| campo | Tipo | richiesto | Descrizione |
|---|---|---|---|
include_revoked | Il booleano (query) | non | Include le chiavi revocate. default 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);
Fare una chiave
POST https://nymbot.ai/api/v1/keys Ritorno - Ritorno 201.
| campo | Tipo | richiesto | Descrizione |
|---|---|---|---|
name | Stringa | SÌ | 1 a 40 caratteri, diversi dalle tue altre chiavi attive (ignorando il caso). |
limit_sats | integrale | non | Il capitale di spesa in sats, almeno 1. lasciarlo fuori per nessun capitale. |
reset_period | Stringa | non | daily, weekly o monthlyNecessità limit_satsLasciatelo fuori per un cappello che non si ripristina mai. |
expire_at | String o integer | non | Quando la chiave smette di funzionare: un tempo ISO 8601, o millisecondi dal 1970. |
La 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"
}
}
| Statuto | Quando |
|---|---|
400 | Un nome mancante o troppo lungo; un nome già in uso (duplicate_name); un capo che non è un numero intero di almeno 1; un periodo di ripristino senza capo; una scadenza nel passato; un campo sconosciuto (unknown_parameter); o 25 chiavi attive già (too_many_keys). |
429 | Più di 60 chiavi realizzate da questo nym, o 120 da questo indirizzo, in un'ora (rate_limit_exceeded, con 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);
Leggere una chiave
GET https://nymbot.ai/api/v1/keys/{id} – ha firmato
Ritorno {"data": {…}} con l'oggetto chiave, o 404
key_not_found se nessuna delle tue chiavi ha questo 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);
Cambiare la chiave
PATCH https://nymbot.ai/api/v1/keys/{id} – ha firmato
Invia uno qualsiasi name, limit_sats, reset_period E
expire_atCon le stesse regole che si applicano quando si ottiene una chiave. null Chiude un campo: nessun capo, nessuna resetta, nessuna scadenza. Cambiare il periodo di resetta inizia un nuovo periodo a zero. Una chiave revocata non può essere cambiata (400 key_revokedIl ritorno
{"data": {…}} con l'oggetto chiave aggiornato.
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);
Rimuovere una chiave
DELETE https://nymbot.ai/api/v1/keys/{id} – ha firmato
Ferma la chiave immediatamente, per bene. rimane nell'elenco con revoked_at si trova e può essere visto con include_revoked=trueRevocare una chiave che è già revocata risponde allo stesso modo. Solo le 50 chiavi revocate più recenti vengono mantenute; quelle più vecchie vengono cancellate quando viene revocata un'altra chiave.
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());
NWC Auto-top-up di nuova generazione
Collegare un portafoglio Lightning con Nostr Wallet Connect e Nymbot si aggiunge un saldo da solo quando le spese di API lo eseguono a basso. La richiesta firmata.
Come funziona: dopo che una richiesta API viene addebitata al saldo che hai scelto di guardare, se quel saldo è caduto al di sotto della tua soglia, Nymbot fa una fattura per il tuo importo top-up, chiede al tuo portafoglio di pagare, e aggiunge il credito. Si accumula al massimo una volta ogni 5 minuti per ogni nym e saldo, quindi una rottura di richieste non può drenare il portafoglio. Spenderlo nelle app non lo attiva. Il tempo e la dimensione dell'ultimo top-up, e l'ultimo errore, sono nelle impostazioni; se un pagamento è passato dopo un errore, controllare la sua fattura con Lo status top-up credito di esso.
Una stringa di connessione consente a chiunque la detenga di chiedere al tuo portafoglio di pagare. Nymbot lo memorizza crittografato e lo usa solo per pagare le proprie fatture, ma fa una connessione solo per questo, con un budget di spesa nel tuo portafoglio, quindi il più che potrebbe mai pagare è un numero che hai scelto. pay_invoice.
Connettere un portafoglio
POST https://nymbot.ai/api/v1/nwc-auto-topup/connect – ha firmato
| campo | Tipo | richiesto | Descrizione |
|---|---|---|---|
nwc_url | Stringa | SÌ | La stringa di connessione, che inizia nostr+walletconnect://Nymbot chiede il portafoglio per get_info prima di salvarlo, e lo memorizza crittografato. |
threshold_sats | integrale | SÌ | Top up quando il saldo scende al di sotto di questi tanti sats. |
topup_sats | integrale | SÌ | Quanto aggiungere ogni volta. da 1.000 a 1.000.000 sats. |
tier | Stringa | non | pro (il default) o standard: l'equilibrio da guardare e top up. |
Risposta
{
"data": {
"connected": true,
"threshold_sats": 5000,
"topup_sats": 20000,
"tier": "pro",
"last_topup_at": null,
"last_topup_sats": null,
"last_error": null
}
}
| Statuto | Quando |
|---|---|
400 | Non è una stringa di connessione (invalid_nwc_url(il portafoglio non ha risposto al suo relay)nwc_unreachable) o ha rifiutato il controllo (nwc_rejected(il collegamento non può pagare le fatture)nwc_missing_permissiono un importo al di fuori dei limiti. |
501 | I top-up automatici non sono attivati per questo server (nwc_unavailableLo stesso vale per gli altri due terminali. |
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());
Leggere le impostazioni
GET https://nymbot.ai/api/v1/nwc-auto-topup – ha firmato
Ritorna lo stesso oggetto che si connette, con connected: false e gli altri campi
null quando nessun portafoglio è collegato. La stringa di connessione stessa non viene mai restituita.
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());
disconnessione
DELETE https://nymbot.ai/api/v1/nwc-auto-topup/connection – ha firmato
Elimina la stringa di connessione memorizzata. Non vengono realizzati ulteriori top-up. Per essere sicuri, puoi anche revocare la connessione nel tuo portafoglio.
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());
Pagamento su richiesta senza chiave
Gli endpoint a prezzo fisso possono essere pagati per una richiesta alla volta su Lightning, senza chiave, senza conto e senza saldo: POST /images/generations, POST /images/edits,
POST /videos, POST /audio/speech, POST /audio/transcriptions,
POST /audio/translations E POST /embeddingsChat, Risposte e Messaggi hanno sempre bisogno di una chiave. Una richiesta che trasporta una chiave viene fatturata al saldo come al solito; il flusso di pagamento inizia solo quando non viene inviata alcuna chiave.
Nymbot parla di due versioni della stessa idea, da un backend: Lightning Labs'
Il 402 (anche accettato sotto il suo vecchio nome, LSATIl progetto IETF
Pagamento Il sistema di autenticazione HTTP con lightning Metodo e
charge Utilizza ciò che il tuo cliente capisce.
Pagare senza una chiave è su solo quando API_L402_SECRET contiene almeno 32 byte casuali, come hex (64 caratteri) o base64 (44). openssl rand -hex 32Un valore più breve o indovinabile spegne la funzione e registra il perché. API_L402_SECRET_PREVIOUS Per un giorno: le credenziali, gli URL di stato e le sfide fatte sotto di esso continuano a funzionare fino a quando non scadono.
La sfida
Invia la richiesta con no Authorization se è valido, nulla viene eseguito, e si ottiene 402 Payment Required con una fattura per esattamente quello che costerebbe la richiesta: lo stesso prezzo che una chiave pagherebbe, convertito a 10 sats un credito standard o 100 sats un credito Pro e arrotondato fino a un intero sat (almeno 1 sat, e almeno il minimo di credito 0,05). WWW-Authenticate Risultati per la stessa 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 }
}
Il pagamento request Il parametro è base64url JSON:
{"amount":"237","currency":"sat","methodDetails":{"invoice":"lnbc...","network":"mainnet","paymentHash":"..."}}.
Una sfida è legata al punto finale, alla Content-Type (il suo tipo di media e, per il multipart, il suo limite) e al SHA-256 del corpo esatto dei byte che hai inviato, e dura 15 minuti. identici Richiesta di nuovo: lo stesso
Content-Type e gli stessi byte JSON, o per i multipart endpoint (/images/edits, /audio/transcriptions, /audio/translationsLa maggior parte delle biblioteche HTTP sceglie un nuovo limite ogni volta che codifica un modulo, quindi codifica una volta e invia quei byte due volte.
Ogni indirizzo può richiedere 30 sfide al minuto (un indirizzo IPv6 conta come il suo intero /64). Le richieste il cui indirizzo non è noto condividono un 10 più rigido al minuto, e c'è un limite generale sui problemi Nymbot sfide in tutti gli indirizzi; una richiesta rifiutata prima di una sfida è fatta (ad esempio con un corpo che non è valido JSON) non conta verso di esso. 429 con Retry-AfterGli endpoint pagati chiamati senza chiave o credenziali contano anche verso il limite generale di 120 richieste non autenticate al minuto per indirizzo. Content-Type non è application/json (o di
multipart/form-data per il caricamento) è rifiutato con 415 Non riceve mai una fattura.
Gli embeddings sono valutati a partire da una stima dei token nell'ingresso, con un margine di 1,5 volte, poiché il conteggio reale è conosciuto solo dopo. Rimborso token.
Invia il pagamento
Pagare la fattura con qualsiasi portafoglio Lightning. Il portafoglio ti dà la preimmagine, 64 caratteri esemplari. Poi inviare la stessa richiesta con uno di questi:
| schema | Il Header |
|---|---|
| Il 402 | Authorization: L402 <macaroon>:<preimage> (LSAT Funziona anche |
| Pagamento | Authorization: Payment <base64url JSON>Dove si trova JSON {"challenge": {every parameter of the challenge, as sent}, "payload": {"preimage": "<hex>"}} |
Una richiesta a pagamento risponde esattamente come quella fatta con una chiave, tranne che la nymbot
L'oggetto non ha campi di equilibrio: {"payment": "l402", "tier": "pro", "paid_sats": 237,
"charged_sats": 237}E non c’è nessun X-Nymbot-Balance-Sats una richiesta pagata con lo schema di pagamento riceve anche un Payment-Receipt Header (base64url JSON con la sfida id, il hash di pagamento come reference, status E
timestampLe richieste pagate non sono legate a nessun nim, quindi non appaiono nella cronologia delle query.
| Statuto | Quando |
|---|---|
402 payment_already_used | Ogni pagamento paga per una richiesta.La risposta è una nuova sfida per questa richiesta, quindi un cliente che cache la sua ultima credenziale (come lnget Insomma: semplicemente di nuovo. |
402 payment_mismatch | La credenziale è stata rilasciata per un altro endpoint, Content-Type Una nuova sfida per questa richiesta viene con esso; se il pagamento è stato troppo piccolo, ciò che hai pagato ritorna come un Rimborso token (refund_token E refund_sats nel nostro corpo). |
402 payment_expired | Più di 15 minuti sono passati dalla sfida. Una nuova sfida viene con essa. Se la preimmagine mostra che hai pagato, ciò che hai pagato ritorna come un Rimborso token (refund_token E refund_sats nel corpo), una volta; la credenziale viene poi usata. |
401 invalid_preimage | La preimmagine non hash al hash di pagamento della fattura. Il pagamento non viene utilizzato. |
401 invalid_payment_credential | La credenziale è malformata, è stata cambiata dopo che Nymbot l'ha rilasciata, o nomi un hash di pagamento per cui Nymbot non ha mai rilasciato una fattura. |
429 rate_limit_exceeded | Più di 30 credenziali o chiavi che non sono riusciti a verificare sono arrivati da questo indirizzo in un minuto, o un token di rimborso è stato inviato più di 60 volte in un minuto. 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"));
I clienti sono costruiti su mppx con un metodo Lightning gestire la sfida di pagamento stessi; puntare loro al punto finale e lasciarli pagare.
Il video
e pagato POST /videos risposte 202 come una chiave, più a
status_url: GET È firmato e funziona per 24 ore, purché il lavoro sia mantenuto.
{
"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..."
}
}
Mantenere il refund_token di questa risposta: viene mostrato solo qui. È vuoto mentre il video riporta (GET /api/v1/l402/refunds risposte "status": "pending"); se il rendimento non viene rilasciato, il pagamento atterra su di esso.
refund_sats per un lavoro rimborsato ma mai il token, quindi la condivisione dell'URL di stato non condivide il rimborso.
Rimborsi
Se una richiesta a pagamento fallisce e il fornitore ha addebitato Nymbot per il tentativo, il pagamento viene mantenuto e l'errore lo dice, con charged_sats, esattamente come per una richiesta chiave. Se fallisce senza essere fatturato, l'errore comporta un Rimborso token Vale quello che hai pagato:
{
"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"
}
}
Le parti inutilizzate vengono restituite allo stesso modo: se hai chiesto due foto e una non è stata rilasciata, la risposta di successo nymbot l'oggetto porta un token di rimborso per quello mancante; una trascrizione la cui lunghezza non poteva essere letta in anticipo è valutata per il più lungo il file poteva essere (mai più di 30 minuti), e la differenza alla lunghezza reale viene restituita come un token di rimborso; se si scopre di essere più lungo di 30 minuti, viene rifiutata con 413
e l'intero pagamento viene restituito. gli embeddings restituiscono ciò che la stima ha trattenuto. un video fallito restituisce il token restituito dalla sua presentazione.
Un token di rimborso è un codice casuale a 256 bit. Nymbot memorizza solo il suo hash e scade dopo 30 giorni.
- Pagare con esso. inviare
Authorization: Bearer REFUND-…In ogni caso, il prezzo è quello che si deve pagare (e il prezzo è quello che si deve pagare) e il prezzo è quello che si deve pagare (refund_token_satsIn quellanymbotUn token vale meno delle risposte alla richiesta402refund_insufficientUn fallimento non fatturato mette la sats di nuovo sullo stesso token. - Verificare il
GET /api/v1/l402/refundscon lo stesso header ritorna{"sats": 237, "status": "open", "expires_at": "..."}. - Un token può essere utilizzato fino a 60 volte al minuto.
- Spostarlo su un nym. Inserisci in Riprendere un regalo nell'app Nymbot, o chiamare
POST /api/v1/l402/refunds/redeemcon a La richiesta firmata E{"refund_token": "REFUND-...", "balance": "standard"}(o di"pro"Tutti i crediti vanno al saldo (10 rate ciascuno su standard, 100 su Pro); rate che non fanno un intero credito rimanere sul token per le 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 } }