Ir ao contido
Volver a Nymbot

Base de coñecemento desenvolvedores

Balance, top-ups e chaves

Comprobe o que ten, top-up sobre Lightning, top-up automaticamente a partir da súa propia carteira, ver o que cada solicitude custa, e xestionar as claves a partir de código.

Comprobar o equilibrio

Ambos os seus saldos, e canto da capa desta chave é usada.

GET https://nymbot.ai/api/v1/credits/balance Necesita unha chave de API. POST Tamén funciona, para os clientes que o esperan.

balance é os dous saldos xuntos en dólares ao prezo actual de Bitcoin, para ferramentas que esperan un único número (null O resto está en créditos e sats, que é como os saldos son realmente mantidos. key Describe a clave que solicitou.Unha clave que alcanzou o seu límite aínda pode comprobar o 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"
  }
}
Estatutocando
401A clave está ausente, descoñecida, revogada ou 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étodos de pagamento

Como pode top up, e os límites. Lightning é o único método.

GET https://nymbot.ai/api/v1/topup/payment-methods Non é necesaria ningunha chave.

Un top-up é de 10 a 1.000.000 sats; un top-up Pro ten que mercar polo menos un crédito Pro, polo que comeza en 100 sats. bulk_bonus lista o crédito extra en top-ups máis grandes, o mesmo que na aplicación: 10%, 15% ou 20% máis en top-ups estándar de 500, 1.000 ou 5.000 sats, e en top-ups Pro de 5.000, 10.000 ou 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);

Subindo sobre o lóstrego

Realiza unha factura de lóstrego que engade crédito ao ninja á que pertence a clave. comprobalo para ter o crédito engadido.

POST https://nymbot.ai/api/v1/topup/create/btc-lightning Necesita unha chave de API.

CampoTipoNecesarioDescrición
amountNúmeroSiCanto máis, en currencyUn número enteiro para a aposta.
currencyunha stringnonSATS (A miña debilidade) USD ou BTCOs dólares son convertidos ao prezo actual de Bitcoin.
tierunha stringnonpro (O defecto ou standard: a que balance vai o crédito.

Un crédito estándar é 10 sats e un crédito Pro 100 sats, máis calquera bonos en masa; credits Diga o que esta factura vai engadir.

Resposta

{
  "invoice_id": "b7d41e0c95a2f38e6c1d0e9a4b7f2c61d3e8a05f9b2c4d7e1a6f3b8c0d5e2a9f4",
  "payment_request": "lnbc100u1p5...",
  "amount_sats": 10000,
  "credits": 115,
  "tier": "pro",
  "expires_at": "2026-09-30T09:27:00Z",
  "status": "pending"
}
Estatutocando
400Outro método no camiño (unsupported_method), unha moeda descoñecida (unsupported_currency) ou nivel, unha cantidade faltante, ou unha cantidade por debaixo do mínimo (amount_too_smallMáis de 1 millón (amount_too_large) ou rexeitado pola carteira Lightning (amount_out_of_range).
429Máis de 60 facturas para este nim, ou 120 desde esta dirección, nunha hora (rate_limit_exceeded, con Retry-After).
502Non se pode facer unha factura no 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);

Coñecer un top-up

Pregunta se a factura foi pagada e, unha vez que ten, engade o crédito. creditedComprobar de novo despois é seguro: o crédito aterra unha vez, por moitas veces que pida.

GET https://nymbot.ai/api/v1/topup/status/{invoice_id} - Necesita unha chave do nim que fixo a factura.

status é pending (aínda non se pagou) paid (Pago, pero aínda non creditado; verifique de novo), credited (no seu balance) ou expired (Non se paga no tempo).Os campos de balance son para o nivel no que se cobre a factura.

Resposta

{
  "invoice_id": "b7d41e0c95a2f38e6c1d0e9a4b7f2c61d3e8a05f9b2c4d7e1a6f3b8c0d5e2a9f4",
  "status": "credited",
  "amount_sats": 10000,
  "credits": 115,
  "tier": "pro",
  "expires_at": null,
  "balance_credits": 523.33,
  "balance_sats": 52333
}
Estatutocando
400O id non é o id de 64 caracteres da chamada creada.
404Ningunha factura por ese id para o 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);

Queremos historia

Unha liña por solicitude: o que era, que modelo, cantos tokens e o que custou. Non se conservan prompts ou respostas, polo que non se devolven. As filas consérvanse durante 90 días, o máis recente primeiro. Unha clave que alcanzou o seu límite aínda pode ler o seu historial.

GET https://nymbot.ai/api/v1/queries/history precisa dunha clave de API, que ve as súas propias solicitudes, ou unha Solicitude asinada da túa ninfa, que ve cada chave.

CampoTipoNecesarioDescrición
pageXénero integer (query)nonPor defecto 1, como máximo 1.000; unha páxina máis alta é 400 invalid_value.
page_countXénero integer (query)nonRoldas por páxina. 20 por defecto, ata un máximo de 100.
start_date
end_date
Conexión (query)nonISO 8601 datas ou horas.
modelConexión (query)nonSó este modelo.
typeConexión (query)nonchat, responses, messages, image, video, speech, transcription ou embedding.
all_keysgalego (query)noncunha chave: true inclúe todas as claves do mesmo nym. false.
key_idConexión (query)nonmediante solicitude asinada ou mediante all_keys=trueSó esta chave.

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

Solicitacións de conta

A creación, o cambio e a revogación de chaves, o resumo da conta e os top-ups automáticos non toman unha clave de API. Eles toman unha sinatura do seu nim, polo que unha clave perdida pode gastar ata o seu límite, pero nunca pode facer outra clave ou aumentar o seu propio límite.

A aplicación fai isto por ti: todo na súa fogueira Sheet usa estes puntos finais. Só precisa desta sección para xestionar as claves do seu propio código.

A sinatura é un evento Nostr do tipo 27235 (NIP-98), enviado base64-codificado na Authorization Header coa palabra Nostr Na súa fronte:

O 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 é a URL completa da solicitude, incluída a cadea de consulta, exactamente como foi enviada.
  • method É o método HTTP.
  • payload é o SHA-256 do corpo de solicitude bruto, en hexadecimal. POST e PATCH, e o corpo que envía debe ser byte por byte o que hashou.
  • created_at Debe estar dentro de 60 segundos do reloxo do servidor.
  • Cada evento funciona unha vez, a GET incluído, polo que un encabezado capturado non pode ser reproducido. Asine un novo para cada solicitude. nonce etiqueta cun valor aleatorio para que dúas solicitudes asinadas no mesmo segundo aínda sexan diferentes.
  • O corpo dunha solicitude asinada pode ter un máximo de 64 KB, e un corpo necesita Content-Type: application/json.

Un evento perdido regresa 401 missing_nostr_authUn que está mal formado, mal asinado, moi antigo, ou para unha URL diferente, método ou corpo devolve invalid_nostr_auth, coa razón na mensaxe; un reutilizado un retorna nostr_auth_replayedA sinatura é verificada antes de que o corpo sexa lido, e cada enderezo pode fallar 30 veces por minuto (un enderezo IPv6 conta como o seu enteiro /64); despois recibe 429 con Retry-After.

Os navegadores só poden chamar estes puntos finais desde os propios sitios de Nymbot (https://nymbot.ai, https://nymchat.app Unha páxina en calquera outro sitio non recibe os encabezados CORS de volta, polo que non pode ler o que devolven. OriginNon están afectados.

A túa clave secreta

A sinatura necesita a clave secreta do seu nim (o nsec), que controla todo: a túa identidade, o teu historial e o teu saldo.Só o pon nun guión nunha máquina en que confías, lea-lo desde o ambiente en lugar de escribilo no ficheiro, e prefire a aplicación cando poidas.

Estes axudantes constrúen o encabezado.Os exemplos posteriores nesta páxina utilízanse. NOSTR_SECRET_HEX; o cURL un usa o Neno ferramenta de liña de comandos, que toma unha clave nsec ou hexadecimal, e sha256sum para os 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");
}

Resumo da conta

O que a folla de API da aplicación mostra na parte superior: a súa clave pública, ambos os equilibrios, cantas claves están activas (non revocadas ou expiradas) e o número de Top-up automático configuración, ou null cando o servidor non os ofrece.

GET https://nymbot.ai/api/v1/account Necesito unha Solicitude asinada.

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

Xestión de chaves

Os puntos finais detrás da lista de claves da aplicación. Todos eles necesitan unha Solicitude asinadaCada clave é devolta nesta forma, con tempos en ISO 8601 e cantidades en sats:

Obxecto 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 É suficiente para recoñecer unha clave pero non para usala.A clave en si só se devolve unha vez, cando se fai.

Listaxe de chaves

GET https://nymbot.ai/api/v1/keys e asinaron.

CampoTipoNecesarioDescrición
include_revokedgalego (query)nonInclúe as claves revocadas. 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);

Facer unha chave

POST https://nymbot.ai/api/v1/keys Asinado, devolto 201.

CampoTipoNecesarioDescrición
nameunha stringSi1 a 40 caracteres, diferentes das súas outras claves activas (ignorando o caso).
limit_satsenteirononO límite de gastos en sats, polo menos 1. deixalo fóra por ningún cap.
reset_periodunha stringnondaily, weekly ou monthlyNecesidades limit_satsDeixalo para unha tapa que nunca se restaura.
expire_atString ou enteirononCando a clave deixa de funcionar: unha ISO 8601 veces, ou milisegundos desde 1970.

Resposta á pregunta (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"
  }
}
Estatutocando
400Un nome perdido ou demasiado longo; un nome xa en uso (duplicate_name); un cap que non é un número enteiro de polo menos 1; un período de restauración sen cap; unha expiración no pasado; un campo descoñecido (unknown_parameter); ou 25 chaves activas xa (too_many_keys).
429Máis de 60 chaves feitas por este nim, ou 120 desde esta dirección, nunha 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);

Ler unha chave

GET https://nymbot.ai/api/v1/keys/{id} e asinaron.

Regreso {"data": {…}} co obxecto clave, ou 404 key_not_found Se a túa clave non ten 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 unha chave

PATCH https://nymbot.ai/api/v1/keys/{id} e asinaron.

Envíanos calquera name, limit_sats, reset_period e expire_at, coas mesmas regras que cando se fai unha chave. null Cambiar o período de reposición comeza un novo período a cero. (400 key_revoked) Regreso {"data": {…}} co obxecto 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);

Revogación dunha chave

DELETE https://nymbot.ai/api/v1/keys/{id} e asinaron.

Detén a clave de inmediato, para ben. revoked_at Aínda así, e pódese ver con include_revoked=trueA revogación dunha clave que xa foi revogada responde do mesmo xeito. Só se conservan as máis recentes 50 chaves revogadas; as máis antigas se borran cando se revoga outra clave.

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

NWC Auto-top-up en liña

Conecta unha carteira Lightning con Nostr Wallet Connect e Nymbot cumpre un saldo por si só cando o gasto en API é baixo. A folla de API da aplicación ten as mesmas opcións; estes son os puntos finais detrás dela. Solicitude asinada.

Como funciona: despois de que unha solicitude de API se cargue no saldo que escolleu para ver, se ese saldo caeu por baixo do seu limiar, Nymbot fai unha factura para o seu importe superior, pide que a súa carteira o pague e engade o crédito. Supera o máximo unha vez cada 5 minutos para cada nim e saldo, de xeito que unha explosión de solicitudes non pode drenar a carteira. O gasto nas aplicacións non o desencadea. O tempo e o tamaño do último top-up, e o último erro, están nas configuracións; se un pagamento pasou despois dun erro, verifique a súa factura con O estado top-up Créditos para iso.

Antes de conectar unha carteira

Nymbot almacena cifrado e só o usa para pagar as súas propias facturas, pero fai unha conexión só para iso, cun orzamento de gasto na súa carteira, polo que o máis que pode pagar é un número escollido por vostede. pay_invoice.

Conectar unha carteira

POST https://nymbot.ai/api/v1/nwc-auto-topup/connect e asinaron.

CampoTipoNecesarioDescrición
nwc_urlunha stringSiA cadea de conexión, comezando nostr+walletconnect://Nymbot pide a carteira para get_info antes de gardalo, e almacena cifrado.
threshold_satsenteiroSiTop up cando o saldo cae por baixo destes moitos sats. polo menos 1.000.
topup_satsenteiroSiCanto engadir cada vez. 1.000 a 1.000.000 sats.
tierunha stringnonpro (O defecto ou standard: o equilibrio para mirar e top up.

Resposta

{
  "data": {
    "connected": true,
    "threshold_sats": 5000,
    "topup_sats": 20000,
    "tier": "pro",
    "last_topup_at": null,
    "last_topup_sats": null,
    "last_error": null
  }
}
Estatutocando
400Non é unha liña de conexión (invalid_nwc_url(A páxina non responde á súa mensaxe)nwc_unreachable) ou rexeitou o control (nwc_rejected); a conexión non pode pagar facturas (nwc_missing_permission); ou unha cantidade fóra dos límites.
501Non se activan as top-ups automáticas para este servidor (nwc_unavailableO mesmo se aplica aos outros dous puntos finais.

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

Ler as configuracións

GET https://nymbot.ai/api/v1/nwc-auto-topup e asinaron.

Devolve o mesmo obxecto que conectar, con connected: false e os outros campos null Cando non hai ningunha carteira conectada, a cadea de conexión en si nunca se devolve.

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 e asinaron.

Elimina a cadea de conexión almacenada. Non se realizan máis top-ups. Para estar seguro, tamén pode revogar a conexión na súa carteira.

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

Pagar por solicitude sen unha chave

Os puntos finais de prezo fixo poden ser pagados por unha solicitude ao mesmo tempo en Lightning, sen clave, sen conta e sen saldo: POST /images/generations, POST /images/edits, POST /videos, POST /audio/speech, POST /audio/transcriptions, POST /audio/translations e POST /embeddingsChat, respostas e mensaxes sempre necesitan unha clave. Unha solicitude que leva unha clave é facturada ao saldo como de costume; o fluxo de pagamento só comeza cando non se envía ningunha clave.

Nymbot fala de dúas versións da mesma idea, desde un backend: Lightning Labs Xénero 402 (tamén aceptado baixo o seu antigo nome, LSAT) e o proxecto IETF Pago O esquema de autenticación HTTP coa lightning Método e charge Use o que o seu cliente entenda.

Executa o teu propio servidor

Pagar sen unha chave é só en cando API_L402_SECRET ten polo menos 32 bytes aleatorios, como hexadecimal (64 caracteres) ou base64 (44). openssl rand -hex 32Un valor máis curto ou adiviñable apaga a característica e rexistra por que. API_L402_SECRET_PREVIOUS durante un día: credenciais, URLs de estado e desafíos feitos baixo el continúan funcionando ata que expiran.

O reto

Envía a túa solicitude con NON Authorization se é válido, nada se executa, e obtés 402 Payment Required cunha factura para exactamente o que a solicitude custa: o mesmo prezo que pagaría unha clave, convertido en 10 sats un crédito estándar ou 100 sats un crédito Pro e arredondado ata un sat enteiro (polo menos 1 sat, e polo menos o mínimo de crédito 0,05). WWW-Authenticate Recomendacións para a mesma 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 }
}

O pago request O parámetro é base64url JSON: {"amount":"237","currency":"sat","methodDetails":{"invoice":"lnbc...","network":"mainnet","paymentHash":"..."}}.

Un reto está ligado ao punto final, ao Content-Type (o seu tipo de soporte e, para multipart, o seu límite) e ao SHA-256 dos bytes corporais exactos que enviou, e dura 15 minutos. identidade Unha vez máis: o mesmo Content-Type e os mesmos bytes JSON, ou para os puntos finais de varias partes (/images/edits, /audio/transcriptions, /audio/translationsA maioría das bibliotecas HTTP elixen un novo límite cada vez que codifican un formulario, polo que codifícao unha vez e envía eses bytes dúas veces.

Cada enderezo pode pedir 30 desafíos por minuto (un enderezo IPv6 conta como o seu enteiro /64). As solicitudes cuxo enderezo non é coñecido comparten un 10 máis estricto por minuto, e hai un límite global sobre os desafíos Nymbot problemas en todos os enderezos; unha solicitude rexeitada antes de que se faga un desafío (por exemplo, cun corpo que non é válido JSON) non conta para el. 429 con Retry-AfterOs puntos finais pagos chamados sen unha clave ou credencial tamén contan cara ao límite xeral de 120 solicitudes non autenticadas por minuto por enderezo. Content-Type Non é application/json ou multipart/form-data para cargas) é rexeitado con 415 e nunca recibe unha factura.

As incorporacións son prezadas a partir dunha estimación dos tokens na entrada, cunha marxe de 1,5 veces, xa que a conta real só se coñece despois. Recuperación de tokens.

Envío do pagamento

Pague a factura con calquera carteira Lightning. A carteira dálle a imaxe previa, 64 caracteres hexadecimales. A continuación, envíe a mesma solicitude cun destes:

esquemaHeader
Xénero 402Authorization: L402 <macaroon>:<preimage> (LSAT que tamén funciona)
PagoAuthorization: Payment <base64url JSON>, onde o JSON está {"challenge": {every parameter of the challenge, as sent}, "payload": {"preimage": "<hex>"}}

Unha solicitude paga responde exactamente como unha solicitude feita cunha chave, agás que o nymbot O obxecto non ten campos de equilibrio: {"payment": "l402", "tier": "pro", "paid_sats": 237, "charged_sats": 237}e non hai ningunha X-Nymbot-Balance-Sats unha solicitude pagada co esquema de pagamento tamén recibe un Payment-Receipt header (base64url JSON co id de desafío, o hash de pagamento como reference, status e timestampAs solicitudes pagas non están vinculadas a ningún nim, polo que non aparecen no historial de consultas.

Estatutocando
402 payment_already_usedCada pago paga por unha solicitude.A resposta é un novo reto para esta solicitude, polo que un cliente que cache a súa última credencial (como lnget O que pasa é que simplemente volven a nacer.
402 payment_mismatchA credencial foi emitida para outro punto final, Content-Type ou corpo, ou paga menos do que esta solicitude agora custa. un novo desafío para esta solicitude vén con el; se o pagamento foi demasiado pequeno, o que pagou volve como un Recuperación de tokens (refund_token e refund_sats no seu corpo).
402 payment_expiredMáis de 15 minutos pasaron desde o desafío.Un novo desafío vén con el. Se a preimpresión mostra que pagou, o que pagou volve como un Recuperación de tokens (refund_token e refund_sats no corpo), unha vez; a credencial é entón usada.
401 invalid_preimageA preimpresión non hash ao hash de pagamento da factura.
401 invalid_payment_credentialA credencial está mal formada, foi cambiada despois de que Nymbot a emitise, ou nomea un hash de pagamento para o que Nymbot nunca emitiu unha factura.
429 rate_limit_exceededMáis de 30 credenciais ou chaves que non foron verificadas chegaron deste enderezo nun minuto, ou un token de reembolso foi enviado máis de 60 veces nun 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"));

Os clientes están construídos mppx cun método Lightning xestionar o reto de pagamento eles mesmos; apunta-los no punto final e deixalos pagar.

Vídeos

Unha paga POST /videos Respostas 202 como unha chave, máis a status_url: GET É asinado e funciona durante 24 horas, mentres se manteña o traballo.

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

Manteña o refund_token Aínda que non está dispoñible, o vídeo está dispoñible para descargar (GET /api/v1/l402/refunds Respostas "status": "pending"); se o rendemento falla sen facturar, o pagamento aterra nel. A URL de estado mostra refund_sats para un traballo reembolsado pero nunca o token, así que compartir a URL de estado non comparte o reembolso.

Reembolsos

Se unha solicitude paga fracasou e o provedor cobrou Nymbot polo intento, o pagamento mantense e o erro di así, con charged_sats, exactamente como para unha solicitude clave. Se falla sen ser facturado, o erro leva un Recuperación de tokens Vale o que pagaches:

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

As pezas non utilizadas volven do mesmo xeito: se pediches dúas imaxes e unha non foi descargada, a resposta de éxito nymbot O obxecto leva un token de reembolso para o que falta; unha transcrición cuxa lonxitude non puidese ser lida antes é prezada polo máis longo que puidese ser o ficheiro (nunca máis de 30 minutos), e a diferenza á lonxitude real volve como un token de reembolso; se resulta ser máis longo que 30 minutos, é rexeitado con 413 Os embeddings devolven o que a estimación mantiña de volta.Un vídeo fallado devolve ao token que a súa presentación devolveu.

Un token de reembolso é un código aleatorio de 256 bits. Nymbot só almacena o seu hash, e expira despois de 30 días.

  • Paga con iso. Envío Authorization: Bearer REFUND-… En calquera caso, o prezo da tarxeta de crédito é o mesmo que o prezo da tarxeta de crédito. (refund_token_sats Na súa nymbot Un token vale menos que as respostas á solicitude 402 refund_insufficientUn fracaso non contabilizado coloca a taxa de volta no mesmo token.
  • Comproba iso. GET /api/v1/l402/refunds co mesmo header volve {"sats": 237, "status": "open", "expires_at": "..."}.
  • Un token pode ser usado ata 60 veces por minuto.
  • Mova-lo para unha nym. Poñémolo en Recibir un agasallo na aplicación Nymbot, ou chamando POST /api/v1/l402/refunds/redeem Con unha Solicitude asinada e {"refund_token": "REFUND-...", "balance": "standard"} ou "pro"Os créditos enteiros van ao saldo (10 cotas cada unha no estándar, 100 no Pro); cotas que non fan que un crédito enteiro permaneza no token para as 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"

Resposta

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