Saltar al contenido
Volver a Nymbot

base de conocimientos desarrolladores

Balance, top-ups y llaves

Compruebe lo que tiene, sobreponerse a Lightning, sobreponerse automáticamente desde su propia cartera, ver cuánto cuesta cada solicitud y gestionar las claves de código.

Comprobar el equilibrio

Ambos de sus saldos, y cuánto de la capa de esta clave se utiliza.

GET https://nymbot.ai/api/v1/credits/balance Necesita una llave de fuego. POST También para los clientes que lo esperan.

balance es los dos saldos juntos en dólares al precio actual de Bitcoin, para herramientas que esperan un único número (null El resto está en créditos y sats, que es como los saldos se mantienen realmente. key Una clave que ha alcanzado su límite todavía puede comprobar el saldo.

Respuesta

{
  "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"
  }
}
EstatusCuando
401La clave está desaparecida, desconocida, revocada o expirada.

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étodos de pago

Cómo se puede superar, y los límites. Lightning es el único método.

GET https://nymbot.ai/api/v1/topup/payment-methods No es necesaria la llave.

Un top-up es de 10 a 1.000.000 sats; un top-up Pro tiene que comprar al menos un crédito Pro, por lo que comienza en 100 sats. bulk_bonus enumera el crédito adicional en top-ups más grandes, lo mismo que en la aplicación: 10%, 15% o 20% más en top-ups estándar de 500, 1.000 o 5.000 sats, y en top-ups Pro de 5.000, 10.000 o 50.000 sats.

Respuesta

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

Subiendo por encima del rayo

Crea una factura de relámpago que agrega crédito a la nube a la que pertenece la clave. Comprobarlo Para tener el crédito añadido.

POST https://nymbot.ai/api/v1/topup/create/btc-lightning Necesita una llave de fuego.

CampoTipoRequiereDescripción
amountnúmeroSí¿Cuánto, en currencyUn número entero para la apuesta.
currencyStringNoSATS (en el caso del defecto), USD o BTCLos dólares se convierten al precio actual de Bitcoin.
tierStringNopro (el defecto) o standard: a qué balance va el crédito.

Un crédito estándar es 10 sats y un crédito Pro 100 sats, más cualquier bonificación en volumen; credits dice lo que esta factura añadirá.

Respuesta

{
  "invoice_id": "b7d41e0c95a2f38e6c1d0e9a4b7f2c61d3e8a05f9b2c4d7e1a6f3b8c0d5e2a9f4",
  "payment_request": "lnbc100u1p5...",
  "amount_sats": 10000,
  "credits": 115,
  "tier": "pro",
  "expires_at": "2026-09-30T09:27:00Z",
  "status": "pending"
}
EstatusCuando
400Un nuevo método en el camino (unsupported_method), una moneda desconocida (unsupported_currency) o nivel, una cantidad faltante, o una cantidad por debajo del mínimo (amount_too_small), por encima de 1.000.000 de tarifas (amount_too_large) o rechazado por la cartera Lightning (amount_out_of_range).
429Más de 60 facturas para este nym, o 120 desde esta dirección, en una hora (rate_limit_exceeded, con Retry-After).
502No se puede hacer factura en este 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);

Comprobar el top-up

Pregunta si la factura ha sido pagada y, una vez que la tiene, añade el crédito. creditedVerificar de nuevo después es seguro: el crédito aterriza una vez, no importa cuántas veces lo pidas.

GET https://nymbot.ai/api/v1/topup/status/{invoice_id} - Necesita una llave de la nim que hizo la factura.

status es pending (Aún no se ha pagado) paid (Pago, pero aún no acreditado; compruebe de nuevo), credited (en su balance) o expired (no pagado a tiempo).Los campos de balance son para el nivel en el que la factura se eleva.

Respuesta

{
  "invoice_id": "b7d41e0c95a2f38e6c1d0e9a4b7f2c61d3e8a05f9b2c4d7e1a6f3b8c0d5e2a9f4",
  "status": "credited",
  "amount_sats": 10000,
  "credits": 115,
  "tier": "pro",
  "expires_at": null,
  "balance_credits": 523.33,
  "balance_sats": 52333
}
EstatusCuando
400El id no es el id de 64 caracteres de la llamada creada.
404No hay factura por ese id para tu 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);

Queremos historia

Una fila por solicitud: qué era, qué modelo, cuántos tokens y cuánto costó. No se conservan prompts ni respuestas, por lo que no se devuelve ninguno. Las filas se conservan durante 90 días, lo más reciente primero. Una clave que ha alcanzado su límite todavía puede leer su historia.

GET https://nymbot.ai/api/v1/queries/history — necesita una clave de API, que ve sus propias solicitudes, o una Solicitud firmada de tu ninja, que ve cada llave.

CampoTipoRequiereDescripción
pageEs decir, el integer (queryNoPor defecto 1, con un máximo de 1000; una página más alta es 400 invalid_value.
page_countEs decir, el integer (queryNoRodas por página. 20 por defecto, con un máximo de 100.
start_date
end_date
Conexión (Query)NoISO 8601 Datas y horas.
modelConexión (Query)NoSólo este modelo.
typeConexión (Query)Nochat, responses, messages, image, video, speech, transcription o embedding.
all_keysBoolean (en español)NoCon una llave: true incluye todas las claves del mismo nym. Default false.
key_idConexión (Query)NoCon una solicitud firmada, o con all_keys=trueSólo esta llave.

Respuesta

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

Solicitud de firma de cuenta

La creación, el cambio y la revocación de claves, el resumen de la cuenta y los top-ups automáticos no toman una clave de API. Ellos toman una firma de su nym, por lo que una clave que se ha filtrado puede gastar hasta su límite, pero nunca puede hacer otra clave o elevar su propio límite.

La aplicación hace esto por usted: todo en su Fuego Sheet utiliza estos puntos finales. sólo necesita esta sección para administrar las claves de su propio código.

La firma es un evento Nostr del tipo 27235 (NIP-98), enviado base64-codificado en el Authorization Header con la palabra Nostr En el frente:

El 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 es la URL completa de la solicitud, la cadena de consulta incluida, exactamente como fue enviada.
  • method Es el método HTTP.
  • payload es el SHA-256 del cuerpo de la solicitud cruda, en hex. POST y PATCH, y el cuerpo que envía debe ser byte por byte el que ha hashado.
  • created_at Debe estar dentro de los 60 segundos del reloj del servidor.
  • Cada evento funciona una vez, a GET incluido, por lo que un encabezado capturado no puede ser reproducido. Asine un nuevo para cada solicitud. nonce etiqueta con un valor aleatorio para que dos solicitudes firmadas en el mismo segundo sigan siendo diferentes.
  • El cuerpo de una solicitud firmada puede ser de un máximo de 64 KB, y un cuerpo necesita Content-Type: application/json.

Un evento perdido regresa 401 missing_nostr_auth; uno que está mal formado, mal firmado, demasiado viejo, o para una URL, método o cuerpo diferente devuelve invalid_nostr_auth, con la razón en el mensaje; un reutilizado uno vuelve nostr_auth_replayedLa firma se verifica antes de que se lea el cuerpo, y cada dirección puede fallarlo 30 veces por minuto (una dirección IPv6 cuenta como su totalidad /64); después de eso se obtiene 429 con Retry-After.

Los navegadores pueden llamar a estos puntos finales sólo desde los sitios propios de Nymbot (https://nymbot.ai, https://nymchat.app Una página en cualquier otro sitio no recibe encabezados CORS de vuelta, por lo que no puede leer lo que devuelven. OriginNo están afectados.

Tu llave secreta

La firma necesita la llave secreta de tu nim (la nsec), que controla todo: su identidad, su historial y su balance. Solo coloque en un script en una máquina en la que confíe, lea desde el entorno en lugar de escribirlo en el archivo, y prefiere la aplicación cuando pueda.

Estos ayudantes construyen el encabezado. Los ejemplos posteriores en esta página los usan. NOSTR_SECRET_HEX; el cURL uno utiliza el Naco herramienta de línea de comandos, que toma una clave nsec o hex, y 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");
}

El resumen de la cuenta

Lo que muestra la hoja de la API de la aplicación en la parte superior: su clave pública, ambos saldos, cuántas claves están activas (no revocadas o expiradas), y la Top-up automático establecimientos, o null cuando el servidor no los ofrece.

GET https://nymbot.ai/api/v1/account Necesito a Solicitud firmada.

Respuesta

{
  "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ón de claves

Los puntos finales detrás de la lista de claves de la aplicación. Todos ellos necesitan una Solicitud firmadaCada clave se devuelve en este formulario, con tiempos en ISO 8601 y cantidades en sats:

Objeto clave

{
  "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 Es suficiente para reconocer una llave pero no para utilizarla.La misma llave sólo se devuelve una vez, cuando se hace.

Listado de claves

GET https://nymbot.ai/api/v1/keys y firmado.

CampoTipoRequiereDescripción
include_revokedBoolean (en español)NoIncluye las claves revocadas. false.

Respuesta

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

Hacer una llave

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

CampoTipoRequiereDescripción
nameStringSí1 a 40 caracteres, diferentes de sus otras claves activas (ignorar el caso).
limit_satsEntierroNoEl límite de gasto en sats, al menos 1.
reset_periodStringNodaily, weekly o monthlyNecesidades limit_satsDejarlo fuera para una tapa que nunca se restaura.
expire_atString o integerNoCuando la clave deja de funcionar: un tiempo ISO 8601, o milisegundos desde 1970.

La respuesta (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"
  }
}
EstatusCuando
400Un nombre perdido o demasiado largo; un nombre ya en uso (duplicate_name); un cap que no es un número entero de al menos 1; un período de restauración sin cap; una expiración en el pasado; un campo desconocido (unknown_parameter); o 25 claves activas ya (too_many_keys).
429Más de 60 llaves hechas por este nim, o 120 desde esta dirección, en una hora (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);

Leer una clave

GET https://nymbot.ai/api/v1/keys/{id} y firmado.

Regreso {"data": {…}} con el objeto clave, o 404 key_not_found si ninguna de tus claves tiene esa identificación.

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

Cambiar una llave

PATCH https://nymbot.ai/api/v1/keys/{id} y firmado.

Envíe cualquier name, limit_sats, reset_period y expire_at, con las mismas reglas que cuando se hace una llave. null Cambiar el período de restauración inicia un nuevo período a cero. (400 key_revoked) Regreso {"data": {…}} con el objeto clave actualizado.

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ón de una clave

DELETE https://nymbot.ai/api/v1/keys/{id} y firmado.

Detiene la llave de inmediato, para bien. Se mantiene en la lista con revoked_at y se puede ver con include_revoked=trueRevocar una clave que ya ha sido revocada responde de la misma manera. Sólo se conservan las últimas 50 claves revocadas; las más antiguas se borran cuando se revoca otra clave.

Respuesta

{ "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 en España

Conecte una cartera de Lightning con Nostr Wallet Connect y Nymbot sube un saldo por sí solo cuando el gasto de API lo ejecuta bajo. la hoja de API de la aplicación tiene las mismas configuraciones; estos son los puntos finales detrás de ella. Solicitud firmada.

Cómo funciona: después de que una solicitud de API se cargue en el saldo que eligió para ver, si ese saldo ha caído por debajo de su umbral, Nymbot hace una factura para su cantidad superior, le pide a su cartera que la pague, y añade el crédito. Se eleva a lo más una vez cada 5 minutos para cada nim y saldo, por lo que una explosión de solicitudes no puede drenar la cartera. El gasto en las aplicaciones no lo desencadena. El tiempo y el tamaño del último top-up, y el último error, están en las configuraciones; si un pago pasó después de un error, comprueba su factura con El estado top-up que lo acrediten.

Antes de conectar una cartera

Nymbot lo almacena encriptado y sólo lo utiliza para pagar sus propias facturas de arriba, pero haga una conexión sólo para esto, con un presupuesto de gasto en su cartera, por lo que lo más que podría pagar es un número elegido por usted. pay_invoice.

Conectar una cartera

POST https://nymbot.ai/api/v1/nwc-auto-topup/connect y firmado.

CampoTipoRequiereDescripción
nwc_urlStringSíLa cadena de conexión, que comienza nostr+walletconnect://Nymbot pide la cartera para get_info antes de guardarlo, y lo almacena encriptado.
threshold_satsEntierroSíTop up cuando el saldo cae por debajo de estos muchos sats. al menos 1.000.
topup_satsEntierroSíCuánto añadir cada vez. 1.000 a 1.000.000 sats.
tierStringNopro (el defecto) o standard: el equilibrio para observar y top up.

Respuesta

{
  "data": {
    "connected": true,
    "threshold_sats": 5000,
    "topup_sats": 20000,
    "tier": "pro",
    "last_topup_at": null,
    "last_topup_sats": null,
    "last_error": null
  }
}
EstatusCuando
400No es una línea de conexión (invalid_nwc_url); la cartera no respondió sobre su relay (nwc_unreachable) o rechazó el cheque (nwc_rejected); la conexión no puede pagar facturas (nwc_missing_permission(o una cantidad fuera de los límites.
501No se activan las top-ups automáticas para este servidor (nwc_unavailableLo mismo ocurre con los otros dos puntos finales.

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

Leer las configuraciones

GET https://nymbot.ai/api/v1/nwc-auto-topup y firmado.

Devuelve el mismo objeto que conectar, con connected: false Y los otros campos null cuando no hay cartera conectada. La cadena de conexión en sí nunca se devuelve.

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

Desconexión

DELETE https://nymbot.ai/api/v1/nwc-auto-topup/connection y firmado.

Elimina la cadena de conexión almacenada. No se realizan más top-ups. Para estar seguro, también puede revocar la conexión en su cartera.

Respuesta

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

Pago por solicitud sin llave

Los puntos finales de precio fijo se pueden pagar por una solicitud a la vez a través de Lightning, sin llave, sin cuenta y sin saldo: POST /images/generations, POST /images/edits, POST /videos, POST /audio/speech, POST /audio/transcriptions, POST /audio/translations y POST /embeddingsChat, Respuestas y Mensajes siempre necesitan una clave.Una solicitud que lleva una clave se factura al saldo como de costumbre; el flujo de pago sólo comienza cuando no se envía ninguna clave.

Nymbot habla de dos versiones de la misma idea, desde un backend: Lightning Labs L402 (También aceptado bajo su antiguo nombre, LSAT) y el proyecto IETF Pago Esquema de autenticación HTTP con lightning Método y charge Utilice lo que su cliente entienda.

Ejecuta tu propio servidor

Pagar sin una llave está en solo cuando API_L402_SECRET contiene al menos 32 bytes aleatorios, como hex (64 caracteres) o base64 (44). openssl rand -hex 32Un valor más corto o estimable apaga la función y registra por qué. Para girarla, desplaza el valor antiguo a API_L402_SECRET_PREVIOUS durante un día: las credenciales, las URLs de estado y los desafíos realizados bajo ella continúan funcionando hasta que expiran.

El desafío

Enviar la solicitud con no Authorization si es válido, nada se ejecuta, y se obtiene 402 Payment Required con una factura para exactamente lo que esa solicitud cuesta: el mismo precio que una clave pagaría, convertido en 10 sats un crédito estándar o 100 sats un crédito Pro y redondeado hasta un sat completo (al menos 1 sat, y al menos el mínimo de crédito 0.05). WWW-Authenticate Desafíos para la misma 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 pago request El parámetro es base64url JSON: {"amount":"237","currency":"sat","methodDetails":{"invoice":"lnbc...","network":"mainnet","paymentHash":"..."}}.

Un desafío está vinculado al punto final, al Content-Type (su tipo de soporte y, para multipart, su límite) y al SHA-256 de los bytes de cuerpo exactos que envió, y dura 15 minutos. identidad Solicitar otra vez: lo mismo Content-Type y los mismos bytes JSON, o para los puntos finales multipartes (/images/edits, /audio/transcriptions, /audio/translationsLa mayoría de las bibliotecas HTTP eligen un nuevo límite cada vez que codifican un formulario, por lo que codifican una vez y envían esos bytes dos veces.

Cada dirección puede pedir 30 desafíos por minuto (una dirección IPv6 cuenta como su totalidad /64). Las solicitudes cuya dirección no es conocida comparten un 10 más estricto por minuto, y hay un límite general en los desafíos Nymbot problemas en todas las direcciones; una solicitud rechazada antes de que se haga un desafío (por ejemplo, con un cuerpo que no es válido JSON) no cuenta hacia ella. 429 con Retry-AfterLos puntos finales pagados llamados sin una clave o credencial también cuentan hacia el límite general de 120 solicitudes no autenticadas por minuto por dirección. Content-Type No es application/json (o el multipart/form-data para las subidas) se rechaza con 415 Nunca recibe una factura.

Las incorporaciones se valoran a partir de una estimación de los tokens en la entrada, con un margen de 1,5 veces, ya que el número real sólo se conoce después. Reembolsar Token.

Enviar el pago

Paga la factura con cualquier billetera Lightning. La billetera te da la preimagen, 64 caracteres hexadecimales. Luego envía la misma solicitud con uno de estos:

El esquemaHeader
L402Authorization: L402 <macaroon>:<preimage> (LSAT también funciona)
PagoAuthorization: Payment <base64url JSON>Dónde está el JSON {"challenge": {every parameter of the challenge, as sent}, "payload": {"preimage": "<hex>"}}

Una solicitud de pago responde exactamente como una solicitud realizada con una llave, excepto que la nymbot El objeto no tiene campos de equilibrio: {"payment": "l402", "tier": "pro", "paid_sats": 237, "charged_sats": 237}Y hay que no X-Nymbot-Balance-Sats Una solicitud pagada con el esquema de pago también recibe un Payment-Receipt header (base64url JSON con el ID de desafío, el hash de pago como reference, status y timestampLas solicitudes pagadas no están vinculadas a ningún nim, por lo que no aparecen en el historial de consultas.

EstatusCuando
402 payment_already_usedCada pago paga por una solicitud.La respuesta es un nuevo desafío para esta solicitud, por lo que un cliente que cacha su última credencial (como lnget Y es que, simplemente, vuelve a nacer.
402 payment_mismatchLa credencial se emitió para otro punto final, Content-Type o cuerpo, o paga menos de lo que esta solicitud ahora cuesta. Un nuevo desafío para esta solicitud viene con él; si el pago fue demasiado pequeño, lo que pagó vuelve como un Reembolsar Token (refund_token y refund_sats en el cuerpo).
402 payment_expiredMás de 15 minutos han pasado desde el reto. Un nuevo desafío viene con él. Si la preimagen muestra que has pagado, lo que has pagado vuelve como un Reembolsar Token (refund_token y refund_sats en el cuerpo), una vez; la credencial se usa después.
401 invalid_preimageLa preimagen no hash al hash de pago de la factura.
401 invalid_payment_credentialLa credencial está mal formada, fue cambiada después de que Nymbot la emitió, o nombres de un hash de pago que Nymbot nunca emitió una factura.
429 rate_limit_exceededMás de 30 credenciales o claves que no pudieron verificarse llegaron de esta dirección en un minuto, o un token de reembolso fue enviado más de 60 veces en 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"));

Clientes construidos en mppx con un método Lightning manejan el desafío de pago ellos mismos; señalarlos en el punto final y dejar que paguen.

Videos

El pago POST /videos Respuestas 202 como una llave, más a status_url: GET Se firma y funciona durante 24 horas, siempre que se mantenga el trabajo.

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

Mantenga el refund_token de esta respuesta: sólo se muestra aquí. Está vacío mientras el videoGET /api/v1/l402/refunds Respuestas "status": "pending"); si el rendimiento falla sin facturar, el pago aterriza en él. La URL de estado muestra refund_sats para un trabajo reembolsado pero nunca el token, por lo que compartir la URL de estado no comparte el reembolso.

reembolsos

Si una solicitud pagada falla y el proveedor factura a Nymbot por el intento, el pago se mantiene y el error dice eso, con charged_sats, exactamente como para una solicitud clave. Si falla sin ser facturado, el error lleva un Reembolsar Token Vale lo que pagaste:

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

Las piezas no utilizadas se devuelven de la misma manera: si pidió dos fotos y uno falló sin facturar, la respuesta de éxito nymbot el objeto lleva un token de reembolso para el que falta; una transcripción cuya longitud no podía ser leída de antemano se precia por lo más largo que el archivo pudiera ser (nunca más de 30 minutos), y la diferencia a la longitud real se devuelve como un token de reembolso; si resulta ser más largo de 30 minutos, se rechaza con 413 Los embeddings devuelven lo que la estimación retiene.Un vídeo fallido devuelve el token de su presentación devuelto.

Un token de reembolso es un código aleatorio de 256 bits. Nymbot solo almacena su hash, y expira después de 30 días.

  • Paga con ella. Envío Authorization: Bearer REFUND-… en cualquiera de los puntos finales anteriores (un SDK OpenAI lo toma como su clave API).El precio sale del token y lo que queda queda en él (refund_token_sats En la nymbot Un token vale menos que las respuestas a la solicitud 402 refund_insufficientUn fracaso no contable pone la sats de vuelta en el mismo token.
  • Compruebe lo GET /api/v1/l402/refunds con el mismo header vuelve {"sats": 237, "status": "open", "expires_at": "..."}.
  • Un token puede ser usado hasta 60 veces por minuto.
  • Moverlo a un nym. Ponerlo en Recibir un regalo en la aplicación Nymbot, o llamando POST /api/v1/l402/refunds/redeem con a Solicitud firmada y {"refund_token": "REFUND-...", "balance": "standard"} (o el "pro"Los créditos enteros van al saldo (10 centavos cada uno en estándar, 100 en Pro); centavos que no hacen que un crédito entero permanezca en el token para las solicitudes de 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"

Respuesta

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