Saltar al contingut
Torna cap a Nymbot

Base de coneixement Desenvolupadors

Balanç, top-ups i claus

Comproveu el que teniu, sobrepasseu Lightning, sobrepasseu automàticament des de la vostra cartera, vegeu el cost de cada sol·licitud i gestioneu les claus des del codi.

Comprovar l'equilibri

Tant de les teves balances, com de la quantitat que s'utilitza la tapa d'aquesta clau.

GET https://nymbot.ai/api/v1/credits/balance Necessita una clau de foc. POST I també per als clients que s’ho esperen.

balance és els dos saldos junts en dòlars al preu actual de Bitcoin, per a eines que esperen un únic nombre (null La resta és en crèdits i sats, que és com es mantenen els saldos. key Una clau que ha arribat al seu cap encara pot comprovar el saldo.

Resposta

{
  "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"
  }
}
EstatutQuan
401La clau és desapareguda, desconeguda, revocada o caducada.

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);

Mètodes de pagament

Com es pot pujar, i els límits. llamp és l'únic mètode.

GET https://nymbot.ai/api/v1/topup/payment-methods Cap clau necessària.

Un top-up és de 10 a 1.000.000 sats; un top-up Pro ha de comprar almenys un crèdit Pro, de manera que comença a 100 sats. bulk_bonus enumera el crèdit addicional en els top-ups més grans, el mateix que en l'aplicació: 10%, 15% o 20% més en els top-ups estàndard de 500, 1.000 o 5.000 sats, i en els top-ups Pro de 5.000, 10.000 o 50.000 sats.

Resposta

{
  "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);

Pujant sobre el llamp

Crea una factura de llamp que afegeix crèdit a la memòria a la qual pertany la clau. comprovació Per tenir el crèdit afegit.

POST https://nymbot.ai/api/v1/topup/create/btc-lightning Necessita una clau de foc.

El campTipusNecessitatDescripció
amountNúmeroSíQuants, en currencyUn nombre sencer per a l’aposta.
currencyLíniaNoSATS (en el seu defecte) USD o BTCEls dòlars es converteixen al preu actual de Bitcoin.
tierLíniaNopro (el defecte) o standard: a quin balanç va el crèdit.

Un crèdit estàndard és 10 sats i un crèdit Pro 100 sats, més qualsevol bonificació a granel; credits Diu el que afegirà aquesta factura.

Resposta

{
  "invoice_id": "b7d41e0c95a2f38e6c1d0e9a4b7f2c61d3e8a05f9b2c4d7e1a6f3b8c0d5e2a9f4",
  "payment_request": "lnbc100u1p5...",
  "amount_sats": 10000,
  "credits": 115,
  "tier": "pro",
  "expires_at": "2026-09-30T09:27:00Z",
  "status": "pending"
}
EstatutQuan
400Un altre mètode en el camí (unsupported_method), una moneda desconeguda (unsupported_currency) o nivell, una quantitat mancada, o una quantitat inferior al mínim (amount_too_small, per sobre de 1.000.000 de tasses (amount_too_large) o rebutjat per la cartera Lightning (amount_out_of_range).
429Més de 60 factures per a aquest nim, o 120 des d'aquesta adreça, en una hora (rate_limit_exceeded, amb Retry-After).
502No es pot fer cap factura en aquest moment (invoice_unavailable, amb 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);

Comprovar el top-up

Pregunta si la factura s'ha pagat i, una vegada que hi hagi, afegeix el crèdit. creditedComprovar de nou després és segur: el crèdit aterra una vegada, no importa quantes vegades es demani.

GET https://nymbot.ai/api/v1/topup/status/{invoice_id} necessita una clau de la nim que va fer la factura.

status És pending (Encara no s’ha pagat el preu) paid (Pagat, però encara no acreditat; tornar a comprovar), credited (en el seu balanç) o expired (no pagat en el temps).Els camps de balanç són per al nivell que arriba la factura.

Resposta

{
  "invoice_id": "b7d41e0c95a2f38e6c1d0e9a4b7f2c61d3e8a05f9b2c4d7e1a6f3b8c0d5e2a9f4",
  "status": "credited",
  "amount_sats": 10000,
  "credits": 115,
  "tier": "pro",
  "expires_at": null,
  "balance_credits": 523.33,
  "balance_sats": 52333
}
EstatutQuan
400L'id no és l'id de 64 caràcters de la crida de creació.
404No hi ha cap factura per aquest id per al teu nim (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);

Volem història

Una fila per sol·licitud: què era, quin model, quants tokens i què costa. No es conserven promptes ni respostes, de manera que no es retorna cap. Les files es conserven durant 90 dies, primer el més nou. Una clau que ha arribat al seu límit encara pot llegir el seu historial.

GET https://nymbot.ai/api/v1/queries/history necessita una clau d'API, que veu les seves pròpies sol·licituds, o una Sol·licitud signada de la teva nim, que veu cada clau.

El campTipusNecessitatDescripció
pageTotes les preguntes (query)NoPer defecte 1, amb un màxim de 1.000; una pàgina més alta és 400 invalid_value.
page_countTotes les preguntes (query)NoRams per pàgina. Default 20, a un màxim de 100.
start_date
end_date
Càlcul (query)NoISO 8601 Dates i hores.
modelCàlcul (query)NoNomés aquest model.
typeCàlcul (query)Nochat, responses, messages, image, video, speech, transcription o embedding.
all_keysbooleà (query)NoAmb una clau: true inclou totes les claus del mateix nym. false.
key_idCàlcul (query)NoAmb una sol·licitud signada, o amb all_keys=trueNomés aquesta clau.

Resposta

{
  "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);

Signatura de sol·licituds de compte

La creació, canvi i revocació de claus, el resum del compte i els top-ups automàtics no prenen una clau de l'API. Prenen una signatura del seu nim, de manera que una clau perduda pot gastar fins al seu cap, però mai pot fer una altra clau o augmentar la seva pròpia cap.

L'aplicació fa això per a vostè: tot en la seva El foc El full utilitza aquests punts finals. Només necessites aquesta secció per gestionar les claus del teu propi codi.

La signatura és un esdeveniment Nostr del tipus 27235 (NIP-98), enviat base64-codificat en el Authorization Header amb la paraula Nostr En el davant:

L’esdeveniment

{
  "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 és l'URL completa de la sol·licitud, la cadena de consulta inclosa, exactament com s'ha enviat.
  • method És el mètode HTTP.
  • payload és el SHA-256 del cos de la sol·licitud crua, en hexà. POST i PATCH, i el cos que envieu ha de ser byte per byte el que heu hashat.
  • created_at ha de ser dins de 60 segons de l'horari del servidor.
  • Cada esdeveniment funciona una vegada, a GET inclòs, de manera que un encapçalament capturat no es pugui reproduir. Signar un nou per a cada sol·licitud. nonce etiquetar amb un valor aleatori de manera que dues sol·licituds signades en el mateix segon encara siguin diferents.
  • El cos d'una sol·licitud signada pot tenir un màxim de 64 KB, i un cos necessita Content-Type: application/json.

Un esdeveniment perdut torna 401 missing_nostr_authUna que està mal format, mal signat, massa vell, o per a una URL diferent, mètode o cos retorna invalid_nostr_auth, amb la raó en el missatge; un reutilitzat un torna nostr_auth_replayedLa signatura es comprova abans que es llegeixi el cos, i cada adreça pot fallar-la 30 vegades per minut (una adreça IPv6 compta com el seu conjunt /64); després d'això, obté el 429 amb Retry-After.

Els navegadors poden trucar a aquests terminis només des dels llocs propis de Nymbot (https://nymbot.ai, https://nymchat.app Una pàgina en qualsevol altre lloc no rep cap encapçalament CORS, de manera que no pot llegir el que torna. OriginNo estan afectats.

La teva clau secreta

La signatura necessita la clau secreta del teu nim (la clau nsec), que controla tot: la teva identitat, el teu historial i el teu balanç. Només posar-ho en un guió en una màquina de confiança, llegir-ho des de l'entorn en comptes d'escriure'l al fitxer, i preferir l'aplicació quan puguis.

Aquests ajudants construeixen l'encapçalament. Els exemples posteriors d'aquesta pàgina els utilitzen. NOSTR_SECRET_HEX· El cURL utilitza el Nàstic eina de línia de comandes, que pren una clau nsec o hexagonal, i sha256sum En el 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");
}

Resum del compte

El que mostra la fitxa de l'API de l'aplicació a la part superior: la teva clau pública, ambdues balances, quantes claus estan actives (no revocades o expirades), i la Top-up automàtic Instal·lacions o null quan el servidor no els ofereix.

GET https://nymbot.ai/api/v1/account Necessitem a Sol·licitud signada.

Resposta

{
  "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());

Gestió de claus

Els punts finals darrere de la llista de claus de l'aplicació. Tots ells necessiten una Sol·licitud signadaCada clau es retorna en aquest formulari, amb temps en ISO 8601 i quantitats en sats:

Objecte clau

{
  "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 És suficient per reconèixer una clau però no per utilitzar-la.La clau mateixa només es retorna una vegada, quan es fa.

Llista de claus

GET https://nymbot.ai/api/v1/keys Es va signar.

El campTipusNecessitatDescripció
include_revokedbooleà (query)NoInclou les claus revocades. Default false.

Resposta

{ "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);

Fer una clau

POST https://nymbot.ai/api/v1/keys Inscripcions - Retorn 201.

El campTipusNecessitatDescripció
nameLíniaSí1 a 40 caràcters, diferents de les altres claus actives (ignorar el cas).
limit_satsíntegreNoEl cap de despesa en sats, almenys 1.
reset_periodLíniaNodaily, weekly o monthlyNecessitats limit_satsDeixeu-ho fora per a una tapa que mai es restableix.
expire_atLínia o íntegreNoQuan la clau deixa de funcionar: un temps ISO 8601, o mil·lisegons des de 1970.

La resposta (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"
  }
}
EstatutQuan
400Un nom perdut o massa llarg; un nom ja en ús (duplicate_name); un cap que no és un nombre sencer d'almenys 1; un període de restauració sense cap; una expiració en el passat; un camp desconegut (unknown_parameter); o 25 claus actives ja (too_many_keys).
429Més de 60 claus fetes per aquest nim, o 120 d'aquesta adreça, en una hora (rate_limit_exceeded, amb 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);

Llegir una clau

GET https://nymbot.ai/api/v1/keys/{id} Es va signar.

Retorns {"data": {…}} amb l’objecte clau, o 404 key_not_found si cap de les teves claus té aquesta identificació.

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);

Canviar la clau

PATCH https://nymbot.ai/api/v1/keys/{id} Es va signar.

Envia qualsevol name, limit_sats, reset_period i expire_at, amb les mateixes regles que quan es fa una clau. null No hi ha cap límit, no hi ha cap reset, no hi ha expiració. Canviar el període de reset comença un nou període a zero.400 key_revoked• Retorns {"data": {…}} amb l'objecte clau actualitzat.

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);

Revocació de la clau

DELETE https://nymbot.ai/api/v1/keys/{id} Es va signar.

Es tanca la clau de seguida, per bé. Es queda a la llista amb revoked_at es troba, i es pot veure amb include_revoked=trueRevocar una clau que ja ha estat revocada respon de la mateixa manera. Només es conserven les últimes 50 claus revocades; les més antigues s'esborren quan es revoca una altra clau.

Resposta

{ "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());

El nou NWC Auto-top-up

Connecta una cartera de llamp amb Nostr Wallet Connect i Nymbot arriba a un saldo per si mateix quan la despesa de l'API s'executa baix. Sol·licitud signada.

Com funciona: després que una sol·licitud d'API es carregui al saldo que has triat per veure, si aquest saldo ha caigut per sota del teu llindar, Nymbot fa una factura per la teva quantitat superior, demana a la teva cartera que la pagui, i afegeix el crèdit. S'acumula al màxim una vegada cada 5 minuts per a cada nim i saldo, de manera que una explosió de sol·licituds no pot esborrar la cartera. La despesa en les aplicacions no la desencadena. El temps i la mida de l'últim top-up, i l'últim error, estan en la configuració; si un pagament va passar després d'un error, comproveu la seva factura amb Situació top-up El crèdit.

Abans de connectar una cartera

Nymbot l'emmagatzema encriptat i només l'utilitza per pagar les seves pròpies factures, però fa una connexió només per això, amb un pressupost de despesa a la seva cartera, de manera que el més que mai podria pagar és un nombre que vostè va triar. pay_invoice.

Connectar una cartera

POST https://nymbot.ai/api/v1/nwc-auto-topup/connect Es va signar.

El campTipusNecessitatDescripció
nwc_urlLíniaSíLa cadena de connexió, que comença nostr+walletconnect://Nymbot demana la cartera per get_info abans de guardar-lo i emmagatzemar-lo encriptat.
threshold_satsíntegreSíSupera quan el saldo cau per sota d'aquests molts sats. almenys 1.000.
topup_satsíntegreSíQuant s'ha d'afegir cada vegada. 1.000 a 1.000.000 apostes.
tierLíniaNopro (el defecte) o standard: l'equilibri per mirar i pujar.

Resposta

{
  "data": {
    "connected": true,
    "threshold_sats": 5000,
    "topup_sats": 20000,
    "tier": "pro",
    "last_topup_at": null,
    "last_topup_sats": null,
    "last_error": null
  }
}
EstatutQuan
400No hi ha una cadena de connexió (invalid_nwc_url); la cartera no va respondre sobre el seu relleu (nwc_unreachable) o va rebutjar el control (nwc_rejected); la connexió no pot pagar les factures (nwc_missing_permissiono una quantitat fora dels límits.
501No s'ha activat el top-up automàtic per a aquest servidor (nwc_unavailableEl mateix passa amb els altres dos terminis.

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());

Llegir les configuracions

GET https://nymbot.ai/api/v1/nwc-auto-topup Es va signar.

Retorna el mateix objecte que la connexió, amb connected: false I els altres camps null Quan no hi ha cartera connectada, la cadena de connexió en si no es torna mai.

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());

Desconnexió

DELETE https://nymbot.ai/api/v1/nwc-auto-topup/connection Es va signar.

Elimina la cadena de connexió emmagatzemada. No es fan més top-ups. Per estar segur, també podeu revocar la connexió a la cartera.

Resposta

{ "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());

Pagament a petició sense clau

Els punts finals de preu fix es poden pagar per una sol·licitud alhora a través de Lightning, sense clau, sense compte i sense saldo: POST /images/generations, POST /images/edits, POST /videos, POST /audio/speech, POST /audio/transcriptions, POST /audio/translations i POST /embeddingsUna sol·licitud que porta una clau es factura al saldo com de costum; el flux de pagament només comença quan no s'envia cap clau.

Nymbot parla de dues versions de la mateixa idea, des d'un backend: Lightning Labs' El 402 (també acceptat sota el seu antic nom, LSAT) i el projecte IETF Pagament L’autenticació HTTP amb el lightning Mètode i charge Utilitza el que el teu client entengui.

Gestió del teu propi servidor

Pagar sense clau és en només quan API_L402_SECRET conté almenys 32 bytes aleatoris, com a hex (64 caràcters) o base64 (44). openssl rand -hex 32Un valor més curt o predictible desactiva la funció i registra per què. API_L402_SECRET_PREVIOUS durant un dia: les credencials, les URL d'estat i els reptes realitzats en virtut d'això continuen funcionant fins que expiren.

El repte

Envia la sol·licitud amb no Authorization si és vàlid, res no s'executa, i s'obté 402 Payment Required amb una factura per exactament el que costa aquesta sol·licitud: el mateix preu que pagaria una clau, convertit a 10 sats un crèdit estàndard o 100 sats un crèdit Pro i arrodonit fins a un sat sencer (almenys 1 sat, i almenys el mínim de crèdit 0,05). WWW-Authenticate Recomanacions per a la mateixa factura:

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 }
}

El pagament request El paràmetre és base64url JSON: {"amount":"237","currency":"sat","methodDetails":{"invoice":"lnbc...","network":"mainnet","paymentHash":"..."}}.

Un repte està lligat al punt final, al Content-Type (el seu tipus de mitjans i, per a múltiples parts, el seu límit) i al SHA-256 del cos exacte dels bytes que ha enviat, i dura 15 minuts. idèntica Reiteració: El mateix Content-Type i els mateixos bytes JSON, o per als punts finals multiparts (/images/edits, /audio/transcriptions, /audio/translationsLa majoria de biblioteques HTTP trien un nou límit cada vegada que codifiquen un formulari, així que el codifiquen una vegada i envien aquests bytes dues vegades.

Cada adreça pot demanar 30 reptes per minut (una adreça IPv6 compta com el seu conjunt /64). Les sol·licituds l'adreça de les quals no és coneguda comparteixen un 10 més estricte per minut, i hi ha un límit global en els reptes Nymbot problemes a través de totes les adreces; una sol·licitud rebutjada abans d'un desafiament es fa (per exemple, amb un cos que no és vàlid JSON) no compta cap a ella. 429 amb Retry-AfterEls terminis de pagament trucats sense clau o credencial també compten cap al límit general de 120 sol·licituds no autenticades per minut per adreça. Content-Type No és application/json (o el multipart/form-data per a subministraments) es refusa amb 415 Mai no obté una factura.

Els embeddings són preuats a partir d'una estimació dels tokens en l'entrada, amb un marge de 1,5 vegades, ja que el recompte real només es coneix després. Recuperació de token.

Enviar el pagament

Pagar la factura amb qualsevol cartera de llamp. La cartera li dóna la preimatge, 64 caràcters hexàtics. Llavors enviar la mateixa sol·licitud amb un d'aquests:

EsquemaHeader
El 402Authorization: L402 <macaroon>:<preimage> (LSAT També es treballa)
PagamentAuthorization: Payment <base64url JSON>On es troba el JSON {"challenge": {every parameter of the challenge, as sent}, "payload": {"preimage": "<hex>"}}

Una sol·licitud de pagament respon exactament com una sol·licitud feta amb una clau, excepte que la nymbot L'objecte no té camps d'equilibri: {"payment": "l402", "tier": "pro", "paid_sats": 237, "charged_sats": 237}I no hi ha X-Nymbot-Balance-Sats Una sol·licitud pagada amb l'esquema de pagament també rep una Payment-Receipt header (base64url JSON amb el desafiament id, el pagament hash com reference, status i timestampLes sol·licituds pagades no estan lligades a cap nim, de manera que no apareixen en l'historial de consultes.

EstatutQuan
402 payment_already_usedCada pagament paga per una sol·licitud.La resposta és un nou repte per a aquesta sol·licitud, de manera que un client que cache la seva última credencial (com lnget Només vol tornar a pujar.
402 payment_mismatchLa credencial es va emetre per a un altre punt final, Content-Type o cos, o paga menys del que aquesta sol·licitud ara costa. Un nou desafiament per a aquesta sol·licitud ve amb ell; si el pagament va ser massa petit, el que va pagar es torna com un Recuperació de token (refund_token i refund_sats en el cos)
402 payment_expiredMés de 15 minuts han passat des del repte. Un nou repte ve amb ell. Si la preimatge mostra que has pagat, el que has pagat torna com un repte. Recuperació de token (refund_token i refund_sats en el cos), una vegada; el credencial es fa servir.
401 invalid_preimageLa preimatge no hash a la hash de pagament de la factura. El pagament no s'utilitza.
401 invalid_payment_credentialLa credencial està mal formada, es va canviar després que Nymbot la va emetre, o el nom d'un hash de pagament que Nymbot mai no va emetre una factura.
429 rate_limit_exceededMés de 30 credencials o claus que no es van verificar van arribar d'aquesta adreça en un minut, o un token de reemborsament es va enviar més de 60 vegades en un minut. 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"));

Els clients es construeixen mppx amb un mètode de llamp gestionar el desafiament de pagament ells mateixos; assenyalar-los al punt final i deixar-los pagar.

Vídeo

El pagament POST /videos Respostes 202 com una clau, més a status_url: GET Es signa i funciona durant 24 hores, sempre que es mantingui la feina.

{
  "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..."
  }
}

Mantingui el refund_token A partir d'aquesta resposta: només es mostra aquí. Està buit mentre que el vídeoGET /api/v1/l402/refunds Respostes "status": "pending"); si el rendiment fracassa sense facturar, el pagament aterra en ell. refund_sats per a un treball reemborsat però mai el token, de manera que compartir la URL d'estat no comparteix el reemborsament.

Reemborsament

Si una sol·licitud de pagament fracassa i el proveïdor factura Nymbot per l'intent, el pagament es manté i l'error diu que ho és, amb charged_sats, exactament com per a una sol·licitud clau. Si fracassa sense ser facturat, l'error porta una Recuperació de token Val la pena el que hagis pagat:

{
  "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"
  }
}

Les parts no utilitzades tornen de la mateixa manera: si heu demanat dues imatges i una no ha estat facturada, la resposta d'èxit nymbot l'objecte porta un token de reemborsament per al que falta; una transcripció la longitud de la qual no es podia llegir abans es preu per el més llarg que podia ser el fitxer (mai més de 30 minuts), i la diferència a la longitud real es torna com un token de reemborsament; si resulta ser més llarg de 30 minuts, es nega amb 413 Els embeddings retornen el que l'estimació va mantenir. Un vídeo fallit reemborsarà el token que va retornar la seva presentació.

Un token de reemborsament és un codi aleatori de 256 bits. Nymbot només emmagatzema el seu hash, i expira després de 30 dies.

  • Pagueu amb això. Envia Authorization: Bearer REFUND-… En aquest cas, el preu de la targeta és el mateix que el preu de la targeta, i el preu és el mateix que el de la targeta (refund_token_sats En la nymbot Un token que val menys que les respostes a la sol·licitud 402 refund_insufficientUn fracàs no comptabilitzat posa la taxa de tornada en el mateix token.
  • Comproveu el GET /api/v1/l402/refunds amb el mateix header torna {"sats": 237, "status": "open", "expires_at": "..."}.
  • Un token es pot utilitzar un màxim de 60 vegades per minut.
  • Poseu-la en una nit. Posa’l en Recuperar un regal a l'aplicació Nymbot, o trucar POST /api/v1/l402/refunds/redeem Amb a Sol·licitud signada i {"refund_token": "REFUND-...", "balance": "standard"} (o el "pro"Els crèdits sencers van al saldo (10 sats cadascun en estàndard, 100 en Pro); les apostes que no fan que un crèdit sencer es quedi en el token per a les sol·licituds d'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"

Resposta

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