Base de connaissances Développeurs
Balance, top-ups et clés
Vérifiez ce que vous avez, surmontez Lightning, surmontez automatiquement à partir de votre propre portefeuille, voyez combien coûte chaque demande et gérez les clés à partir de code.
Cette page est traduite automatiquement pour plus de commodité. La version originale anglaise est la version qui s’applique.
Vérifier l’équilibre
Les deux de vos soldes, et combien de cette clé de cap est utilisé.
GET https://nymbot.ai/api/v1/credits/balance Vous avez besoin d’une clé API. POST C’est aussi pour les clients qui s’y attendent.
balance est les deux soldes ensemble en dollars au prix Bitcoin actuel, pour les outils qui s'attendent à un seul nombre (null si le prix ne peut pas être lu). le reste est en crédits et en taux, ce qui est la façon dont les soldes sont effectivement conservés. key Une clé qui a atteint son cap peut toujours vérifier le solde.
Réponse
{
"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"
}
}
| Statut | Quand |
|---|---|
401 | La clé est manquante, inconnue, révoquée ou expirée. |
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éthodes de paiement
Comment pouvez-vous surmonter, et les limites. Lightning est la seule méthode.
GET https://nymbot.ai/api/v1/topup/payment-methods Pas de clé nécessaire.
Un top-up est de 10 à 1 000 000 sats; un top-up Pro doit acheter au moins un crédit Pro, donc il commence à 100 sats. bulk_bonus liste le crédit supplémentaire sur les top-ups plus grands, le même que dans l'application: 10%, 15% ou 20% de plus sur les top-ups standard à partir de 500, 1000 ou 5000 sats, et sur les top-ups Pro à partir de 5000, 10.000 ou 50 000 sats.
Réponse
{
"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);
Se lever au-dessus des éclairs
Créer une facture Lightning qui ajoute du crédit au nym à laquelle appartient la clé. Payer à partir de n'importe quel portefeuille Lightning, puis Vérifiez le Pour avoir le crédit ajouté.
POST https://nymbot.ai/api/v1/topup/create/btc-lightning Vous avez besoin d’une clé API.
| champs | Type | Nécessaire | Description |
|---|---|---|---|
amount | Numéro | Oui | Combien, en currencyUn nombre entier pour la mise. |
currency | String à | Non pas | SATS (Le défaut est USD ou BTCLes dollars sont convertis au prix Bitcoin actuel. |
tier | String à | Non pas | pro (le défaut) ou standard: à quelle balance va le crédit. |
Un crédit standard est de 10 sats et un crédit Pro de 100 sats, plus tout bonus en vrac;
credits dit ce que cette facture ajoutera.
Réponse
{
"invoice_id": "b7d41e0c95a2f38e6c1d0e9a4b7f2c61d3e8a05f9b2c4d7e1a6f3b8c0d5e2a9f4",
"payment_request": "lnbc100u1p5...",
"amount_sats": 10000,
"credits": 115,
"tier": "pro",
"expires_at": "2026-09-30T09:27:00Z",
"status": "pending"
}
| Statut | Quand |
|---|---|
400 | Une autre méthode dans le chemin (unsupported_method, une monnaie inconnue (unsupported_currencyou un montant manquant, ou un montant inférieur au minimum (amount_too_smallplus de 1 000 000 $ (amount_too_large) ou refusé par le portefeuille Lightning (amount_out_of_range). |
429 | Plus de 60 factures pour ce nym, ou 120 de cette adresse, en une heure (rate_limit_exceeded, avec Retry-After). |
502 | Il n'y a pas de facture disponible pour le moment (invoice_unavailable, avec 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);
Vérifier un top-up
Demande si la facture a été payée et, une fois qu'il l'a, ajoute le crédit. vérifier est ce qu'il crédite, donc après le paiement, vérifiez jusqu'à ce que le statut est creditedVérifier à nouveau par la suite est sûr: le crédit atterrit une fois, peu importe le nombre de fois que vous demandez.
GET https://nymbot.ai/api/v1/topup/status/{invoice_id} - besoin d'une clé de la nym qui a fait la facture.
status est pending (il n’est pas encore payé) paid (paié, mais pas encore crédité; vérifiez à nouveau), credited (sur votre équilibre) ou expired (pas payé à temps).Les champs de solde sont pour le niveau de la facture.
Réponse
{
"invoice_id": "b7d41e0c95a2f38e6c1d0e9a4b7f2c61d3e8a05f9b2c4d7e1a6f3b8c0d5e2a9f4",
"status": "credited",
"amount_sats": 10000,
"credits": 115,
"tier": "pro",
"expires_at": null,
"balance_credits": 523.33,
"balance_sats": 52333
}
| Statut | Quand |
|---|---|
400 | L'id n'est pas l'id de 64 caractères de l'appel créé. |
404 | Pas de facture par cette id pour votre 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);
Recherche Histoire
Une ligne par requête: ce qu'il était, quel modèle, combien de jetons et ce qu'il coûtait. Aucune prompt ou réponse n'est conservée, donc aucune n'est retournée. Les rangées sont conservées pendant 90 jours, le plus récent d'abord. Une clé qui a atteint son cap peut toujours lire son historique.
GET https://nymbot.ai/api/v1/queries/history besoin d'une clé API, qui voit ses propres requêtes, ou Demande signée de votre nym, qui voit chaque clé.
| champs | Type | Nécessaire | Description |
|---|---|---|---|
page | L’intégralité (query) | Non pas | Par défaut 1, au maximum 1000; une page plus élevée est 400 invalid_value. |
page_count | L’intégralité (query) | Non pas | Rues par page. 20 par défaut, au maximum 100. |
start_dateend_date | String (recherche) | Non pas | ISO 8601 Dates et heures. |
model | String (recherche) | Non pas | Seul ce modèle. |
type | String (recherche) | Non pas | chat, responses, messages, image, video, speech, transcription ou embedding. |
all_keys | Le bouléen (query) | Non pas | Avec une clé : true contient toutes les clés du même nym. false. |
key_id | String (recherche) | Non pas | Avec une demande signée, ou all_keys=trueSeule cette clé. |
Réponse
{
"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);
Signature des demandes de compte
La création, le changement et la révocation de clés, le résumé du compte et le top-up automatique ne prennent pas une clé API. Ils prennent une signature de votre nym, de sorte qu'une clé fuit peut dépenser jusqu'à son cap, mais ne peut jamais faire une autre clé ou augmenter sa propre cap.
L’application fait cela pour vous : tout dans son ensemble Le feu Sheet utilise ces endpoints. Vous avez seulement besoin de cette section pour gérer les clés à partir de votre propre code.
La signature est un événement Nostr de type 27235 (NIP-98), envoyé base64-encodé dans le
Authorization Header avec le mot Nostr En face :
L’événement
{
"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": "..."
}
uest l'URL complète de la requête, la chaîne de requête incluse, exactement comme envoyée.methodC’est la méthode HTTP.payloadest le SHA-256 du corps de la demande brute, en hexadecimal.POSTetPATCH, et le corps que vous envoyez doit être le byte par byte que vous avez hashé.created_atIl doit être dans les 60 secondes de l'horloge du serveur.- Chaque événement fonctionne une fois, a
GETinclus, de sorte qu'un en-tête capturé ne peut pas être reproduit. Signez un nouveau en-tête pour chaque demande.noncemarque avec une valeur aléatoire de sorte que deux demandes signées dans la même seconde sont toujours différentes. - Le corps d'une demande signée peut être au maximum de 64 KB, et un corps a besoin
Content-Type: application/json.
Retour d'un événement manquant 401 missing_nostr_auth; un qui est mal formé, mal signé, trop vieux, ou pour une URL différente, méthode ou corps renvoie
invalid_nostr_auth, avec la raison dans le message; un réutilisé revient
nostr_auth_replayedUne clé API envoyée à ces endpoints est refusée.La signature est vérifiée avant de lire le corps, et chaque adresse peut l'échouer 30 fois par minute (une adresse IPv6 compte comme son entier /64) ; après cela, elle obtient 429 avec
Retry-After.
Les navigateurs peuvent appeler ces endpoints uniquement à partir des sites de Nymbot (https://nymbot.ai,
https://nymchat.app Une page sur n'importe quel autre site ne reçoit pas d'en-têtes CORS, de sorte qu'elle ne peut pas lire ce qu'ils retournent.
OriginIls ne sont pas affectés.
La signature a besoin de la clé secrète de votre nym (la nsec), qui contrôle tout: votre identité, votre historique et votre bilan. Mettez-le uniquement dans un script sur une machine en laquelle vous avez confiance, lisez-le à partir de l'environnement plutôt que de l'écrire dans le fichier, et préférez l'application lorsque vous le pouvez.
Ces aides construisent l'en-tête. Les exemples ultérieurs de cette page les utilisent. Ils lisent la clé secrète en hexadecimale de NOSTR_SECRET_HEX; le cURL un utilise le
Nak outil de ligne de commande, qui prend une clé nsec ou hex, et sha256sum sur 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");
}
Résumé du compte
Ce que la feuille d'API de l'application affiche en haut: votre clé publique, les deux équilibres, le nombre de clés actives (non révoquées ou expirées) et le Top-up automatique les réglages, ou
null lorsque le serveur ne les fournit pas.
GET https://nymbot.ai/api/v1/account Il faut a Demande signée.
Réponse
{
"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());
Gestion des clés
Les endpoints derrière la liste des clés de l'application. Tous ont besoin d'un Demande signéeChaque clé est retournée dans ce formulaire, avec des temps en ISO 8601 et des montants en sats:
Objet clé
{
"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 Il suffit de reconnaître une clé mais pas de l'utiliser.La clé elle-même n'est retournée qu'une fois, lorsqu'elle est faite.
Liste des clés
GET https://nymbot.ai/api/v1/keys et signé.
| champs | Type | Nécessaire | Description |
|---|---|---|---|
include_revoked | Le bouléen (query) | Non pas | Inclut les clés révocées. Défaut false. |
Réponse
{ "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);
Faire une clé
POST https://nymbot.ai/api/v1/keys Signature - Retour 201.
| champs | Type | Nécessaire | Description |
|---|---|---|---|
name | String à | Oui | 1 à 40 caractères, différents de vos autres clés actives (ignorant le cas). |
limit_sats | Intégrale | Non pas | Le cap de dépenses en sats, au moins 1. laissez-le sans cap. |
reset_period | String à | Non pas | daily, weekly ou monthlyBesoins limit_satsLaissez-le pour un capot qui ne se redémarre jamais. |
expire_at | String ou integer | Non pas | Lorsque la clé cesse de fonctionner : une fois ISO 8601, ou millisecondes depuis 1970. |
Répondre (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"
}
}
| Statut | Quand |
|---|---|
400 | Un nom manquant ou trop long; un nom déjà utilisé (duplicate_name) ; un cap qui n'est pas un nombre entier d'au moins 1 ; une période de réinitialisation sans cap ; une expiration dans le passé ; un champ inconnu (unknown_parameter) ; ou 25 clés actives déjà (too_many_keys). |
429 | Plus de 60 clés faites par ce nym, ou 120 de cette adresse, en une heure (rate_limit_exceeded, avec 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);
Lire une clé
GET https://nymbot.ai/api/v1/keys/{id} et signé.
retournés {"data": {…}} avec l’objet clé, ou 404
key_not_found Si vous ne possédez pas cette clé.
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);
Changer une clé
PATCH https://nymbot.ai/api/v1/keys/{id} et signé.
Envoyer un de name, limit_sats, reset_period et
expire_atavec les mêmes règles que lors de la création d'une clé. null Il n'y a pas de changement, il n'y a pas de changement, il n'y a pas de changement, il n'y a pas de changement, il n'y a pas de changement (400 key_revoked) Les retournements
{"data": {…}} avec l’objet clé mis à jour.
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);
Révoquer une clé
DELETE https://nymbot.ai/api/v1/keys/{id} et signé.
Arrête la clé immédiatement, pour le mieux. Il reste dans la liste avec revoked_at déterminée, et peut être vue avec include_revoked=trueRévoquer une clé qui a déjà été révoquée répond de la même manière.Seules les 50 dernières clés révoquées sont conservées; les anciennes sont supprimées lorsque une autre clé est révoquée.
Réponse
{ "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());
Nouveau NWC Auto-top-up
Connectez un portefeuille Lightning avec Nostr Wallet Connect et Nymbot monte un solde par lui-même lorsque les dépenses d'API sont faibles. la feuille d'API de l'application a les mêmes paramètres; ce sont les endpoints derrière elle. Demande signée.
Comment cela fonctionne: après qu'une demande API est facturée au solde que vous avez choisi de regarder, si ce solde est tombé en dessous de votre seuil, Nymbot fait une facture pour votre montant de top-up, demande à votre portefeuille de le payer, et ajoute le crédit. Il monte au maximum une fois toutes les 5 minutes pour chaque nym et le solde, de sorte qu'une explosion de demandes ne peut pas drainer le portefeuille. Les dépenses dans les applications ne le déclenchent pas. Le top-up de la situation crédit à elle.
Une chaîne de connexion permet à quiconque la détient de demander à votre portefeuille de payer. Nymbot le stocke crypté et ne l'utilise que pour payer ses propres factures, mais établit une connexion juste pour cela, avec un budget de dépenses dans votre portefeuille, de sorte que le plus qu'il pourrait jamais payer est un numéro que vous avez choisi. pay_invoice.
Connecter un portefeuille
POST https://nymbot.ai/api/v1/nwc-auto-topup/connect et signé.
| champs | Type | Nécessaire | Description |
|---|---|---|---|
nwc_url | String à | Oui | La chaîne de connexion, commençant par nostr+walletconnect://Nymbot demande le portefeuille pour get_info avant de le sauvegarder et de le stocker crypté. |
threshold_sats | Intégrale | Oui | En haut lorsque le solde tombe en dessous de ce nombre de sats. au moins 1000. |
topup_sats | Intégrale | Oui | Combien ajouter chaque fois. 1000 à 1 000 000 sats. |
tier | String à | Non pas | pro (le défaut) ou standard: l’équilibre à regarder et au top. |
Réponse
{
"data": {
"connected": true,
"threshold_sats": 5000,
"topup_sats": 20000,
"tier": "pro",
"last_topup_at": null,
"last_topup_sats": null,
"last_error": null
}
}
| Statut | Quand |
|---|---|
400 | Il n'y a pas de connexion (invalid_nwc_url(le portefeuille n'a pas répondu sur son relais)nwc_unreachable) ou refusé le contrôle (nwc_rejected(La facture ne peut pas être payée)nwc_missing_permission(ou un montant en dehors des limites. |
501 | Les enregistrements automatiques ne sont pas activés pour ce serveur (nwc_unavailableIl en va de même pour les deux autres endpoints. |
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());
Lire les réglages
GET https://nymbot.ai/api/v1/nwc-auto-topup et signé.
Retourne le même objet que la connexion, avec connected: false et les autres champs
null lorsque aucun portefeuille n'est connecté. La chaîne de connexion elle-même n'est jamais retournée.
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());
déconnexion
DELETE https://nymbot.ai/api/v1/nwc-auto-topup/connection et signé.
Supprime la chaîne de connexion stockée. Aucun top-up n'est effectué. Pour être sûr, vous pouvez également révoquer la connexion dans votre portefeuille.
Réponse
{ "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());
Payer sur demande sans clé
Les endpoints à prix fixe peuvent être payés pour une demande à la fois sur Lightning, sans clé, sans compte et sans solde: POST /images/generations, POST /images/edits,
POST /videos, POST /audio/speech, POST /audio/transcriptions,
POST /audio/translations et POST /embeddingsChat, Réponses et Messages ont toujours besoin d'une clé. Une demande qui porte une clé est facturée au solde comme d'habitude; le flux de paiement ne commence que lorsque aucune clé n'est envoyée.
Nymbot parle de deux versions de la même idée, d'un seul backend: Lightning Labs'
Le 402 (Il est également accepté sous son ancien nom, LSATLe projet IETF
paiement Système d’authentification HTTP avec lightning Méthode et
charge Utilisez ce que votre client comprend.
Payer sans clé n'est activé que lorsque API_L402_SECRET détient au moins 32 octets aléatoires, comme hex (64 caractères) ou base64 (44). openssl rand -hex 32Une valeur plus courte ou devinable éteint la fonctionnalité et enregistre pourquoi. API_L402_SECRET_PREVIOUS pendant une journée : les identifiants, les URL d’état et les défis effectués en vertu de celui-ci continuent de fonctionner jusqu’à leur expiration.
Le défi
Envoyer la demande avec non Authorization si elle est valide, rien ne fonctionne, et vous obtenez 402 Payment Required avec une facture pour exactement ce que cette demande coûte: le même prix qu'une clé paierait, converti à 10 sats un crédit standard ou 100 sats un crédit Pro et arrondi jusqu'à un sat entier (au moins 1 sat, et au moins le minimum de crédit 0,05). WWW-Authenticate Défis pour la même facture :
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 }
}
Le paiement request Paramètre de base64url JSON :
{"amount":"237","currency":"sat","methodDetails":{"invoice":"lnbc...","network":"mainnet","paymentHash":"..."}}.
Un défi est lié au point final, au Content-Type (son type de média et, pour le multipart, sa limite) et au SHA-256 du corps exact que vous avez envoyé, et dure 15 minutes. identique Demande de nouveau : la même chose
Content-Type et les mêmes bytes JSON, ou pour les endpoints multiparts (/images/edits, /audio/transcriptions, /audio/translationsLa plupart des bibliothèques HTTP choisissent une nouvelle limite chaque fois qu'elles codent un formulaire, donc codez-le une fois et envoyez ces bytes deux fois.
Chaque adresse peut demander 30 défis par minute (une adresse IPv6 compte comme sa totalité /64). Les demandes dont l'adresse n'est pas connue partagent une plus stricte 10 par minute, et il y a une limite globale sur les défis Nymbot problèmes sur toutes les adresses; une demande refusée avant qu'un défi soit fait (par exemple avec un corps qui n'est pas valide JSON) ne compte pas vers elle. 429 avec Retry-After, envoyé avant que le corps ne soit lu. Les endpoints payés appelés sans clé ou créance comptent également vers la limite générale de 120 demandes non authentifiées par minute par adresse. Content-Type n’est pas application/json (ou de
multipart/form-data pour les téléchargements) est refusé avec 415 Ne jamais recevoir de facture.
Les embeddings sont tarifiés à partir d'une estimation des jetons dans l'entrée, avec une marge de 1,5 fois, car le nombre réel n'est connu qu'après. Remboursement de token.
Envoyer le paiement
Payez la facture avec n'importe quel portefeuille Lightning. Le portefeuille vous donne la pré-image, 64 caractères hexagonaux. Envoyez ensuite la même demande avec l'un de ces:
| Le schéma | Header |
|---|---|
| Le 402 | Authorization: L402 <macaroon>:<preimage> (LSAT Il fonctionne aussi) |
| paiement | Authorization: Payment <base64url JSON>Où se trouve le JSON {"challenge": {every parameter of the challenge, as sent}, "payload": {"preimage": "<hex>"}} |
Une demande payante répond exactement comme celle faite avec une clé, sauf que la nymbot
L'objet n'a pas de champs d'équilibre : {"payment": "l402", "tier": "pro", "paid_sats": 237,
"charged_sats": 237}Et il n'y a pas de X-Nymbot-Balance-Sats Une demande payée avec le schéma de paiement reçoit également un Payment-Receipt header (base64url JSON avec le challenge id, le hash de paiement comme reference, status et
timestampLes requêtes payantes ne sont pas liées à un nym, de sorte qu’elles ne s’affichent pas dans l’historique des requêtes.
| Statut | Quand |
|---|---|
402 payment_already_used | Chaque paiement paie pour une demande.La réponse est un nouveau défi pour cette demande, de sorte qu'un client qui cache son dernier identifiant (comme lnget Et c’est tout simplement une fois de plus. |
402 payment_mismatch | Le certificat a été délivré pour un autre endpoint, Content-Type ou le corps, ou payer moins que cette demande coûte maintenant. Un nouveau défi pour cette demande vient avec elle; si le paiement était trop petit, ce que vous avez payé revient comme un Remboursement de token (refund_token et refund_sats dans le corps). |
402 payment_expired | Plus de 15 minutes se sont écoulées depuis le défi. Un nouveau défi s’accompagne de celui-ci. Si la préimpression montre que vous avez payé, ce que vous avez payé revient comme un Remboursement de token (refund_token et refund_sats dans le corps), une fois; le crédit est ensuite utilisé. |
401 invalid_preimage | La préimpression ne correspond pas au hash de paiement de la facture. |
401 invalid_payment_credential | L'identification est malformée, a été modifiée après que Nymbot l'a émise, ou nomme un hash de paiement pour lequel Nymbot n'a jamais émis de facture. Un macaron avec un avertissement que Nymbot ne connaît pas ou avec des avertissements contradictoires est refusé. |
429 rate_limit_exceeded | Plus de 30 identifiants ou clés qui n'ont pas pu être vérifiés sont venus de cette adresse en une minute, ou un jeton de remboursement a été envoyé plus de 60 fois en une minute. 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"));
Les clients sont construits sur mppx avec une méthode Lightning gérer le défi de paiement eux-mêmes; les pointer au point final et les laisser payer.
Vidéo
A payé POST /videos Réponses 202 comme une clé, plus un
status_url: GET Il est signé et fonctionne pendant 24 heures, aussi longtemps que le travail est gardé.
{
"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..."
}
}
Gardez le refund_token Il n'y a qu'à ce moment-là qu'il y a une réponse : il n'y a qu'à ce moment-là qu'il n'y a qu'une réponse. (GET /api/v1/l402/refunds Réponses "status": "pending") ; si le rendu échoue sans facturation, le paiement y atterrit.
refund_sats pour un travail remboursé mais jamais le jeton, de sorte que le partage de l'URL de statut ne partage pas le remboursement.
Remboursement
Si une demande payante échoue et que le fournisseur a facturé Nymbot pour l'essai, le paiement est conservé et l'erreur le dit, avec charged_sats, exactement comme pour une requête clé. Si elle échoue sans être facturée, l'erreur comporte une Remboursement de token Valeur de ce que vous avez payé :
{
"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 pièces inutilisées reviennent de la même manière : si vous avez demandé deux photos et que l’une n’a pas été facturée, la réponse réussie est nymbot l'objet porte un jeton de remboursement pour celui qui manque; une transcription dont la longueur ne pouvait pas être lue à l'avance est prix pour le plus long que le fichier pouvait être (pas plus de 30 minutes), et la différence à la longueur réelle revient comme un jeton de remboursement; si elle se révèle être plus longue que 30 minutes, il est refusé avec 413
L'intégration retourne ce que l'estimation a retenu. Une vidéo échouée remboursera le jeton de sa soumission retourné.
Un jeton de remboursement est un code aléatoire de 256 bits. Nymbot ne stocke que son hachage, et il expire après 30 jours.
- Payez avec elle. Envoyer
Authorization: Bearer REFUND-…Dans le cadre de l’évaluation de l’efficacité de l’évaluation, il est possible d’ajouter que l’évaluation de l’efficacité de l’évaluation de l’efficacité de l’évaluation de l’efficacité de l’efficacité de l’évaluation (refund_token_satsDans lenymbotUn jeton valant moins que les réponses à la demande402refund_insufficientUn échec non facturé remet le sats sur le même jeton. - Vérifiez le.
GET /api/v1/l402/refundsavec le même header retourne{"sats": 237, "status": "open", "expires_at": "..."}. - Un jeton peut être utilisé au maximum 60 fois par minute.
- Mettez-le dans un nym. Mettez-le dans Récupérer un cadeau dans l'application Nymbot, ou appelez
POST /api/v1/l402/refunds/redeemAvec A Demande signée et{"refund_token": "REFUND-...", "balance": "standard"}(ou de"pro"Les crédits entiers vont au solde (10 taux chacun sur le standard, 100 sur le Pro); les taux qui ne font pas un crédit entier rester sur le jeton pour les demandes 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"
Réponse
{ "data": { "credited": 23, "tier": "standard", "balance_credits": 123, "remaining_sats": 7 } }