Passa al contenuto
Torna a Nymbot

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.

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"
  }
}
StatutoQuando
401La 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.

campoTiporichiestoDescrizione
amountNumeroSÌQuanto, in currencyUn numero intero per scommessa.
currencyStringanonSATS e il default, USD o BTCI dollari sono convertiti al prezzo corrente di Bitcoin.
tierStringanonpro (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"
}
StatutoQuando
400Un 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).
429Più di 60 fatture per questo nym, o 120 da questo indirizzo, in un'ora (rate_limit_exceeded, con Retry-After).
502Nessuna 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
}
StatutoQuando
400L'id non è l'id a 64 caratteri della chiamata creata.
404Nessuna 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.

campoTiporichiestoDescrizione
pageIntegrazione (query)nonImpostazione predefinita 1, al massimo 1000; una pagina più alta è 400 invalid_value.
page_countIntegrazione (query)nonRane per pagina. Impostazione predefinita 20, al massimo 100.
start_date
end_date
String di ricerca (Query)nonISO 8601 Date o orari.
modelString di ricerca (Query)nonSolo questo modello.
typeString di ricerca (Query)nonchat, responses, messages, image, video, speech, transcription o embedding.
all_keysIl booleano (query)nonCon una chiave: true Include tutte le chiavi dello stesso nym. false.
key_idString di ricerca (Query)nonCon 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.
  • method Questo è il metodo HTTP.
  • payload è il SHA-256 del corpo della richiesta cruda, in es. POST E PATCH, e il corpo che hai inviato deve essere byte per byte quello che hai hashato.
  • created_at deve essere entro 60 secondi dall'orologio del server.
  • Ogni evento funziona una volta, a GET inclusa, in modo che un intestazione catturata non possa essere riprodotta. Scrivi una nuova per ogni richiesta. Aggiungi un nonce tag 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 chiave segreta

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

campoTiporichiestoDescrizione
include_revokedIl booleano (query)nonInclude 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.

campoTiporichiestoDescrizione
nameStringaSÌ1 a 40 caratteri, diversi dalle tue altre chiavi attive (ignorando il caso).
limit_satsintegralenonIl capitale di spesa in sats, almeno 1. lasciarlo fuori per nessun capitale.
reset_periodStringanondaily, weekly o monthlyNecessità limit_satsLasciatelo fuori per un cappello che non si ripristina mai.
expire_atString o integernonQuando 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"
  }
}
StatutoQuando
400Un 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).
429Più 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.

Prima di collegare un portafoglio

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

campoTiporichiestoDescrizione
nwc_urlStringaSÌLa stringa di connessione, che inizia nostr+walletconnect://Nymbot chiede il portafoglio per get_info prima di salvarlo, e lo memorizza crittografato.
threshold_satsintegraleSÌTop up quando il saldo scende al di sotto di questi tanti sats.
topup_satsintegraleSÌQuanto aggiungere ogni volta. da 1.000 a 1.000.000 sats.
tierStringanonpro (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
  }
}
StatutoQuando
400Non è 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.
501I 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.

Eseguire il proprio server

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:

schemaIl Header
Il 402Authorization: L402 <macaroon>:<preimage> (LSAT Funziona anche
PagamentoAuthorization: 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.

StatutoQuando
402 payment_already_usedOgni 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_mismatchLa 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_expiredPiù 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_preimageLa preimmagine non hash al hash di pagamento della fattura. Il pagamento non viene utilizzato.
401 invalid_payment_credentialLa 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_exceededPiù 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_sats In quella nymbot Un token vale meno delle risposte alla richiesta 402 refund_insufficientUn fallimento non fatturato mette la sats di nuovo sullo stesso token.
  • Verificare il GET /api/v1/l402/refunds con 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/redeem con 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 } }